mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-16 16:51:51 +00:00
feat(android): localize onboarding and navigation
This commit is contained in:
@@ -3,6 +3,7 @@ package ai.openclaw.app.ui
|
||||
import ai.openclaw.app.GatewayModelProviderSummary
|
||||
import ai.openclaw.app.GatewayModelSummary
|
||||
import ai.openclaw.app.MainViewModel
|
||||
import ai.openclaw.app.i18n.nativeString
|
||||
import ai.openclaw.app.ui.design.ClawEmptyState
|
||||
import ai.openclaw.app.ui.design.ClawPanel
|
||||
import ai.openclaw.app.ui.design.ClawPlainIconButton
|
||||
@@ -97,25 +98,25 @@ internal fun CommandPalette(
|
||||
) {
|
||||
ClawPlainIconButton(
|
||||
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Close search",
|
||||
contentDescription = nativeString("Close search"),
|
||||
onClick = onDismiss,
|
||||
)
|
||||
Text(text = "Search", style = ClawTheme.type.title, color = ClawTheme.colors.text, modifier = Modifier.weight(1f), textAlign = TextAlign.Center)
|
||||
Text(text = nativeString("Search"), style = ClawTheme.type.title, color = ClawTheme.colors.text, modifier = Modifier.weight(1f), textAlign = TextAlign.Center)
|
||||
CommandAvatar(text = "OC")
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
ClawTextField(value = query, onValueChange = { query = it }, placeholder = "Search OpenClaw")
|
||||
ClawTextField(value = query, onValueChange = { query = it }, placeholder = nativeString("Search OpenClaw"))
|
||||
}
|
||||
|
||||
item {
|
||||
CommandSectionLabel(title = "Quick actions")
|
||||
CommandSectionLabel(title = nativeString("Quick actions"))
|
||||
}
|
||||
|
||||
if (actionRows.isEmpty()) {
|
||||
item {
|
||||
ClawEmptyState(title = "No actions found", body = "Try Chat, Voice, Sessions, Providers, or Settings.")
|
||||
ClawEmptyState(title = nativeString("No actions found"), body = nativeString("Try Chat, Voice, Sessions, Providers, or Settings."))
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
@@ -124,14 +125,14 @@ internal fun CommandPalette(
|
||||
}
|
||||
|
||||
item {
|
||||
CommandSectionLabel(title = "Sessions")
|
||||
CommandSectionLabel(title = nativeString("Sessions"))
|
||||
}
|
||||
|
||||
if (sessionRows.isEmpty()) {
|
||||
item {
|
||||
ClawPanel {
|
||||
Text(
|
||||
text = if (isConnected) "No matching sessions yet." else "Connect the Gateway to search sessions.",
|
||||
text = if (isConnected) nativeString("No matching sessions yet.") else nativeString("Connect the Gateway to search sessions."),
|
||||
style = ClawTheme.type.body,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
)
|
||||
@@ -145,7 +146,7 @@ internal fun CommandPalette(
|
||||
CommandSessionRow(
|
||||
key = session.key,
|
||||
title = commandSessionTitle(session.displayName),
|
||||
subtitle = if (pendingRunCount > 0) "Assistant working" else "OpenClaw session",
|
||||
subtitle = if (pendingRunCount > 0) nativeString("Assistant working") else nativeString("OpenClaw session"),
|
||||
metadata = session.updatedAtMs?.let(::commandRelativeTime) ?: "now",
|
||||
)
|
||||
},
|
||||
@@ -200,10 +201,10 @@ private fun CommandActionRow(row: CommandItem) {
|
||||
) {
|
||||
CommandRowIcon(icon = row.icon)
|
||||
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) {
|
||||
Text(text = row.title, style = ClawTheme.type.body, color = ClawTheme.colors.text, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = row.subtitle, style = ClawTheme.type.caption, color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = nativeString(row.title), style = ClawTheme.type.body, color = ClawTheme.colors.text, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = nativeString(row.subtitle), style = ClawTheme.type.caption, color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
CommandRowChevron(contentDescription = "Open ${row.title}")
|
||||
CommandRowChevron(contentDescription = nativeString("Open \${row.title}", row.title))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,7 +244,7 @@ private fun CommandSessionListRow(
|
||||
Text(text = row.subtitle, style = ClawTheme.type.caption, color = ClawTheme.colors.textSubtle, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
Text(text = row.metadata, style = ClawTheme.type.caption, color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
CommandRowChevron(contentDescription = "Open session")
|
||||
CommandRowChevron(contentDescription = nativeString("Open session"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,16 +302,16 @@ internal fun providerCommandSubtitle(
|
||||
providers: List<GatewayModelProviderSummary>,
|
||||
models: List<GatewayModelSummary>,
|
||||
): String {
|
||||
if (!isConnected) return "Connect Gateway to view providers"
|
||||
if (!isConnected) return nativeString("Connect Gateway to view providers")
|
||||
val rows = providerRows(providers = providers, models = models)
|
||||
val readyProviderCount = rows.count { it.ready }
|
||||
if (readyProviderCount > 0) return "$readyProviderCount providers ready"
|
||||
if (rows.any { it.availability == ProviderAvailability.Unknown }) return "Provider availability unknown"
|
||||
return "No ready providers"
|
||||
if (readyProviderCount > 0) return nativeString("\$readyProviderCount providers ready", readyProviderCount)
|
||||
if (rows.any { it.availability == ProviderAvailability.Unknown }) return nativeString("Provider availability unknown")
|
||||
return nativeString("No ready providers")
|
||||
}
|
||||
|
||||
/** Falls back to the canonical main-session label when gateway display names are blank. */
|
||||
private fun commandSessionTitle(displayName: String?): String = displayName?.takeIf { it.isNotBlank() } ?: "Main session"
|
||||
private fun commandSessionTitle(displayName: String?): String = displayName?.takeIf { it.isNotBlank() } ?: nativeString("Main session")
|
||||
|
||||
/** Formats command-palette session timestamps for compact rows. */
|
||||
private fun commandRelativeTime(updatedAtMs: Long): String {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ai.openclaw.app.ui
|
||||
|
||||
import ai.openclaw.app.gateway.isLocalCleartextGatewayHost
|
||||
import ai.openclaw.app.i18n.nativeString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
@@ -304,26 +305,26 @@ internal fun gatewayEndpointValidationMessage(
|
||||
GatewayEndpointValidationError.INSECURE_REMOTE_URL ->
|
||||
when (source) {
|
||||
GatewayEndpointInputSource.SETUP_CODE ->
|
||||
"Setup code points to an insecure remote gateway. $remoteGatewaySecurityRule $remoteGatewaySecurityFix"
|
||||
nativeString("Setup code points to an insecure remote gateway. \$remoteGatewaySecurityRule \$remoteGatewaySecurityFix", remoteGatewaySecurityRule, remoteGatewaySecurityFix)
|
||||
GatewayEndpointInputSource.QR_SCAN ->
|
||||
"QR code points to an insecure remote gateway. $remoteGatewaySecurityRule $remoteGatewaySecurityFix"
|
||||
nativeString("QR code points to an insecure remote gateway. \$remoteGatewaySecurityRule \$remoteGatewaySecurityFix", remoteGatewaySecurityRule, remoteGatewaySecurityFix)
|
||||
GatewayEndpointInputSource.MANUAL ->
|
||||
"$remoteGatewaySecurityRule $remoteGatewaySecurityFix"
|
||||
nativeString("\$remoteGatewaySecurityRule \$remoteGatewaySecurityFix", remoteGatewaySecurityRule, remoteGatewaySecurityFix)
|
||||
}
|
||||
GatewayEndpointValidationError.IPV6_ZONE_ID_UNSUPPORTED ->
|
||||
when (source) {
|
||||
GatewayEndpointInputSource.SETUP_CODE ->
|
||||
"Setup code uses an IPv6 zone ID. Use an unscoped IPv6 address or a LAN hostname."
|
||||
nativeString("Setup code uses an IPv6 zone ID. Use an unscoped IPv6 address or a LAN hostname.")
|
||||
GatewayEndpointInputSource.QR_SCAN ->
|
||||
"QR code uses an IPv6 zone ID. Use an unscoped IPv6 address or a LAN hostname."
|
||||
nativeString("QR code uses an IPv6 zone ID. Use an unscoped IPv6 address or a LAN hostname.")
|
||||
GatewayEndpointInputSource.MANUAL ->
|
||||
"IPv6 zone IDs are not supported. Use an unscoped IPv6 address or a LAN hostname."
|
||||
nativeString("IPv6 zone IDs are not supported. Use an unscoped IPv6 address or a LAN hostname.")
|
||||
}
|
||||
GatewayEndpointValidationError.INVALID_URL ->
|
||||
when (source) {
|
||||
GatewayEndpointInputSource.SETUP_CODE -> "Setup code has invalid gateway URL."
|
||||
GatewayEndpointInputSource.QR_SCAN -> "QR code did not contain a valid setup code."
|
||||
GatewayEndpointInputSource.MANUAL -> "Enter a valid manual endpoint to connect."
|
||||
GatewayEndpointInputSource.SETUP_CODE -> nativeString("Setup code has invalid gateway URL.")
|
||||
GatewayEndpointInputSource.QR_SCAN -> nativeString("QR code did not contain a valid setup code.")
|
||||
GatewayEndpointInputSource.MANUAL -> nativeString("Enter a valid manual endpoint to connect.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import ai.openclaw.app.GatewayConnectionProblem
|
||||
import ai.openclaw.app.GatewayNodeApprovalState
|
||||
import ai.openclaw.app.GatewayNodeCapabilityApproval
|
||||
import ai.openclaw.app.gateway.normalizeGatewayApprovalRequestId
|
||||
import ai.openclaw.app.i18n.nativeString
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
@@ -22,7 +23,7 @@ internal fun openClawAndroidVersionLabel(): String {
|
||||
}
|
||||
|
||||
/** Normalizes blank gateway status text for display and diagnostics copy. */
|
||||
internal fun gatewayStatusForDisplay(statusText: String): String = statusText.trim().ifEmpty { "Offline" }
|
||||
internal fun gatewayStatusForDisplay(statusText: String): String = statusText.trim().ifEmpty { nativeString("Offline") }
|
||||
|
||||
/** Resolves the best non-secret endpoint label available to diagnostics surfaces. */
|
||||
internal fun gatewayDiagnosticsEndpoint(
|
||||
@@ -77,15 +78,15 @@ private enum class GatewayAuthRecoveryLabelKind {
|
||||
|
||||
private fun gatewayAuthRecoveryLabel(kind: GatewayAuthRecoveryLabelKind): String =
|
||||
when (kind) {
|
||||
GatewayAuthRecoveryLabelKind.SETUP_CODE_EXPIRED -> "Setup code expired"
|
||||
GatewayAuthRecoveryLabelKind.TOKEN_NEEDED -> "Gateway token needed"
|
||||
GatewayAuthRecoveryLabelKind.TOKEN_NOT_CONFIGURED -> "Gateway token not configured"
|
||||
GatewayAuthRecoveryLabelKind.PASSWORD_NEEDED -> "Gateway password needed"
|
||||
GatewayAuthRecoveryLabelKind.PASSWORD_INVALID -> "Gateway password invalid"
|
||||
GatewayAuthRecoveryLabelKind.PASSWORD_NOT_CONFIGURED -> "Gateway password not configured"
|
||||
GatewayAuthRecoveryLabelKind.ACCESS_NEEDS_REVIEW -> "Gateway access needs review"
|
||||
GatewayAuthRecoveryLabelKind.SAVED_AUTH_INVALID -> "Saved auth invalid"
|
||||
GatewayAuthRecoveryLabelKind.DEVICE_IDENTITY_REQUIRED -> "Device identity required"
|
||||
GatewayAuthRecoveryLabelKind.SETUP_CODE_EXPIRED -> nativeString("Setup code expired")
|
||||
GatewayAuthRecoveryLabelKind.TOKEN_NEEDED -> nativeString("Gateway token needed")
|
||||
GatewayAuthRecoveryLabelKind.TOKEN_NOT_CONFIGURED -> nativeString("Gateway token not configured")
|
||||
GatewayAuthRecoveryLabelKind.PASSWORD_NEEDED -> nativeString("Gateway password needed")
|
||||
GatewayAuthRecoveryLabelKind.PASSWORD_INVALID -> nativeString("Gateway password invalid")
|
||||
GatewayAuthRecoveryLabelKind.PASSWORD_NOT_CONFIGURED -> nativeString("Gateway password not configured")
|
||||
GatewayAuthRecoveryLabelKind.ACCESS_NEEDS_REVIEW -> nativeString("Gateway access needs review")
|
||||
GatewayAuthRecoveryLabelKind.SAVED_AUTH_INVALID -> nativeString("Saved auth invalid")
|
||||
GatewayAuthRecoveryLabelKind.DEVICE_IDENTITY_REQUIRED -> nativeString("Device identity required")
|
||||
}
|
||||
|
||||
/** Returns the exact host command for one node's approval state when available. */
|
||||
@@ -168,5 +169,5 @@ internal fun copyGatewayDiagnosticsReport(
|
||||
val clipboard = context.getSystemService(ClipboardManager::class.java) ?: return
|
||||
val report = buildGatewayDiagnosticsReport(screen = screen, gatewayAddress = gatewayAddress, statusText = statusText)
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText("OpenClaw gateway diagnostics", report))
|
||||
Toast.makeText(context, "Copied gateway diagnostics", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, nativeString("Copied gateway diagnostics"), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import ai.openclaw.app.SensitiveFeatureConfig
|
||||
import ai.openclaw.app.gateway.GatewayEndpoint
|
||||
import ai.openclaw.app.gateway.isLocalCleartextGatewayHost
|
||||
import ai.openclaw.app.hasPhotoReadPermission
|
||||
import ai.openclaw.app.i18n.nativeString
|
||||
import ai.openclaw.app.node.DeviceNotificationListenerService
|
||||
import ai.openclaw.app.photoReadPermissionsForRequest
|
||||
import ai.openclaw.app.ui.design.ClawDesignTheme
|
||||
@@ -695,12 +696,12 @@ fun OnboardingFlow(
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = viewModel::acceptGatewayTrustPrompt) {
|
||||
Text("Trust")
|
||||
Text(nativeString("Trust"))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = viewModel::declineGatewayTrustPrompt) {
|
||||
Text("Cancel")
|
||||
Text(nativeString("Cancel"))
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -891,8 +892,8 @@ private fun WelcomeScreen(
|
||||
Column(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
OnboardingHeroTopSpacer(afterHeader = false)
|
||||
OnboardingIntroHero(
|
||||
title = "Welcome to OpenClaw",
|
||||
subtitle = "Turn this device into a secure OpenClaw node for chat, voice, camera, and device tools.",
|
||||
title = nativeString("Welcome to OpenClaw"),
|
||||
subtitle = nativeString("Turn this device into a secure OpenClaw node for chat, voice, camera, and device tools."),
|
||||
mark = { WelcomeLogo() },
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
@@ -901,7 +902,7 @@ private fun WelcomeScreen(
|
||||
SecurityNotice()
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
OnboardingActions {
|
||||
ClawPrimaryButton(text = "Continue", onClick = onConnect, modifier = Modifier.onboardingActionButton())
|
||||
ClawPrimaryButton(text = nativeString("Continue"), onClick = onConnect, modifier = Modifier.onboardingActionButton())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -917,7 +918,7 @@ private fun WelcomeLogo() {
|
||||
border = BorderStroke(1.dp, ClawTheme.colors.border),
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize().padding(12.dp), contentAlignment = Alignment.Center) {
|
||||
OpenClawMascot(contentDescription = "OpenClaw logo", modifier = Modifier.fillMaxSize())
|
||||
OpenClawMascot(contentDescription = nativeString("OpenClaw logo"), modifier = Modifier.fillMaxSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -973,9 +974,9 @@ private fun OnboardingHeroTopSpacer(afterHeader: Boolean) {
|
||||
private fun WelcomeChecklist() {
|
||||
SoftPanel {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(13.dp)) {
|
||||
WelcomeChecklistRow(icon = Icons.Default.Link, text = "Connect to your Gateway")
|
||||
WelcomeChecklistRow(icon = Icons.Default.Security, text = "Choose device permissions")
|
||||
WelcomeChecklistRow(icon = Icons.Default.CheckCircle, text = "Use OpenClaw from your phone")
|
||||
WelcomeChecklistRow(icon = Icons.Default.Link, text = nativeString("Connect to your Gateway"))
|
||||
WelcomeChecklistRow(icon = Icons.Default.Security, text = nativeString("Choose device permissions"))
|
||||
WelcomeChecklistRow(icon = Icons.Default.CheckCircle, text = nativeString("Use OpenClaw from your phone"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -997,9 +998,9 @@ private fun SecurityNotice() {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.Top) {
|
||||
Icon(imageVector = Icons.Default.ErrorOutline, contentDescription = null, modifier = Modifier.size(24.dp), tint = ClawTheme.colors.warning)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
Text(text = "Security notice", style = ClawTheme.type.section, color = ClawTheme.colors.text)
|
||||
Text(text = nativeString("Security notice"), style = ClawTheme.type.section, color = ClawTheme.colors.text)
|
||||
Text(
|
||||
text = "The connected OpenClaw agent can use device capabilities you enable. Continue only if you trust the Gateway and agent you connect to.",
|
||||
text = nativeString("The connected OpenClaw agent can use device capabilities you enable. Continue only if you trust the Gateway and agent you connect to."),
|
||||
style = ClawTheme.type.body,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
)
|
||||
@@ -1040,8 +1041,8 @@ private fun GatewaySetupScreen(
|
||||
OnboardingHeroTopSpacer(afterHeader = true)
|
||||
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
OnboardingIntroHero(
|
||||
title = "Connect Gateway",
|
||||
subtitle = "Scan a QR code or use the setup code from your OpenClaw Gateway.",
|
||||
title = nativeString("Connect Gateway"),
|
||||
subtitle = nativeString("Scan a QR code or use the setup code from your OpenClaw Gateway."),
|
||||
mark = { GatewayLogo() },
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
@@ -1050,7 +1051,7 @@ private fun GatewaySetupScreen(
|
||||
runCatching {
|
||||
uriHandler.openUri(ANDROID_SETUP_GUIDE_URL)
|
||||
}.onFailure {
|
||||
Toast.makeText(context, "Could not open setup guide.", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, nativeString("Could not open setup guide."), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -1058,13 +1059,13 @@ private fun GatewaySetupScreen(
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
OnboardingActions {
|
||||
ClawPrimaryButton(
|
||||
text = "Scan QR or setup code",
|
||||
text = nativeString("Scan QR or setup code"),
|
||||
icon = Icons.Default.QrCode2,
|
||||
onClick = onSetupCode,
|
||||
modifier = Modifier.onboardingActionButton(),
|
||||
)
|
||||
ClawSecondaryButton(
|
||||
text = "Set up manually",
|
||||
text = nativeString("Set up manually"),
|
||||
icon = Icons.Default.Link,
|
||||
onClick = onManualSetup,
|
||||
modifier = Modifier.onboardingActionButton(),
|
||||
@@ -1086,24 +1087,24 @@ private fun OnboardingActions(content: @Composable ColumnScope.() -> Unit) {
|
||||
private fun GatewayPrerequisites(onOpenSetupGuide: () -> Unit) {
|
||||
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(14.dp)) {
|
||||
Text(
|
||||
text = "Before you start",
|
||||
text = nativeString("Before you start"),
|
||||
style = ClawTheme.type.label,
|
||||
color = ClawTheme.colors.text,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
GatewayPrerequisiteRow(
|
||||
title = "Access to the Gateway device",
|
||||
body = "Have a terminal open on the device running OpenClaw.",
|
||||
title = nativeString("Access to the Gateway device"),
|
||||
body = nativeString("Have a terminal open on the device running OpenClaw."),
|
||||
)
|
||||
GatewayPrerequisiteRow(
|
||||
title = "Phone can reach the Gateway",
|
||||
body = "Use the same network, or a secure remote Gateway URL.",
|
||||
title = nativeString("Phone can reach the Gateway"),
|
||||
body = nativeString("Use the same network, or a secure remote Gateway URL."),
|
||||
)
|
||||
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||
TextButton(onClick = onOpenSetupGuide) {
|
||||
Icon(imageVector = Icons.Default.Link, contentDescription = null, modifier = Modifier.size(16.dp), tint = ClawTheme.colors.primary)
|
||||
Spacer(modifier = Modifier.width(7.dp))
|
||||
Text(text = "Android setup guide", style = ClawTheme.type.label, color = ClawTheme.colors.primary)
|
||||
Text(text = nativeString("Android setup guide"), style = ClawTheme.type.label, color = ClawTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1146,19 +1147,19 @@ private fun SetupCodeInstructionsScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(18.dp),
|
||||
) {
|
||||
item {
|
||||
OnboardingHeader(title = "Setup Gateway", onBack = onBack)
|
||||
OnboardingHeader(title = nativeString("Setup Gateway"), onBack = onBack)
|
||||
}
|
||||
item {
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(top = 8.dp), verticalArrangement = Arrangement.spacedBy(18.dp)) {
|
||||
SetupInstruction(
|
||||
step = "Step 1",
|
||||
title = "Start your Gateway.",
|
||||
title = nativeString("Start your Gateway."),
|
||||
body = "openclaw gateway",
|
||||
monospaceBody = true,
|
||||
)
|
||||
SetupInstruction(
|
||||
step = "Step 2",
|
||||
title = "Generate a QR code.",
|
||||
title = nativeString("Generate a QR code."),
|
||||
body = "openclaw qr",
|
||||
monospaceBody = true,
|
||||
)
|
||||
@@ -1179,13 +1180,13 @@ private fun SetupCodeInstructionsScreen(
|
||||
}
|
||||
OnboardingActions {
|
||||
ClawSecondaryButton(
|
||||
text = "Choose from gallery",
|
||||
text = nativeString("Choose from gallery"),
|
||||
icon = Icons.Default.Image,
|
||||
onClick = onChooseFromGallery,
|
||||
modifier = Modifier.onboardingActionButton(),
|
||||
)
|
||||
ClawSecondaryButton(
|
||||
text = "Enter setup code",
|
||||
text = nativeString("Enter setup code"),
|
||||
icon = Icons.Default.QrCode2,
|
||||
onClick = onEnterSetupCode,
|
||||
modifier = Modifier.onboardingActionButton(),
|
||||
@@ -1233,7 +1234,7 @@ private fun SetupScanErrorDialog(
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = "QR code not accepted",
|
||||
text = nativeString("QR code not accepted"),
|
||||
style = ClawTheme.type.title,
|
||||
color = ClawTheme.colors.text,
|
||||
modifier = Modifier.weight(1f),
|
||||
@@ -1248,13 +1249,13 @@ private fun SetupScanErrorDialog(
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(OnboardingActionGap), modifier = Modifier.fillMaxWidth()) {
|
||||
ClawPrimaryButton(
|
||||
text = "Choose another image",
|
||||
text = nativeString("Choose another image"),
|
||||
icon = Icons.Default.Image,
|
||||
onClick = onChooseAnotherImage,
|
||||
modifier = Modifier.onboardingActionButton(),
|
||||
)
|
||||
ClawSecondaryButton(
|
||||
text = "Enter setup code",
|
||||
text = nativeString("Enter setup code"),
|
||||
icon = Icons.Default.QrCode2,
|
||||
onClick = onEnterSetupCode,
|
||||
modifier = Modifier.onboardingActionButton(),
|
||||
@@ -1301,9 +1302,9 @@ private fun ScanQrTile(
|
||||
Icon(imageVector = Icons.Default.CameraAlt, contentDescription = null, modifier = Modifier.size(28.dp))
|
||||
}
|
||||
}
|
||||
Text(text = "Scan QR code", style = ClawTheme.type.title.copy(fontSize = 20.sp, lineHeight = 25.sp), color = ClawTheme.colors.text, textAlign = TextAlign.Center)
|
||||
Text(text = nativeString("Scan QR code"), style = ClawTheme.type.title.copy(fontSize = 20.sp, lineHeight = 25.sp), color = ClawTheme.colors.text, textAlign = TextAlign.Center)
|
||||
Text(
|
||||
text = "Open the camera and frame the code from openclaw qr.",
|
||||
text = nativeString("Open the camera and frame the code from openclaw qr."),
|
||||
style = ClawTheme.type.caption,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
textAlign = TextAlign.Center,
|
||||
@@ -1337,7 +1338,7 @@ private fun ScanQrTile(
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "Align the QR code inside the square.",
|
||||
text = nativeString("Align the QR code inside the square."),
|
||||
style = ClawTheme.type.caption,
|
||||
color = Color.White,
|
||||
modifier =
|
||||
@@ -1355,12 +1356,12 @@ private fun ScanQrTile(
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Icon(imageVector = Icons.Default.CameraAlt, contentDescription = null, modifier = Modifier.size(36.dp), tint = ClawTheme.colors.text)
|
||||
Text(
|
||||
text = "Camera access is needed to scan the setup QR.",
|
||||
text = nativeString("Camera access is needed to scan the setup QR."),
|
||||
style = ClawTheme.type.body,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
ClawPrimaryButton(text = "Allow camera", icon = Icons.Default.CameraAlt, onClick = onRequestCameraPermission, modifier = Modifier.onboardingActionButton())
|
||||
ClawPrimaryButton(text = nativeString("Allow camera"), icon = Icons.Default.CameraAlt, onClick = onRequestCameraPermission, modifier = Modifier.onboardingActionButton())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1381,7 +1382,7 @@ private fun ScannerCloseButton(
|
||||
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.26f)),
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(imageVector = Icons.Default.Close, contentDescription = "Close scanner", modifier = Modifier.size(20.dp))
|
||||
Icon(imageVector = Icons.Default.Close, contentDescription = nativeString("Close scanner"), modifier = Modifier.size(20.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1543,20 +1544,20 @@ private fun SetupCodeEntryScreen(
|
||||
ClawScaffold(modifier = modifier, contentPadding = onboardingContentPadding()) {
|
||||
Column(modifier = Modifier.fillMaxSize().imePadding(), verticalArrangement = Arrangement.SpaceBetween) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(18.dp)) {
|
||||
OnboardingHeader(title = "Enter setup code", onBack = onBack)
|
||||
LabeledField(label = "Setup code") {
|
||||
OnboardingHeader(title = nativeString("Enter setup code"), onBack = onBack)
|
||||
LabeledField(label = nativeString("Setup code")) {
|
||||
ClawTextField(
|
||||
value = setupCode,
|
||||
onValueChange = onSetupCodeChange,
|
||||
placeholder = "Paste setup code",
|
||||
placeholder = nativeString("Paste setup code"),
|
||||
)
|
||||
}
|
||||
error?.let { message ->
|
||||
InlineError(title = "Setup code was not accepted", body = message)
|
||||
InlineError(title = nativeString("Setup code was not accepted"), body = message)
|
||||
}
|
||||
}
|
||||
OnboardingActions {
|
||||
ClawPrimaryButton(text = "Use setup code", icon = Icons.Default.QrCode2, onClick = onUseSetupCode, modifier = Modifier.onboardingActionButton())
|
||||
ClawPrimaryButton(text = nativeString("Use setup code"), icon = Icons.Default.QrCode2, onClick = onUseSetupCode, modifier = Modifier.onboardingActionButton())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1594,47 +1595,47 @@ private fun ManualGatewaySetupScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
item {
|
||||
OnboardingHeader(title = "Manual setup", onBack = onBack)
|
||||
OnboardingHeader(title = nativeString("Manual setup"), onBack = onBack)
|
||||
}
|
||||
item {
|
||||
LabeledField(label = "Gateway URL") {
|
||||
LabeledField(label = nativeString("Gateway URL")) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
ClawTextField(value = manualHost, onValueChange = onManualHostChange, placeholder = "Host", modifier = Modifier.weight(1f))
|
||||
ClawTextField(value = manualPort, onValueChange = onManualPortChange, placeholder = "Port", modifier = Modifier.width(104.dp))
|
||||
ClawTextField(value = manualHost, onValueChange = onManualHostChange, placeholder = nativeString("Host"), modifier = Modifier.weight(1f))
|
||||
ClawTextField(value = manualPort, onValueChange = onManualPortChange, placeholder = nativeString("Port"), modifier = Modifier.width(104.dp))
|
||||
}
|
||||
Text(
|
||||
text = "Use the Gateway computer's LAN address or secure remote hostname.",
|
||||
text = nativeString("Use the Gateway computer's LAN address or secure remote hostname."),
|
||||
style = ClawTheme.type.caption,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
LabeledField(label = "Token") {
|
||||
ClawTextField(value = token, onValueChange = onTokenChange, placeholder = "Paste token")
|
||||
LabeledField(label = nativeString("Token")) {
|
||||
ClawTextField(value = token, onValueChange = onTokenChange, placeholder = nativeString("Paste token"))
|
||||
Text(
|
||||
text = "Paste a shared Gateway token or operator-issued token.",
|
||||
text = nativeString("Paste a shared Gateway token or operator-issued token."),
|
||||
style = ClawTheme.type.caption,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
LabeledField(label = "Password") {
|
||||
ClawTextField(value = password, onValueChange = onPasswordChange, placeholder = "Password optional")
|
||||
LabeledField(label = nativeString("Password")) {
|
||||
ClawTextField(value = password, onValueChange = onPasswordChange, placeholder = nativeString("Password optional"))
|
||||
}
|
||||
}
|
||||
item {
|
||||
LabeledField(label = "Connection security") {
|
||||
LabeledField(label = nativeString("Connection security")) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(9.dp)) {
|
||||
TogglePill(
|
||||
text = "Unencrypted",
|
||||
text = nativeString("Unencrypted"),
|
||||
selected = !transport.effectiveTls,
|
||||
enabled = !transport.requiresTls,
|
||||
onClick = { onManualTlsChange(false) },
|
||||
)
|
||||
TogglePill(
|
||||
text = "Secure (TLS)",
|
||||
text = nativeString("Secure (TLS)"),
|
||||
selected = transport.effectiveTls,
|
||||
onClick = { onManualTlsChange(true) },
|
||||
)
|
||||
@@ -1650,12 +1651,12 @@ private fun ManualGatewaySetupScreen(
|
||||
}
|
||||
error?.let { message ->
|
||||
item {
|
||||
InlineError(title = "Could not test connection", body = message)
|
||||
InlineError(title = nativeString("Could not test connection"), body = message)
|
||||
}
|
||||
}
|
||||
}
|
||||
OnboardingActions {
|
||||
ClawPrimaryButton(text = "Test connection", icon = Icons.Default.Security, onClick = onPair, modifier = Modifier.onboardingActionButton())
|
||||
ClawPrimaryButton(text = nativeString("Test connection"), icon = Icons.Default.Security, onClick = onPair, modifier = Modifier.onboardingActionButton())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1740,18 +1741,21 @@ private fun GatewayRecoveryScreen(
|
||||
val protocolUpdateCommand = recoveryGatewayProtocolMismatchCommand(gatewayConnectionProblem)
|
||||
val recoveryTitle =
|
||||
when {
|
||||
recoveryState == GatewayRecoveryUiState.Connected -> "Gateway paired"
|
||||
gatewayConnectionProblem?.code == "AUTH_BOOTSTRAP_TOKEN_INVALID" -> "Setup code was not accepted"
|
||||
else -> recoveryState.title
|
||||
recoveryState == GatewayRecoveryUiState.Connected -> nativeString("Gateway paired")
|
||||
gatewayConnectionProblem?.code == "AUTH_BOOTSTRAP_TOKEN_INVALID" ->
|
||||
nativeString("Setup code was not accepted")
|
||||
else -> nativeString(recoveryState.title)
|
||||
}
|
||||
val recoveryMessage =
|
||||
when {
|
||||
recoveryState == GatewayRecoveryUiState.Connected ->
|
||||
"Your phone is paired with ${recoveryGatewayName(serverName = serverName, attemptedGatewayName = attemptedGatewayName)}. " +
|
||||
"Continue to finish node access."
|
||||
nativeString(
|
||||
"Your phone is paired with \${recoveryGatewayName(serverName = serverName, attemptedGatewayName = attemptedGatewayName)}. Continue to finish node access.",
|
||||
recoveryGatewayName(serverName = serverName, attemptedGatewayName = attemptedGatewayName),
|
||||
)
|
||||
gatewayConnectionProblem != null && recoveryState == GatewayRecoveryUiState.Failed ->
|
||||
recoveryGatewayAuthDetail(gatewayConnectionProblem)
|
||||
else -> recoveryState.message
|
||||
else -> nativeString(recoveryState.message)
|
||||
}
|
||||
val recoveryProgressItems =
|
||||
gatewayRecoveryProgressItems(
|
||||
@@ -1797,8 +1801,8 @@ private fun GatewayRecoveryScreen(
|
||||
OnboardingHeader(
|
||||
title =
|
||||
when (recoveryState) {
|
||||
GatewayRecoveryUiState.Connected -> "Gateway paired"
|
||||
else -> "Pair Gateway"
|
||||
GatewayRecoveryUiState.Connected -> nativeString("Gateway paired")
|
||||
else -> nativeString("Pair Gateway")
|
||||
},
|
||||
onBack = onBack,
|
||||
)
|
||||
@@ -1830,7 +1834,7 @@ private fun GatewayRecoveryScreen(
|
||||
protocolUpdateCommand?.let { command ->
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
Text(
|
||||
text = "On the Gateway computer, run:",
|
||||
text = nativeString("On the Gateway computer, run:"),
|
||||
style = ClawTheme.type.caption,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
)
|
||||
@@ -1844,7 +1848,7 @@ private fun GatewayRecoveryScreen(
|
||||
if (showDiagnosticAction) {
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
TextButton(onClick = { diagnosticDialogVisible = true }) {
|
||||
Text("View details", style = ClawTheme.type.body, color = ClawTheme.colors.text)
|
||||
Text(nativeString("View details"), style = ClawTheme.type.body, color = ClawTheme.colors.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1852,7 +1856,7 @@ private fun GatewayRecoveryScreen(
|
||||
primaryAction?.let { action ->
|
||||
OnboardingActions {
|
||||
ClawPrimaryButton(
|
||||
text = action.text,
|
||||
text = nativeString(action.text),
|
||||
icon = action.icon,
|
||||
onClick =
|
||||
when (action) {
|
||||
@@ -1877,7 +1881,7 @@ private fun GatewayRecoveryDiagnosticDialog(
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
containerColor = ClawTheme.colors.surfaceRaised,
|
||||
title = { Text("Connection details", style = ClawTheme.type.section, color = ClawTheme.colors.text) },
|
||||
title = { Text(nativeString("Connection details"), style = ClawTheme.type.section, color = ClawTheme.colors.text) },
|
||||
text = {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
@@ -1889,12 +1893,12 @@ private fun GatewayRecoveryDiagnosticDialog(
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onCopy) {
|
||||
Text("Copy")
|
||||
Text(nativeString("Copy"))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("Close")
|
||||
Text(nativeString("Close"))
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -1906,7 +1910,7 @@ private fun copyGatewayDiagnostic(
|
||||
) {
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText("OpenClaw gateway diagnostic", diagnosticText))
|
||||
Toast.makeText(context, "Details copied", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, nativeString("Details copied"), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -1976,7 +1980,7 @@ private fun NodeApprovalScreen(
|
||||
|
||||
ClawScaffold(modifier = modifier, contentPadding = onboardingContentPadding()) {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
OnboardingHeader(title = "Approve node access", onBack = onBack)
|
||||
OnboardingHeader(title = nativeString("Approve node access"), onBack = onBack)
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
@@ -1991,14 +1995,14 @@ private fun NodeApprovalScreen(
|
||||
GatewayRecoveryIcon(state = GatewayRecoveryUiState.NodeCapabilityApprovalPending)
|
||||
Spacer(modifier = Modifier.height(13.dp))
|
||||
Text(
|
||||
text = "Approve node access",
|
||||
text = nativeString("Approve node access"),
|
||||
style = ClawTheme.type.display,
|
||||
color = ClawTheme.colors.text,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Gateway pairing is complete. Approve this phone as a node so OpenClaw can use the device capabilities you enable.",
|
||||
text = nativeString("Gateway pairing is complete. Approve this phone as a node so OpenClaw can use the device capabilities you enable."),
|
||||
style = ClawTheme.type.body,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
textAlign = TextAlign.Center,
|
||||
@@ -2006,7 +2010,7 @@ private fun NodeApprovalScreen(
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(
|
||||
text = "On the Gateway computer, run:",
|
||||
text = nativeString("On the Gateway computer, run:"),
|
||||
style = ClawTheme.type.caption,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
textAlign = TextAlign.Center,
|
||||
@@ -2015,7 +2019,7 @@ private fun NodeApprovalScreen(
|
||||
ApprovalCommandBlock(command = "openclaw nodes pending", onCopy = { onCopyCommand("openclaw nodes pending") })
|
||||
ApprovalCommandBlock(command = approveCommand, onCopy = { onCopyCommand(approveCommand) })
|
||||
Text(
|
||||
text = "Use the requestId from the pending command in the approve command.",
|
||||
text = nativeString("Use the requestId from the pending command in the approve command."),
|
||||
style = ClawTheme.type.caption,
|
||||
color = ClawTheme.colors.textSubtle,
|
||||
textAlign = TextAlign.Center,
|
||||
@@ -2039,17 +2043,17 @@ private fun NodeApprovalScreen(
|
||||
if (showWaitingDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { waitingDialogDismissed = true },
|
||||
title = { Text(text = "Still waiting for approval") },
|
||||
title = { Text(text = nativeString("Still waiting for approval")) },
|
||||
text = {
|
||||
Text(
|
||||
text = "Run the approve command on the Gateway computer, then check again.",
|
||||
text = nativeString("Run the approve command on the Gateway computer, then check again."),
|
||||
style = ClawTheme.type.body,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { waitingDialogDismissed = true }) {
|
||||
Text(text = "OK")
|
||||
Text(text = nativeString("OK"))
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -2088,7 +2092,7 @@ private fun OnboardingLoadingPrimaryButton(
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
Text(text = if (loading) loadingText else text, style = ClawTheme.type.label)
|
||||
Text(text = nativeString(if (loading) loadingText else text), style = ClawTheme.type.label)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2199,7 +2203,7 @@ private fun ApprovalCommandBlock(
|
||||
border = BorderStroke(1.dp, ClawTheme.colors.border),
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(imageVector = Icons.Default.ContentCopy, contentDescription = "Copy approval command", modifier = Modifier.size(18.dp))
|
||||
Icon(imageVector = Icons.Default.ContentCopy, contentDescription = nativeString("Copy approval command"), modifier = Modifier.size(18.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2225,7 +2229,7 @@ private fun PermissionSetupScreen(
|
||||
}
|
||||
item {
|
||||
Text(
|
||||
text = "Only enable access you are comfortable letting OpenClaw use while this phone is connected. You can change these later in Android Settings.",
|
||||
text = nativeString("Only enable access you are comfortable letting OpenClaw use while this phone is connected. You can change these later in Android Settings."),
|
||||
style = ClawTheme.type.body,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
textAlign = TextAlign.Center,
|
||||
@@ -2237,7 +2241,7 @@ private fun PermissionSetupScreen(
|
||||
}
|
||||
}
|
||||
OnboardingActions {
|
||||
ClawPrimaryButton(text = "Continue", onClick = onContinue, modifier = Modifier.onboardingActionButton())
|
||||
ClawPrimaryButton(text = nativeString("Continue"), onClick = onContinue, modifier = Modifier.onboardingActionButton())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2264,7 +2268,7 @@ private fun OnboardingHeader(
|
||||
contentColor = ClawTheme.colors.text,
|
||||
) {
|
||||
Box(contentAlignment = Alignment.CenterStart) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", modifier = Modifier.size(23.dp))
|
||||
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = nativeString("Back"), modifier = Modifier.size(23.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2274,10 +2278,10 @@ private fun OnboardingHeader(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
if (title.isNotBlank()) {
|
||||
Text(text = title, style = ClawTheme.type.title, color = ClawTheme.colors.text, textAlign = TextAlign.Center)
|
||||
Text(text = nativeString(title), style = ClawTheme.type.title, color = ClawTheme.colors.text, textAlign = TextAlign.Center)
|
||||
}
|
||||
subtitle?.let {
|
||||
Text(text = it, style = ClawTheme.type.body, color = ClawTheme.colors.textMuted, textAlign = TextAlign.Center)
|
||||
Text(text = nativeString(it), style = ClawTheme.type.body, color = ClawTheme.colors.textMuted, textAlign = TextAlign.Center)
|
||||
}
|
||||
}
|
||||
action?.let {
|
||||
@@ -2313,7 +2317,7 @@ private fun TogglePill(
|
||||
|
||||
@Composable
|
||||
private fun PermissionTopBar(onBack: () -> Unit) {
|
||||
OnboardingHeader(title = "Permissions", onBack = onBack)
|
||||
OnboardingHeader(title = nativeString("Permissions"), onBack = onBack)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -2343,19 +2347,19 @@ private fun PermissionRow(row: PermissionRowModel) {
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text(
|
||||
text = row.title,
|
||||
text = nativeString(row.title),
|
||||
style = ClawTheme.type.title.copy(fontSize = 18.sp, lineHeight = 23.sp),
|
||||
color = ClawTheme.colors.text,
|
||||
)
|
||||
Text(
|
||||
text = row.subtitle,
|
||||
text = nativeString(row.subtitle),
|
||||
style = ClawTheme.type.body,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
imageVector = if (row.granted) Icons.Default.CheckCircle else Icons.Default.Close,
|
||||
contentDescription = row.statusText,
|
||||
contentDescription = nativeString(row.statusText),
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = if (row.granted) ClawTheme.colors.success else ClawTheme.colors.danger,
|
||||
)
|
||||
@@ -2526,7 +2530,7 @@ private fun finishingGatewayProgressItems(
|
||||
val nodeAccessCurrent = gatewayAccessComplete
|
||||
return listOf(
|
||||
GatewayRecoveryProgressItem(
|
||||
label = "Opening Gateway connection",
|
||||
label = nativeString("Opening Gateway connection"),
|
||||
status =
|
||||
if (gatewayAccessComplete) {
|
||||
GatewayRecoveryProgressStatus.Complete
|
||||
@@ -2535,7 +2539,7 @@ private fun finishingGatewayProgressItems(
|
||||
},
|
||||
),
|
||||
GatewayRecoveryProgressItem(
|
||||
label = "Checking pairing access",
|
||||
label = nativeString("Checking pairing access"),
|
||||
status =
|
||||
when {
|
||||
gatewayAccessComplete -> GatewayRecoveryProgressStatus.Complete
|
||||
@@ -2543,7 +2547,7 @@ private fun finishingGatewayProgressItems(
|
||||
},
|
||||
),
|
||||
GatewayRecoveryProgressItem(
|
||||
label = "Checking node access",
|
||||
label = nativeString("Checking node access"),
|
||||
status =
|
||||
when {
|
||||
nodeAccessCurrent -> GatewayRecoveryProgressStatus.Current
|
||||
@@ -2636,21 +2640,21 @@ internal fun recoveryGatewayDetail(
|
||||
internal fun recoveryGatewayAuthDetail(gatewayConnectionProblem: GatewayConnectionProblem): String =
|
||||
when (gatewayConnectionProblem.code) {
|
||||
"PROTOCOL_MISMATCH" -> recoveryGatewayProtocolMismatchDetail(gatewayConnectionProblem)
|
||||
"AUTH_BOOTSTRAP_TOKEN_INVALID" -> "The code may have expired or been generated for another Gateway."
|
||||
"AUTH_BOOTSTRAP_TOKEN_INVALID" -> nativeString("The code may have expired or been generated for another Gateway.")
|
||||
"AUTH_DEVICE_TOKEN_MISMATCH",
|
||||
"AUTH_TOKEN_MISMATCH",
|
||||
-> "Saved authentication is invalid. Re-authenticate or reset this gateway connection."
|
||||
"AUTH_PASSWORD_MISSING" -> "Gateway password is required. Enter it again or edit this connection."
|
||||
"AUTH_PASSWORD_MISMATCH" -> "Gateway password is invalid. Re-enter it or reset this gateway connection."
|
||||
"AUTH_TOKEN_MISSING" -> "Gateway token is required. Enter it again or edit this connection."
|
||||
-> nativeString("Saved authentication is invalid. Re-authenticate or reset this gateway connection.")
|
||||
"AUTH_PASSWORD_MISSING" -> nativeString("Gateway password is required. Enter it again or edit this connection.")
|
||||
"AUTH_PASSWORD_MISMATCH" -> nativeString("Gateway password is invalid. Re-enter it or reset this gateway connection.")
|
||||
"AUTH_TOKEN_MISSING" -> nativeString("Gateway token is required. Enter it again or edit this connection.")
|
||||
"CONTROL_UI_DEVICE_IDENTITY_REQUIRED",
|
||||
"DEVICE_IDENTITY_REQUIRED",
|
||||
-> "Gateway requires this device identity. Re-authenticate or reset this gateway connection."
|
||||
-> nativeString("Gateway requires this device identity. Re-authenticate or reset this gateway connection.")
|
||||
else ->
|
||||
when (gatewayConnectionProblem.recommendedNextStep) {
|
||||
"update_auth_credentials" -> "Saved authentication is invalid. Re-authenticate or reset this gateway connection."
|
||||
"update_auth_configuration" -> "Gateway authentication is not configured. Edit this connection and try again."
|
||||
"review_auth_configuration" -> "Gateway authentication needs review. Check gateway settings, then retry."
|
||||
"update_auth_credentials" -> nativeString("Saved authentication is invalid. Re-authenticate or reset this gateway connection.")
|
||||
"update_auth_configuration" -> nativeString("Gateway authentication is not configured. Edit this connection and try again.")
|
||||
"review_auth_configuration" -> nativeString("Gateway authentication needs review. Check gateway settings, then retry.")
|
||||
else -> gatewayConnectionProblem.message.takeIf { it.isNotBlank() } ?: "Gateway authentication needs attention."
|
||||
}
|
||||
}
|
||||
@@ -2798,7 +2802,7 @@ private fun copyApprovalCommand(
|
||||
) {
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText("OpenClaw pairing approval command", command))
|
||||
Toast.makeText(context, "Approval command copied", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, nativeString("Approval command copied"), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun copyGatewayCommand(
|
||||
@@ -2807,7 +2811,7 @@ private fun copyGatewayCommand(
|
||||
) {
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText("OpenClaw gateway command", command))
|
||||
Toast.makeText(context, "Command copied", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, nativeString("Command copied"), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
/** One permission row plus launcher callback for onboarding's final setup step. */
|
||||
@@ -2869,7 +2873,7 @@ internal fun cameraCapabilityAfterRowTap(
|
||||
androidCameraPermissionGranted: Boolean,
|
||||
): Boolean? = if (androidCameraPermissionGranted) !currentCapabilityEnabled else null
|
||||
|
||||
private fun permissionRowStatusText(granted: Boolean): String = if (granted) "Granted" else "Not granted"
|
||||
private fun permissionRowStatusText(granted: Boolean): String = if (granted) nativeString("Granted") else nativeString("Not granted")
|
||||
|
||||
internal fun permissionChangesRequireNodeApproval(
|
||||
currentCameraEnabled: Boolean,
|
||||
|
||||
@@ -16,6 +16,7 @@ import ai.openclaw.app.NodeRuntime
|
||||
import ai.openclaw.app.R
|
||||
import ai.openclaw.app.chat.ChatSessionEntry
|
||||
import ai.openclaw.app.currentAppLanguage
|
||||
import ai.openclaw.app.i18n.nativeString
|
||||
import ai.openclaw.app.node.CanvasController
|
||||
import ai.openclaw.app.ui.chat.ChatScreen
|
||||
import ai.openclaw.app.ui.design.AgentAvatarSource
|
||||
@@ -215,7 +216,10 @@ fun ShellScreen(
|
||||
bottomBar = {
|
||||
if (showBottomNav) {
|
||||
ClawBottomNav(
|
||||
items = shellNavTabs.map { ClawNavItem(key = it.key, label = it.label, icon = it.icon) },
|
||||
items =
|
||||
shellNavTabs.map {
|
||||
ClawNavItem(key = it.key, label = nativeString(it.label), icon = it.icon)
|
||||
},
|
||||
selectedKey = if (nav.activeTab in shellNavTabs) nav.activeTab.key else Tab.Overview.key,
|
||||
onSelect = { key ->
|
||||
nav.selectTab(shellNavTabs.firstOrNull { it.key == key } ?: Tab.Overview)
|
||||
@@ -344,7 +348,7 @@ private fun CanvasOverlay(
|
||||
if (visible) {
|
||||
ClawIconButton(
|
||||
icon = Icons.Default.Close,
|
||||
contentDescription = "Close Canvas",
|
||||
contentDescription = nativeString("Close Canvas"),
|
||||
onClick = onClose,
|
||||
modifier =
|
||||
Modifier
|
||||
@@ -492,7 +496,7 @@ private fun OverviewScreen(
|
||||
|
||||
item {
|
||||
Text(
|
||||
text = "Overview",
|
||||
text = nativeString("Overview"),
|
||||
style = ClawTheme.type.display.copy(fontSize = 24.sp, lineHeight = 28.sp),
|
||||
color = ClawTheme.colors.text,
|
||||
)
|
||||
@@ -538,9 +542,9 @@ private fun OverviewScreen(
|
||||
if (visibleRecentRows.isEmpty()) {
|
||||
item {
|
||||
ClawEmptyState(
|
||||
title = "No recent sessions",
|
||||
body = "Start a chat and your active OpenClaw conversations will appear here.",
|
||||
action = { ClawPrimaryButton(text = "Start Chat", onClick = { onSelectTab(Tab.Chat) }) },
|
||||
title = nativeString("No recent sessions"),
|
||||
body = nativeString("Start a chat and your active OpenClaw conversations will appear here."),
|
||||
action = { ClawPrimaryButton(text = nativeString("Start Chat"), onClick = { onSelectTab(Tab.Chat) }) },
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -586,7 +590,7 @@ private fun OverviewHeader(
|
||||
) {
|
||||
OpenClawMascot(modifier = Modifier.size(25.dp), tint = ClawTheme.colors.text)
|
||||
Text(
|
||||
text = "OpenClaw",
|
||||
text = nativeString("OpenClaw"),
|
||||
style = ClawTheme.type.title.copy(fontSize = 17.sp, lineHeight = 21.sp),
|
||||
color = ClawTheme.colors.text,
|
||||
modifier = Modifier.weight(1f),
|
||||
@@ -594,7 +598,7 @@ private fun OverviewHeader(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
OverviewStatusPill(status = status, onClick = onOpenStatus)
|
||||
ClawPlainIconButton(icon = Icons.Default.Search, contentDescription = "Search", onClick = onOpenCommand)
|
||||
ClawPlainIconButton(icon = Icons.Default.Search, contentDescription = nativeString("Search"), onClick = onOpenCommand)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,7 +628,7 @@ private fun OverviewStatusPill(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
Box(modifier = Modifier.size(6.dp).clip(CircleShape).background(dotColor))
|
||||
Text(text = status.label, style = ClawTheme.type.caption.copy(fontSize = 13.sp, lineHeight = 17.sp), color = ClawTheme.colors.text, maxLines = 1)
|
||||
Text(text = nativeString(status.label), style = ClawTheme.type.caption.copy(fontSize = 13.sp, lineHeight = 17.sp), color = ClawTheme.colors.text, maxLines = 1)
|
||||
Icon(imageVector = Icons.Default.ArrowDropDown, contentDescription = null, modifier = Modifier.size(15.dp), tint = ClawTheme.colors.textMuted)
|
||||
}
|
||||
}
|
||||
@@ -647,7 +651,7 @@ private fun OverviewPrimaryPanel(
|
||||
) {
|
||||
OverviewLayeredPanel(contentPadding = PaddingValues(ClawTheme.spacing.sm), elevated = true) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(ClawTheme.spacing.xs)) {
|
||||
Text(text = "ACTIVE AGENT", style = ClawTheme.type.caption.copy(fontSize = 12.sp, lineHeight = 15.sp), color = ClawTheme.colors.textMuted)
|
||||
Text(text = nativeString("ACTIVE AGENT"), style = ClawTheme.type.caption.copy(fontSize = 12.sp, lineHeight = 15.sp), color = ClawTheme.colors.textMuted)
|
||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(9.dp)) {
|
||||
OverviewAgentBadge(text = agentBadge, active = isConnected, avatarSource = agentAvatarSource)
|
||||
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) {
|
||||
@@ -656,19 +660,19 @@ private fun OverviewPrimaryPanel(
|
||||
}
|
||||
Text(text = overviewAgentActivityText(isConnected = isConnected, pendingRunCount = pendingRunCount, sessionCount = sessionCount, cronJobCount = cronJobCount, statusText = statusText), style = ClawTheme.type.caption.copy(fontSize = 13.5.sp, lineHeight = 17.sp), color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
ClawSecondaryButton(text = "View", onClick = onOpenAgent)
|
||||
ClawSecondaryButton(text = nativeString("View"), onClick = onOpenAgent)
|
||||
}
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
OverviewStateChip(label = "Runs", value = if (pendingRunCount > 0) "$pendingRunCount active" else "Idle", modifier = Modifier.weight(1f))
|
||||
OverviewStateChip(label = "Sessions", value = if (sessionCount == 0) "None" else "$sessionCount recent", modifier = Modifier.weight(1f))
|
||||
OverviewStateChip(label = "Cron", value = cronJobsSummary(cronJobCount), modifier = Modifier.weight(1f))
|
||||
OverviewStateChip(label = nativeString("Runs"), value = if (pendingRunCount > 0) nativeString("\$pendingRunCount active", pendingRunCount) else nativeString("Idle"), modifier = Modifier.weight(1f))
|
||||
OverviewStateChip(label = nativeString("Sessions"), value = if (sessionCount == 0) nativeString("None") else nativeString("\$sessionCount recent", sessionCount), modifier = Modifier.weight(1f))
|
||||
OverviewStateChip(label = nativeString("Cron"), value = cronJobsSummary(cronJobCount), modifier = Modifier.weight(1f))
|
||||
}
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
OverviewActionPill(text = "Chat", icon = Icons.Outlined.ChatBubbleOutline, emphasized = true, onClick = onOpenChat, modifier = Modifier.weight(1f))
|
||||
OverviewActionPill(text = "Talk", icon = Icons.Outlined.MicNone, emphasized = false, onClick = onOpenVoice, modifier = Modifier.weight(1f))
|
||||
OverviewActionPill(text = nativeString("Chat"), icon = Icons.Outlined.ChatBubbleOutline, emphasized = true, onClick = onOpenChat, modifier = Modifier.weight(1f))
|
||||
OverviewActionPill(text = nativeString("Talk"), icon = Icons.Outlined.MicNone, emphasized = false, onClick = onOpenVoice, modifier = Modifier.weight(1f))
|
||||
}
|
||||
if (!isConnected) {
|
||||
ClawSecondaryButton(text = "Reconnect gateway", icon = Icons.Default.Cloud, onClick = onOpenGateway, modifier = Modifier.fillMaxWidth())
|
||||
ClawSecondaryButton(text = nativeString("Reconnect gateway"), icon = Icons.Default.Cloud, onClick = onOpenGateway, modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -818,11 +822,11 @@ private fun OverviewMetricTile(
|
||||
Column(modifier = Modifier.padding(ClawTheme.spacing.xs), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Icon(imageVector = card.icon, contentDescription = null, modifier = Modifier.size(17.dp), tint = card.tint)
|
||||
Text(text = card.title.uppercase(), style = ClawTheme.type.caption.copy(fontSize = 10.5.sp, lineHeight = 13.sp), color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f))
|
||||
Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = "Open ${card.title}", modifier = Modifier.size(15.dp), tint = ClawTheme.colors.textMuted)
|
||||
Text(text = nativeString(card.title).uppercase(), style = ClawTheme.type.caption.copy(fontSize = 10.5.sp, lineHeight = 13.sp), color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f))
|
||||
Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = nativeString("Open \${card.title}", card.title), modifier = Modifier.size(15.dp), tint = ClawTheme.colors.textMuted)
|
||||
}
|
||||
Text(text = card.value, style = ClawTheme.type.title.copy(fontSize = 22.sp, lineHeight = 25.sp), color = ClawTheme.colors.text, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = card.subtitle, style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textSubtle, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = nativeString(card.value), style = ClawTheme.type.title.copy(fontSize = 22.sp, lineHeight = 25.sp), color = ClawTheme.colors.text, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = nativeString(card.subtitle), style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textSubtle, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
||||
card.progressFraction?.let { progress ->
|
||||
OverviewProgressBar(progress = progress, tint = card.tint)
|
||||
}
|
||||
@@ -887,10 +891,10 @@ private fun TalkEntryPanel(
|
||||
}
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text(text = "Talk", style = ClawTheme.type.caption.copy(fontSize = 12.sp, lineHeight = 15.sp), color = ClawTheme.colors.textMuted)
|
||||
Text(text = "Open Talk", style = ClawTheme.type.body, color = ClawTheme.colors.text, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = nativeString("Talk"), style = ClawTheme.type.caption.copy(fontSize = 12.sp, lineHeight = 15.sp), color = ClawTheme.colors.textMuted)
|
||||
Text(text = nativeString("Open Talk"), style = ClawTheme.type.body, color = ClawTheme.colors.text, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
ClawPlainIconButton(icon = Icons.Default.Tune, contentDescription = "Talk settings", onClick = onOpenVoiceSettings)
|
||||
ClawPlainIconButton(icon = Icons.Default.Tune, contentDescription = nativeString("Talk settings"), onClick = onOpenVoiceSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -898,7 +902,7 @@ private fun TalkEntryPanel(
|
||||
@Composable
|
||||
private fun RecentSessionsHeader(onOpenSessions: () -> Unit) {
|
||||
SectionLabel(
|
||||
title = "Recent Sessions",
|
||||
title = nativeString("Recent Sessions"),
|
||||
action = {
|
||||
Surface(
|
||||
onClick = onOpenSessions,
|
||||
@@ -908,7 +912,7 @@ private fun RecentSessionsHeader(onOpenSessions: () -> Unit) {
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
text = "View all",
|
||||
text = nativeString("View all"),
|
||||
style = ClawTheme.type.caption,
|
||||
color = ClawTheme.colors.textMuted,
|
||||
)
|
||||
@@ -1022,10 +1026,10 @@ internal fun overviewMetricCardSpecs(
|
||||
val nodeCount = nodesDevicesSummary.nodes.size
|
||||
return listOf(
|
||||
OverviewMetricCardSpec(
|
||||
title = "Gateway",
|
||||
title = nativeString("Gateway"),
|
||||
value =
|
||||
when {
|
||||
!isConnected -> "Offline"
|
||||
!isConnected -> nativeString("Offline")
|
||||
hasAttention -> "Online"
|
||||
else -> "Healthy"
|
||||
},
|
||||
@@ -1046,8 +1050,8 @@ internal fun overviewMetricCardSpecs(
|
||||
settingsRoute = SettingsRoute.Gateway,
|
||||
),
|
||||
OverviewMetricCardSpec(
|
||||
title = "Nodes",
|
||||
value = if (nodeCount == 0) "None" else "$onlineNodes/$nodeCount",
|
||||
title = nativeString("Nodes"),
|
||||
value = if (nodeCount == 0) nativeString("None") else nativeString("\$onlineNodes/\$nodeCount", onlineNodes, nodeCount),
|
||||
subtitle =
|
||||
if (nodesDevicesSummary.hasNodeCapabilityApprovalPending()) {
|
||||
"Review node access"
|
||||
@@ -1068,7 +1072,7 @@ internal fun overviewMetricCardSpecs(
|
||||
progressFraction = if (nodeCount > 0) onlineNodes.toFloat() / nodeCount.toFloat() else null,
|
||||
),
|
||||
OverviewMetricCardSpec(
|
||||
title = "Approvals",
|
||||
title = nativeString("Approvals"),
|
||||
value = pendingApprovals.toString(),
|
||||
subtitle = approvalsSummary(pendingApprovals),
|
||||
icon = Icons.Default.Security,
|
||||
@@ -1077,17 +1081,17 @@ internal fun overviewMetricCardSpecs(
|
||||
settingsRoute = SettingsRoute.Approvals,
|
||||
),
|
||||
OverviewMetricCardSpec(
|
||||
title = "Sessions",
|
||||
title = nativeString("Sessions"),
|
||||
value = sessionCount.toString(),
|
||||
subtitle = if (sessionCount == 0) "No recent sessions" else "Recent conversations",
|
||||
subtitle = if (sessionCount == 0) nativeString("No recent sessions") else nativeString("Recent conversations"),
|
||||
icon = Icons.Default.Groups,
|
||||
status = if (sessionCount > 0) ClawStatus.Success else ClawStatus.Neutral,
|
||||
tab = Tab.Sessions,
|
||||
),
|
||||
OverviewMetricCardSpec(
|
||||
title = "Files",
|
||||
value = if (isConnected) "Browse" else "Offline",
|
||||
subtitle = "Agent workspace files",
|
||||
title = nativeString("Files"),
|
||||
value = if (isConnected) nativeString("Browse") else nativeString("Offline"),
|
||||
subtitle = nativeString("Agent workspace files"),
|
||||
icon = Icons.Outlined.Folder,
|
||||
status = if (isConnected) ClawStatus.Success else ClawStatus.Neutral,
|
||||
tab = Tab.Files,
|
||||
@@ -1100,7 +1104,7 @@ internal fun overviewAgentName(
|
||||
defaultAgentId: String?,
|
||||
): String {
|
||||
val agent = overviewAgent(agents = agents, defaultAgentId = defaultAgentId)
|
||||
return agent?.name?.takeIf { it.isNotBlank() } ?: agent?.id?.takeIf { it.isNotBlank() } ?: "OpenClaw"
|
||||
return agent?.name?.takeIf { it.isNotBlank() } ?: agent?.id?.takeIf { it.isNotBlank() } ?: nativeString("OpenClaw")
|
||||
}
|
||||
|
||||
internal fun overviewAgentBadgeText(
|
||||
@@ -1114,7 +1118,7 @@ internal fun overviewAgentBadgeText(
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.let { return it }
|
||||
if (agent == null) return "OC"
|
||||
val source = agent.name?.takeIf { it.isNotBlank() } ?: agent.id.takeIf { it.isNotBlank() } ?: "OpenClaw"
|
||||
val source = agent.name?.takeIf { it.isNotBlank() } ?: agent.id.takeIf { it.isNotBlank() } ?: nativeString("OpenClaw")
|
||||
return agentInitials(source)
|
||||
}
|
||||
|
||||
@@ -1201,7 +1205,7 @@ internal fun sessionSourceLabel(
|
||||
} else {
|
||||
normalized
|
||||
}
|
||||
if (!scopedKey.contains(':') && !scopedKey.contains('#')) return "OpenClaw"
|
||||
if (!scopedKey.contains(':') && !scopedKey.contains('#')) return nativeString("OpenClaw")
|
||||
val source = scopedKey.substringBefore(':').substringBefore('#').lowercase()
|
||||
val channelLabel =
|
||||
channelsSummary.channels
|
||||
@@ -1210,7 +1214,7 @@ internal fun sessionSourceLabel(
|
||||
}?.label
|
||||
?.takeIf { it.isNotBlank() }
|
||||
if (channelLabel != null) return channelLabel
|
||||
return sessionSourceLabels[source] ?: "OpenClaw"
|
||||
return nativeString(sessionSourceLabels[source] ?: "OpenClaw")
|
||||
}
|
||||
|
||||
internal data class HomeAttentionRow(
|
||||
@@ -1265,7 +1269,7 @@ private fun HomeAttentionPanel(
|
||||
) {
|
||||
OverviewLayeredPanel(contentPadding = PaddingValues(horizontal = 14.dp, vertical = 8.dp)) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(text = "Needs attention", style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.warning)
|
||||
Text(text = nativeString("Needs attention"), style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.warning)
|
||||
rows.forEach { row ->
|
||||
ModuleListRow(
|
||||
row = ModuleRow(row.title, row.subtitle, row.icon, row.tab, row.settingsRoute),
|
||||
@@ -1293,7 +1297,7 @@ private fun SectionLabel(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(text = title.uppercase(), style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted)
|
||||
Text(text = nativeString(title).uppercase(), style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted)
|
||||
action?.invoke()
|
||||
}
|
||||
}
|
||||
@@ -1318,19 +1322,19 @@ private fun ModuleListRow(
|
||||
Icon(imageVector = row.icon, contentDescription = null, modifier = Modifier.size(20.dp), tint = ClawTheme.colors.text)
|
||||
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) {
|
||||
Text(
|
||||
text = row.title,
|
||||
text = nativeString(row.title),
|
||||
style = ClawTheme.type.body,
|
||||
color = ClawTheme.colors.text,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
row.subtitle?.let {
|
||||
Text(text = it, style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textSubtle, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = nativeString(it), style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textSubtle, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = "Open ${row.title}",
|
||||
contentDescription = nativeString("Open \${row.title}", row.title),
|
||||
modifier = Modifier.size(17.dp),
|
||||
tint = ClawTheme.colors.textMuted,
|
||||
)
|
||||
@@ -1433,12 +1437,12 @@ private fun RecentSessionRowContent(
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) {
|
||||
Text(text = title, style = ClawTheme.type.body, color = ClawTheme.colors.text, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = source, style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textSubtle, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = nativeString(source), style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textSubtle, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
Text(text = metadata, style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted)
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = "Open session",
|
||||
contentDescription = nativeString("Open session"),
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = ClawTheme.colors.textMuted,
|
||||
)
|
||||
@@ -1556,13 +1560,13 @@ private fun SettingsShellScreen(
|
||||
) {
|
||||
ClawPlainIconButton(
|
||||
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
contentDescription = nativeString("Back"),
|
||||
onClick = onBack,
|
||||
)
|
||||
Text(text = "Settings", style = ClawTheme.type.display.copy(fontSize = 24.sp, lineHeight = 28.sp), color = ClawTheme.colors.text, modifier = Modifier.weight(1f))
|
||||
Text(text = nativeString("Settings"), style = ClawTheme.type.display.copy(fontSize = 24.sp, lineHeight = 28.sp), color = ClawTheme.colors.text, modifier = Modifier.weight(1f))
|
||||
ClawPlainIconButton(
|
||||
icon = Icons.Default.Search,
|
||||
contentDescription = "Search settings",
|
||||
contentDescription = nativeString("Search settings"),
|
||||
onClick = onOpenCommand,
|
||||
)
|
||||
}
|
||||
@@ -1614,10 +1618,10 @@ private fun SettingsShellScreen(
|
||||
),
|
||||
SettingsRow("Dreaming", dreamingSummaryText(dreamingSummary), Icons.Default.Storage, status = dreamingStatus(dreamingSummary), route = SettingsRoute.Dreaming),
|
||||
SettingsRow("Terminal", "Shell in the agent workspace", Icons.Outlined.Terminal, status = isConnected, route = SettingsRoute.Terminal),
|
||||
SettingsRow("Voice", if (speakerEnabled) "Speaker on" else "Speaker muted", Icons.Default.Mic, route = SettingsRoute.Voice),
|
||||
SettingsRow("Voice", if (speakerEnabled) nativeString("Speaker on") else nativeString("Speaker muted"), Icons.Default.Mic, route = SettingsRoute.Voice),
|
||||
SettingsRow("Canvas", "Screen surface", Icons.AutoMirrored.Filled.ScreenShare, status = isConnected, route = SettingsRoute.Canvas),
|
||||
SettingsRow("Notifications", if (notificationForwardingEnabled) "Smart delivery" else "Off", Icons.Default.Notifications, route = SettingsRoute.Notifications),
|
||||
SettingsRow("Phone Capabilities", if (cameraEnabled) "Camera enabled" else "Locked", Icons.Default.Lock, status = !cameraEnabled, route = SettingsRoute.PhoneCapabilities),
|
||||
SettingsRow("Notifications", if (notificationForwardingEnabled) nativeString("Smart delivery") else nativeString("Off"), Icons.Default.Notifications, route = SettingsRoute.Notifications),
|
||||
SettingsRow("Phone Capabilities", if (cameraEnabled) nativeString("Camera enabled") else nativeString("Locked"), Icons.Default.Lock, status = !cameraEnabled, route = SettingsRoute.PhoneCapabilities),
|
||||
SettingsRow(
|
||||
"Appearance",
|
||||
"${appearanceThemeSummary(appearanceThemeMode)} · ${appLanguage.displayName}",
|
||||
@@ -1664,10 +1668,10 @@ private fun SettingsShellScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(3.dp),
|
||||
) {
|
||||
Text(text = "OpenClaw ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})", style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted)
|
||||
Text(text = nativeString("OpenClaw \${BuildConfig.VERSION_NAME} (\${BuildConfig.VERSION_CODE})", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE), style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(5.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = if (isConnected) "All systems operational" else "Gateway not connected",
|
||||
text = if (isConnected) nativeString("All systems operational") else nativeString("Gateway not connected"),
|
||||
style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp),
|
||||
color = ClawTheme.colors.textSubtle,
|
||||
)
|
||||
@@ -1682,7 +1686,7 @@ private fun SettingsShellScreen(
|
||||
private fun approvalsSummary(count: Int): String =
|
||||
when (count) {
|
||||
0 -> "No pending approvals"
|
||||
1 -> "1 pending"
|
||||
1 -> nativeString("1 pending")
|
||||
else -> "$count pending"
|
||||
}
|
||||
|
||||
@@ -1699,15 +1703,19 @@ private fun cronJobsSummary(count: Int): String =
|
||||
/** Summarizes provider usage buckets without exposing detailed billing data. */
|
||||
private fun usageSummaryText(count: Int): String =
|
||||
when (count) {
|
||||
0 -> "No provider usage"
|
||||
1 -> "1 provider"
|
||||
else -> "$count providers"
|
||||
0 -> nativeString("No provider usage")
|
||||
1 -> nativeString("1 provider")
|
||||
else -> nativeString("\$count providers", count)
|
||||
}
|
||||
|
||||
/** Reports how many gateway skills are enabled, eligible, and dependency-complete. */
|
||||
private fun skillsSummaryText(skills: List<GatewaySkillSummary>): String {
|
||||
val ready = skills.count { !it.disabled && it.eligible && it.missingCount == 0 }
|
||||
return if (skills.isEmpty()) "No skills" else "$ready/${skills.size} ready"
|
||||
return if (skills.isEmpty()) {
|
||||
nativeString("No skills")
|
||||
} else {
|
||||
nativeString("\$ready/\${skills.size} ready", ready, skills.size)
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts gateway skill health into a tri-state settings status dot. */
|
||||
@@ -1721,13 +1729,13 @@ private fun skillsStatus(skills: List<GatewaySkillSummary>): Boolean? =
|
||||
/** Mirrors the Skill Workshop review queue in one compact Settings row. */
|
||||
internal fun skillWorkshopSummaryText(summary: GatewaySkillWorkshopSummary): String {
|
||||
val pending = summary.proposals.count { it.status == "pending" }
|
||||
if (pending > 0) return if (pending == 1) "1 pending" else "$pending pending"
|
||||
if (pending > 0) return if (pending == 1) nativeString("1 pending") else nativeString("\$pending pending", pending)
|
||||
val held = summary.proposals.count { it.status == "quarantined" || it.status == "stale" }
|
||||
val applied = summary.proposals.count { it.status == "applied" }
|
||||
return when {
|
||||
summary.proposals.isEmpty() -> "No proposals"
|
||||
held > 0 -> if (held == 1) "1 held" else "$held held"
|
||||
applied > 0 -> if (applied == 1) "1 applied" else "$applied applied"
|
||||
held > 0 -> if (held == 1) nativeString("1 held") else nativeString("\$held held", held)
|
||||
applied > 0 -> if (applied == 1) nativeString("1 applied") else nativeString("\$applied applied", applied)
|
||||
else -> "${summary.proposals.size} proposals"
|
||||
}
|
||||
}
|
||||
@@ -1792,9 +1800,9 @@ private fun channelsStatus(summary: GatewayChannelsSummary): Boolean? =
|
||||
/** Summarizes dreaming memory health before enabled/off state. */
|
||||
private fun dreamingSummaryText(summary: GatewayDreamingSummary): String =
|
||||
when {
|
||||
!summary.storeHealthy || !summary.phaseSignalHealthy -> "Needs attention"
|
||||
!summary.storeHealthy || !summary.phaseSignalHealthy -> nativeString("Needs attention")
|
||||
summary.enabled -> "${summary.shortTermCount} waiting"
|
||||
else -> "Off"
|
||||
else -> nativeString("Off")
|
||||
}
|
||||
|
||||
/** Maps dreaming store/phase health and enabled state to a settings status dot. */
|
||||
@@ -1870,7 +1878,7 @@ internal fun settingsSectionTitleForRoute(route: SettingsRoute): String =
|
||||
@Composable
|
||||
private fun SettingsSectionTitle(title: String) {
|
||||
Text(
|
||||
text = title.uppercase(),
|
||||
text = nativeString(title).uppercase(),
|
||||
style = ClawTheme.type.caption.copy(fontSize = 12.sp, lineHeight = 16.sp),
|
||||
color = ClawTheme.colors.textMuted,
|
||||
)
|
||||
@@ -1908,11 +1916,11 @@ private fun ProfilePanel(
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text(text = displayName, style = ClawTheme.type.section, color = ClawTheme.colors.text, maxLines = 1)
|
||||
Text(text = "OpenClaw mobile", style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, maxLines = 1)
|
||||
Text(text = nativeString("OpenClaw mobile"), style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, maxLines = 1)
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = "Open profile",
|
||||
contentDescription = nativeString("Open profile"),
|
||||
modifier = Modifier.size(15.dp),
|
||||
tint = ClawTheme.colors.text,
|
||||
)
|
||||
@@ -1967,10 +1975,10 @@ private fun SettingsListRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
Icon(imageVector = row.icon, contentDescription = null, modifier = Modifier.size(20.dp), tint = ClawTheme.colors.text)
|
||||
Text(text = row.title, style = ClawTheme.type.body, color = ClawTheme.colors.text, modifier = Modifier.weight(1f), maxLines = 1)
|
||||
Text(text = nativeString(row.title), style = ClawTheme.type.body, color = ClawTheme.colors.text, modifier = Modifier.weight(1f), maxLines = 1)
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(5.dp)) {
|
||||
if (row.value.isNotBlank()) {
|
||||
Text(text = row.value, style = ClawTheme.type.caption.copy(fontSize = 13.sp, lineHeight = 17.sp), color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(text = nativeString(row.value), style = ClawTheme.type.caption.copy(fontSize = 13.sp, lineHeight = 17.sp), color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
row.status?.let { active ->
|
||||
Box(modifier = Modifier.size(4.5.dp).clip(CircleShape).background(if (active) ClawTheme.colors.success else ClawTheme.colors.textSubtle))
|
||||
@@ -1978,7 +1986,12 @@ private fun SettingsListRow(
|
||||
if (showDisclosure) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = if (row.route != null) "Open ${row.title}" else row.title,
|
||||
contentDescription =
|
||||
if (row.route != null) {
|
||||
nativeString("Open \${row.title}", row.title)
|
||||
} else {
|
||||
nativeString(row.title)
|
||||
},
|
||||
modifier = Modifier.size(17.dp),
|
||||
tint = ClawTheme.colors.text,
|
||||
)
|
||||
@@ -1990,30 +2003,30 @@ private fun SettingsListRow(
|
||||
private fun relativeSessionTime(updatedAtMs: Long): String {
|
||||
val deltaMs = (System.currentTimeMillis() - updatedAtMs).coerceAtLeast(0L)
|
||||
val minutes = deltaMs / 60_000L
|
||||
if (minutes < 1) return "now"
|
||||
if (minutes < 1) return nativeString("now")
|
||||
if (minutes < 60) return "${minutes}m"
|
||||
val hours = minutes / 60
|
||||
if (hours < 24) return "${hours}h"
|
||||
return "${hours / 24}d"
|
||||
}
|
||||
|
||||
private fun displaySessionTitle(displayName: String?): String = displayName?.takeIf { it.isNotBlank() } ?: "Main session"
|
||||
private fun displaySessionTitle(displayName: String?): String = displayName?.takeIf { it.isNotBlank() } ?: nativeString("Main session")
|
||||
|
||||
internal fun gatewaySummary(
|
||||
statusText: String,
|
||||
isConnected: Boolean,
|
||||
gatewayConnectionProblem: GatewayConnectionProblem? = null,
|
||||
): String {
|
||||
if (isConnected) return "Online and ready"
|
||||
if (isConnected) return nativeString("Online and ready")
|
||||
val status = statusText.trim().lowercase()
|
||||
return when {
|
||||
status.contains("connecting") || status.contains("reconnecting") -> "Connecting..."
|
||||
status.contains("pairing") -> "Waiting for pairing"
|
||||
status.contains("auth") || status.contains("device identity") -> gatewayAuthRecoveryLabel(gatewayConnectionProblem) ?: "Authentication needed"
|
||||
status.contains("fingerprint verification timed out") -> "TLS timed out"
|
||||
status.contains("no tls endpoint") -> "No TLS endpoint"
|
||||
status.contains("certificate") || status.contains("tls") -> "Certificate review needed"
|
||||
else -> "Not connected"
|
||||
status.contains("connecting") || status.contains("reconnecting") -> nativeString("Connecting...")
|
||||
status.contains("pairing") -> nativeString("Waiting for pairing")
|
||||
status.contains("auth") || status.contains("device identity") -> gatewayAuthRecoveryLabel(gatewayConnectionProblem) ?: nativeString("Authentication needed")
|
||||
status.contains("fingerprint verification timed out") -> nativeString("TLS timed out")
|
||||
status.contains("no tls endpoint") -> nativeString("No TLS endpoint")
|
||||
status.contains("certificate") || status.contains("tls") -> nativeString("Certificate review needed")
|
||||
else -> nativeString("Not connected")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user