Skip to content

feat(android): redesign chat settings UI#44894

Merged
obviyus merged 6 commits intomainfrom
android/redesign-chat-settings
Mar 13, 2026
Merged

feat(android): redesign chat settings UI#44894
obviyus merged 6 commits intomainfrom
android/redesign-chat-settings

Conversation

@obviyus
Copy link
Copy Markdown
Contributor

@obviyus obviyus commented Mar 13, 2026

Summary

  • Problem: Android chat settings felt fragmented, and the chat header/composer used more space than needed.
  • Why it matters: scanning controls and status on mobile was slower than it should be.
  • What changed: grouped settings into card sections, refreshed the Connect and Voice tabs, and compacted the chat composer/session header.
  • What did NOT change (scope boundary): no backend, permission-model, or gateway behavior changes.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #

User-visible / Behavior Changes

  • Settings sheet uses grouped device/media sections.
  • Connect and Voice tabs use cleaner status-card layouts.
  • Chat composer and session header are more compact.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Android Gradle debug compile
  • Model/provider: N/A
  • Integration/channel (if any): Android app UI
  • Relevant config (redacted): N/A

Steps

  1. cd apps/android
  2. ./gradlew app:compileDebugKotlin

Expected

  • Kotlin compile succeeds.

Actual

  • Passed.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: ./gradlew app:compileDebugKotlin
  • Edge cases checked: none beyond compile validation.
  • What you did not verify: on-device UI walkthrough.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert this PR.
  • Files/config to restore: apps/android/app/src/main/java/ai/openclaw/app/ui/*
  • Known bad symptoms reviewers should watch for: missing controls or layout regressions in Android chat settings.

Risks and Mitigations

  • Risk: visual regressions on smaller Android screens.
    • Mitigation: review on-device before merge; Kotlin compile already passes.

@openclaw-barnacle openclaw-barnacle bot added app: android App: android size: XL maintainer Maintainer-authored PR labels Mar 13, 2026
obviyus added 6 commits March 13, 2026 14:29
Merge endpoint and status into a single grouped card with icons.
Split connect/disconnect into context-aware buttons.
Add text label under speaker toggle, balance layout with matching
spacer column, and wrap status text in a colored pill.
Remove MESSAGE label and divider, let text field auto-size instead
of fixed 92dp, and merge Detail/Attach into the bottom action row.
Rename role labels to You/OpenClaw/System, update streaming label to
OpenClaw · Live, and remove the redundant SESSION row + Connected pill
since the top bar and chip row already convey both.
Remove header bloat, merge Node info into a single Device card,
group permissions into Media/Notifications/Data Access cards with
internal dividers, and combine Screen+Debug into Preferences.
Sections reduced from 9 to 6.
@obviyus obviyus force-pushed the android/redesign-chat-settings branch from 2bf6ba6 to 2f62eba Compare March 13, 2026 09:01
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR redesigns several Android UI screens — ConnectTabScreen, SettingsSheet, VoiceTabScreen, ChatComposer, ChatMessageViews, and ChatSheetContent — to use grouped card sections, cleaner status layouts, and a more compact chat composer. No backend, permission-model, or gateway logic was changed.

Key findings from the review:

  • ChatSheetContent.kt is missing windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)) on its outer Column, while both VoiceTabScreen and SettingsSheet include it. The imePadding() is also placed only on the inner composer Row rather than the outer container. On gesture-navigation devices this can cause the navigation bar to overlap the chat composer/Send button.
  • SettingsSheet.kt has an unused Icons import and a dead settingsDangerButtonColors() helper function left over from the refactor.
  • ChatMessageViews.kt contains a redundant !! non-null assertion inside an already null-checked branch in ChatBase64Image.
  • All other files (ConnectTabScreen, VoiceTabScreen, ChatComposer) look clean — logic is unchanged and the UI restructuring is consistent.

Confidence Score: 3/5

  • Safe to merge after addressing the missing bottom-inset padding in ChatSheetContent; all other issues are cosmetic.
  • The redesign is purely visual and does not touch any backend, permissions, or data-flow logic. However, the missing windowInsetsPadding in ChatSheetContent is a real UX regression on gesture-navigation devices where the composer row can sit behind the navigation bar. The author also acknowledged no on-device UI walkthrough was performed, so this inset issue was not caught. Fixing it is straightforward, which keeps the score from going lower.
  • apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatSheetContent.kt (missing inset padding), apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt (unused import + dead code)

Comments Outside Diff (4)

  1. apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatSheetContent.kt, line 94-100 (link)

    Missing bottom-inset padding for navigation bar

    Both VoiceTabScreen and SettingsSheet apply .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)) to their outer containers. ChatSheetContent only applies imePadding() to the inner Row wrapping ChatComposer, which guards against the software keyboard but does nothing for the gesture navigation bar. On gesture-navigation devices this means the composer row can sit behind the navigation bar, making the Send button partially or fully inaccessible.

    Consider applying inset handling at the outer Column level, consistent with the other screens:

    (The imePadding() on the inner Row at line 121 can then be removed.)

  2. apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt, line 754-761 (link)

    Dead code: settingsDangerButtonColors is never called

    settingsDangerButtonColors() is defined but has no call site anywhere in this file. If danger-styled buttons are not needed in the new layout, this function should be removed to keep the file clean.

    (Delete lines 754–761.)

  3. apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt, line 37 (link)

    Unused import

    androidx.compose.material.icons.Icons is imported but never referenced in the file — no Icons.Default.* or Icon(...) composable is used after the redesign. This will produce an IDE warning and should be removed.

    (Delete line 37.)

  4. apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt, line 246 (link)

    Redundant non-null assertion

    image is a local val that was already null-checked two lines above (if (image != null)). Kotlin's smart cast makes the !! operator unnecessary here — the compiler already knows image is non-null inside this branch. The assertion should be removed to avoid misleading readers into thinking a nullable access is possible.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatSheetContent.kt
Line: 94-100

Comment:
**Missing bottom-inset padding for navigation bar**

Both `VoiceTabScreen` and `SettingsSheet` apply `.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom))` to their outer containers. `ChatSheetContent` only applies `imePadding()` to the inner `Row` wrapping `ChatComposer`, which guards against the software keyboard but does nothing for the gesture navigation bar. On gesture-navigation devices this means the composer row can sit behind the navigation bar, making the Send button partially or fully inaccessible.

Consider applying inset handling at the outer `Column` level, consistent with the other screens:

```suggestion
  Column(
    modifier =
      Modifier
        .fillMaxSize()
        .padding(horizontal = 20.dp, vertical = 12.dp)
        .imePadding()
        .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)),
    verticalArrangement = Arrangement.spacedBy(8.dp),
  ) {
```

(The `imePadding()` on the inner `Row` at line 121 can then be removed.)

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt
Line: 754-761

Comment:
**Dead code: `settingsDangerButtonColors` is never called**

`settingsDangerButtonColors()` is defined but has no call site anywhere in this file. If danger-styled buttons are not needed in the new layout, this function should be removed to keep the file clean.

```suggestion
```
(Delete lines 754–761.)

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt
Line: 37

Comment:
**Unused import**

`androidx.compose.material.icons.Icons` is imported but never referenced in the file — no `Icons.Default.*` or `Icon(...)` composable is used after the redesign. This will produce an IDE warning and should be removed.

```suggestion
```
(Delete line 37.)

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt
Line: 246

Comment:
**Redundant non-null assertion**

`image` is a local `val` that was already null-checked two lines above (`if (image != null)`). Kotlin's smart cast makes the `!!` operator unnecessary here — the compiler already knows `image` is non-null inside this branch. The assertion should be removed to avoid misleading readers into thinking a nullable access is possible.

```suggestion
            bitmap = image,
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 2f62eba

@obviyus obviyus merged commit 7638052 into main Mar 13, 2026
23 checks passed
@obviyus obviyus deleted the android/redesign-chat-settings branch March 13, 2026 09:01
@obviyus
Copy link
Copy Markdown
Contributor Author

obviyus commented Mar 13, 2026

Landed on main.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2bf6ba6cb2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Box(modifier = Modifier.weight(1f)) {
Box {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep a flexible slot in composer action row

This change removes the only flexible-width element in the composer toolbar by switching the thinking selector from Modifier.weight(1f) to a plain Box, while the same row now also contains fixed-size Attach/Refresh/Abort buttons plus a non-weighted Send button. In the chat sheet (which already has horizontal padding), this overflows on common ~360dp phone widths, causing trailing actions (typically Send) to be clipped/off-screen and difficult or impossible to tap; keep a weighted child (or split/wrap the controls) so the row can contract on narrow devices.

Useful? React with 👍 / 👎.

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 13, 2026

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Fragile connection gating via substring match on remotely-influenced status text (client-side DoS)
2 🔵 Low Chat message provenance spoofing: unknown roles rendered as “OpenClaw” (assistant)

1. 🟡 Fragile connection gating via substring match on remotely-influenced status text (client-side DoS)

Property Value
Severity Medium
CWE CWE-400
Location apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt:219-224

Description

The Connect button behavior is gated by a substring match on statusText:

  • statusText is a UI-facing string (MainViewModel.statusText -> NodeRuntime.statusText) that is not a stable state indicator.
  • NodeRuntime can set the underlying operatorStatusText / nodeStatusText from GatewaySession disconnect callbacks.
  • GatewaySession composes disconnect messages using server-provided WebSocket close reasons (and exception messages), which can be influenced by a malicious/compromised gateway.
  • If the resulting statusText contains the magic substring "operator offline", the UI refuses to perform a normal connect with the user-provided configuration and instead calls refreshGatewayConnection(). This can trap users in a reconnect loop to a previously-cached endpoint and prevent switching to a new endpoint/credentials without killing the app.

Vulnerable code:

if (statusText.contains("operator offline", ignoreCase = true)) {
  validationText = null
  viewModel.refreshGatewayConnection()
  return@​Button
}

Provenance (remote influence):

  • ConnectTabScreen.statusText comes from NodeRuntime.statusText.
  • NodeRuntime.operatorStatusText is set from GatewaySession.onDisconnected(message).
  • GatewaySession passes disconnect messages like "Gateway closed: $reason" where $reason is the WebSocket close reason sent by the server.

This creates a practical client-side denial-of-service / lock-in vector when connecting to an attacker-controlled gateway (or when a gateway is compromised): the gateway can repeatedly close with a reason containing operator offline, causing the app to keep taking the refresh path and preventing a clean connect flow with new settings.

Recommendation

Do not use user-facing status strings for control flow.

  • Introduce an explicit connection state model (sealed class / enum + structured error codes) in the ViewModel/runtime, e.g. GatewayConnectionState with fields like operatorConnected, nodeConnected, lastDisconnectCode.
  • Gate the refresh/reconnect path on that structured state rather than statusText.contains(...).

Example (sketch):

enum class GatewayDisconnectCode { OPERATOR_OFFLINE, AUTH_FAILED, NETWORK_ERROR, UNKNOWN }

data class GatewayConnectionState(
  val operatorConnected: Boolean,
  val nodeConnected: Boolean,
  val lastDisconnectCode: GatewayDisconnectCode? = null,
)

​// UI
val state by viewModel.gatewayConnectionState.collectAsState()
Button(onClick = {
  if (!state.operatorConnected && state.nodeConnected && state.lastDisconnectCode == GatewayDisconnectCode.OPERATOR_OFFLINE) {
    viewModel.refreshGatewayConnection()
  } else {
    viewModel.connectManual()
  }
}) { /* ... */ }

Additionally, ensure disconnect reasons from the network are mapped to fixed codes (and not used directly as logic inputs).


2. 🔵 Chat message provenance spoofing: unknown roles rendered as “OpenClaw” (assistant)

Property Value
Severity Low
CWE CWE-345
Location apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt:225-230

Description

The chat UI derives the displayed sender label from ChatMessage.role and maps any role other than user and system to “OpenClaw”.

  • ChatMessage.role is populated from gateway-controlled JSON (chat.history) without a whitelist/enum (see ChatController.parseHistory: role is taken directly from obj["role"]).
  • In the UI, ChatMessageBubble() normalizes the role string and calls roleLabel(role).
  • roleLabel() now falls back to “OpenClaw” for all other roles, so messages with roles like tool, function, developer, or any unexpected value will be misattributed as the assistant/brand.

Impact (security UX / anti-spoofing):

  • A compromised/malicious gateway (or unexpected upstream role) can cause tool outputs or non-assistant messages to appear as if they were authored by “OpenClaw”, enabling phishing/social engineering and hiding provenance.

Vulnerable code:

private fun roleLabel(role: String): String {
  return when (role) {
    "user" -> "You"
    "system" -> "System"
    else -> "OpenClaw"
  }
}

Recommendation

Use an explicit allowlist of roles and do not treat unknown roles as the assistant.

  • Add explicit handling for roles you expect (at minimum: assistant, tool), and a safe fallback such as "Unknown" (optionally include the raw role for debugging).
  • Consider using a sealed class/enum for roles at the model/parsing layer to prevent arbitrary strings reaching the renderer.
  • Give tool/function roles distinct styling (label + color) so users can distinguish tool output from assistant text.

Example fix:

private fun roleLabel(role: String): String = when (role) {
  "user" -> "You"
  "assistant" -> "OpenClaw"
  "system" -> "System"
  "tool" -> "Tool"
  "developer" -> "Developer"
  else -> "Unknown" // or "Unknown (${role.take(24)})"
}

And update bubbleStyle() similarly to style tool separately from assistant.


Analyzed PR: #44894 at commit 2f62eba

Last updated on: 2026-03-13T09:19:05Z

hougangdev pushed a commit to hougangdev/clawdbot that referenced this pull request Mar 14, 2026
ecochran76 pushed a commit to ecochran76/openclaw that referenced this pull request Mar 14, 2026
Interstellar-code pushed a commit to Interstellar-code/operator1 that referenced this pull request Mar 16, 2026
@obviyus obviyus self-assigned this Mar 23, 2026
sbezludny pushed a commit to sbezludny/openclaw that referenced this pull request Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: android App: android maintainer Maintainer-authored PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant