diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml index facdbf301b4..c9814c00b4f 100644 --- a/apps/android/app/src/main/AndroidManifest.xml +++ b/apps/android/app/src/main/AndroidManifest.xml @@ -50,6 +50,7 @@ diff --git a/apps/android/app/src/main/java/ai/openclaw/android/MainActivity.kt b/apps/android/app/src/main/java/ai/openclaw/android/MainActivity.kt index cafe0958f86..aa85ab7efbc 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/MainActivity.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/MainActivity.kt @@ -9,9 +9,6 @@ import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.material3.Surface import androidx.compose.ui.Modifier -import androidx.core.view.WindowCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle @@ -28,7 +25,6 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) val isDebuggable = (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0 WebView.setWebContentsDebuggingEnabled(isDebuggable) - applyImmersiveMode() NodeForegroundService.start(this) permissionRequester = PermissionRequester(this) screenCaptureRequester = ScreenCaptureRequester(this) @@ -59,18 +55,6 @@ class MainActivity : ComponentActivity() { } } - override fun onResume() { - super.onResume() - applyImmersiveMode() - } - - override fun onWindowFocusChanged(hasFocus: Boolean) { - super.onWindowFocusChanged(hasFocus) - if (hasFocus) { - applyImmersiveMode() - } - } - override fun onStart() { super.onStart() viewModel.setForeground(true) @@ -80,12 +64,4 @@ class MainActivity : ComponentActivity() { viewModel.setForeground(false) super.onStop() } - - private fun applyImmersiveMode() { - WindowCompat.setDecorFitsSystemWindows(window, false) - val controller = WindowInsetsControllerCompat(window, window.decorView) - controller.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - controller.hide(WindowInsetsCompat.Type.systemBars()) - } } diff --git a/apps/android/app/src/main/java/ai/openclaw/android/ui/PostOnboardingTabs.kt b/apps/android/app/src/main/java/ai/openclaw/android/ui/PostOnboardingTabs.kt index b68c06ff2ff..b79b8cb4d80 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/ui/PostOnboardingTabs.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/ui/PostOnboardingTabs.kt @@ -5,14 +5,13 @@ import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.isImeVisible +import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.offset @@ -41,6 +40,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import ai.openclaw.android.MainViewModel @@ -65,10 +65,8 @@ private enum class StatusVisual { } @Composable -@OptIn(ExperimentalLayoutApi::class) fun PostOnboardingTabs(viewModel: MainViewModel, modifier: Modifier = Modifier) { var activeTab by rememberSaveable { mutableStateOf(HomeTab.Connect) } - val imeVisible = WindowInsets.isImeVisible val statusText by viewModel.statusText.collectAsState() val isConnected by viewModel.isConnected.collectAsState() @@ -96,19 +94,29 @@ fun PostOnboardingTabs(viewModel: MainViewModel, modifier: Modifier = Modifier) ) }, bottomBar = { - if (!imeVisible) { - BottomTabBar( - activeTab = activeTab, - onSelect = { activeTab = it }, - ) - } + BottomTabBar( + activeTab = activeTab, + onSelect = { activeTab = it }, + ) }, ) { innerPadding -> + val density = LocalDensity.current + val imeVisible = WindowInsets.ime.getBottom(density) > 0 + val contentBottomPadding = + if (activeTab == HomeTab.Chat && imeVisible) { + 0.dp + } else { + innerPadding.calculateBottomPadding() + } + Box( modifier = Modifier .fillMaxSize() - .padding(innerPadding) + .padding( + top = innerPadding.calculateTopPadding(), + bottom = contentBottomPadding, + ) .background(mobileBackgroundGradient), ) { when (activeTab) { diff --git a/apps/android/app/src/main/java/ai/openclaw/android/ui/chat/ChatComposer.kt b/apps/android/app/src/main/java/ai/openclaw/android/ui/chat/ChatComposer.kt index 7f71995906b..22099500ebf 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/ui/chat/ChatComposer.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/ui/chat/ChatComposer.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -161,6 +162,7 @@ fun ChatComposer( label = "Refresh", icon = Icons.Default.Refresh, enabled = true, + compact = true, onClick = onRefresh, ) @@ -168,6 +170,7 @@ fun ChatComposer( label = "Abort", icon = Icons.Default.Stop, enabled = pendingRunCount > 0, + compact = true, onClick = onAbort, ) } @@ -196,7 +199,12 @@ fun ChatComposer( Icon(Icons.AutoMirrored.Filled.Send, contentDescription = null, modifier = Modifier.size(16.dp)) } Spacer(modifier = Modifier.width(8.dp)) - Text("Send", style = mobileHeadline.copy(fontWeight = FontWeight.Bold)) + Text( + text = "Send", + style = mobileHeadline.copy(fontWeight = FontWeight.Bold), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) } } } @@ -207,12 +215,13 @@ private fun SecondaryActionButton( label: String, icon: androidx.compose.ui.graphics.vector.ImageVector, enabled: Boolean, + compact: Boolean = false, onClick: () -> Unit, ) { Button( onClick = onClick, enabled = enabled, - modifier = Modifier.height(44.dp), + modifier = if (compact) Modifier.size(44.dp) else Modifier.height(44.dp), shape = RoundedCornerShape(14.dp), colors = ButtonDefaults.buttonColors( @@ -222,15 +231,17 @@ private fun SecondaryActionButton( disabledContentColor = mobileTextTertiary, ), border = BorderStroke(1.dp, mobileBorderStrong), - contentPadding = ButtonDefaults.ContentPadding, + contentPadding = if (compact) PaddingValues(0.dp) else ButtonDefaults.ContentPadding, ) { Icon(icon, contentDescription = label, modifier = Modifier.size(14.dp)) - Spacer(modifier = Modifier.width(5.dp)) - Text( - text = label, - style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), - color = if (enabled) mobileTextSecondary else mobileTextTertiary, - ) + if (!compact) { + Spacer(modifier = Modifier.width(5.dp)) + Text( + text = label, + style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), + color = if (enabled) mobileTextSecondary else mobileTextTertiary, + ) + } } } diff --git a/apps/android/app/src/main/java/ai/openclaw/android/ui/chat/ChatSheetContent.kt b/apps/android/app/src/main/java/ai/openclaw/android/ui/chat/ChatSheetContent.kt index d1c2743ef04..12e13ab365a 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/ui/chat/ChatSheetContent.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/ui/chat/ChatSheetContent.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -122,33 +123,35 @@ fun ChatSheetContent(viewModel: MainViewModel) { modifier = Modifier.weight(1f, fill = true), ) - ChatComposer( - healthOk = healthOk, - thinkingLevel = thinkingLevel, - pendingRunCount = pendingRunCount, - attachments = attachments, - onPickImages = { pickImages.launch("image/*") }, - onRemoveAttachment = { id -> attachments.removeAll { it.id == id } }, - onSetThinkingLevel = { level -> viewModel.setChatThinkingLevel(level) }, - onRefresh = { - viewModel.refreshChat() - viewModel.refreshChatSessions(limit = 200) - }, - onAbort = { viewModel.abortChat() }, - onSend = { text -> - val outgoing = - attachments.map { att -> - OutgoingAttachment( - type = "image", - mimeType = att.mimeType, - fileName = att.fileName, - base64 = att.base64, - ) - } - viewModel.sendChat(message = text, thinking = thinkingLevel, attachments = outgoing) - attachments.clear() - }, - ) + Row(modifier = Modifier.fillMaxWidth().imePadding()) { + ChatComposer( + healthOk = healthOk, + thinkingLevel = thinkingLevel, + pendingRunCount = pendingRunCount, + attachments = attachments, + onPickImages = { pickImages.launch("image/*") }, + onRemoveAttachment = { id -> attachments.removeAll { it.id == id } }, + onSetThinkingLevel = { level -> viewModel.setChatThinkingLevel(level) }, + onRefresh = { + viewModel.refreshChat() + viewModel.refreshChatSessions(limit = 200) + }, + onAbort = { viewModel.abortChat() }, + onSend = { text -> + val outgoing = + attachments.map { att -> + OutgoingAttachment( + type = "image", + mimeType = att.mimeType, + fileName = att.fileName, + base64 = att.base64, + ) + } + viewModel.sendChat(message = text, thinking = thinkingLevel, attachments = outgoing) + attachments.clear() + }, + ) + } } }