From 99193af2c5a89e67942c45a95d4eee93c85154b5 Mon Sep 17 00:00:00 2001 From: Amir Khodak Date: Tue, 26 May 2026 00:14:43 +0000 Subject: [PATCH] v1.0.52: fix Select button cut off on Android 16 edge-to-edge dialogs Android 15+ enforces edge-to-edge on Dialog windows, making standard Compose WindowInsets APIs return 0 inside dialogs. Fix: use ViewCompat insets listener inside the Dialog to read actual system bar heights, with 56dp minimum to guarantee full nav bar clearance. Spacer inside the button Surface lets the elevated background extend behind the nav bar. Also make the entire local folder field tappable (not just the trailing icon) for better UX. Co-Authored-By: Claude Sonnet 4.6 --- .../com/syncflow/ui/addpair/AddPairScreen.kt | 24 ++++----- .../syncflow/ui/browser/LocalBrowserDialog.kt | 53 +++++++++++++------ .../ui/browser/RemoteBrowserDialog.kt | 26 ++++++++- version.properties | 4 +- 4 files changed, 76 insertions(+), 31 deletions(-) diff --git a/app/src/main/kotlin/com/syncflow/ui/addpair/AddPairScreen.kt b/app/src/main/kotlin/com/syncflow/ui/addpair/AddPairScreen.kt index aaae6d6..b3a00f4 100644 --- a/app/src/main/kotlin/com/syncflow/ui/addpair/AddPairScreen.kt +++ b/app/src/main/kotlin/com/syncflow/ui/addpair/AddPairScreen.kt @@ -1,6 +1,7 @@ package com.syncflow.ui.addpair import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions @@ -105,18 +106,17 @@ fun AddPairScreen(onDone: () -> Unit, vm: AddPairViewModel = hiltViewModel()) { Spacer(Modifier.height(4.dp)) // Local folder - OutlinedTextField( - value = uriToDisplay(s.localPath), onValueChange = {}, - label = { Text("Local folder") }, - leadingIcon = { Icon(Icons.Default.PhoneAndroid, null) }, - trailingIcon = { - IconButton(onClick = { showLocalBrowser = true }) { - Icon(Icons.Default.FolderOpen, "Browse") - } - }, - readOnly = true, singleLine = true, modifier = Modifier.fillMaxWidth(), - placeholder = { Text("Tap to choose folder…") }, - ) + Box(modifier = Modifier.fillMaxWidth()) { + OutlinedTextField( + value = uriToDisplay(s.localPath), onValueChange = {}, + label = { Text("Local folder") }, + leadingIcon = { Icon(Icons.Default.PhoneAndroid, null) }, + trailingIcon = { Icon(Icons.Default.FolderOpen, null) }, + readOnly = true, singleLine = true, modifier = Modifier.fillMaxWidth(), + placeholder = { Text("Tap to choose folder…") }, + ) + Box(modifier = Modifier.matchParentSize().clickable { showLocalBrowser = true }) + } // Remote folder OutlinedTextField( diff --git a/app/src/main/kotlin/com/syncflow/ui/browser/LocalBrowserDialog.kt b/app/src/main/kotlin/com/syncflow/ui/browser/LocalBrowserDialog.kt index 1606db0..25d28b9 100644 --- a/app/src/main/kotlin/com/syncflow/ui/browser/LocalBrowserDialog.kt +++ b/app/src/main/kotlin/com/syncflow/ui/browser/LocalBrowserDialog.kt @@ -26,9 +26,14 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -131,8 +136,25 @@ fun LocalBrowserDialog( onDismissRequest = onDismiss, properties = DialogProperties(usePlatformDefaultWidth = false, decorFitsSystemWindows = false), ) { + val view = LocalView.current + val density = LocalDensity.current + var topInset by remember { mutableStateOf(0.dp) } + var bottomInset by remember { mutableStateOf(56.dp) } + DisposableEffect(view) { + ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> + val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + with(density) { + topInset = bars.top.toDp() + bottomInset = maxOf(bars.bottom.toDp(), 56.dp) + } + insets + } + ViewCompat.requestApplyInsets(view) + onDispose { ViewCompat.setOnApplyWindowInsetsListener(view, null) } + } + Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.surface) { - Column(modifier = Modifier.fillMaxSize().statusBarsPadding()) { + Column(modifier = Modifier.fillMaxSize().padding(top = topInset)) { // ── Top bar ────────────────────────────────────────────────── TopAppBar( @@ -242,20 +264,21 @@ fun LocalBrowserDialog( // ── Select button ──────────────────────────────────────────── Surface(tonalElevation = 4.dp, modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.navigationBarsPadding()) { - Button( - onClick = { onSelect(currentPath.absolutePath) }, - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, top = 12.dp, end = 16.dp, bottom = 12.dp) - .height(52.dp), - shape = RoundedCornerShape(14.dp), - ) { - Icon(Icons.Default.CheckCircle, null, Modifier.size(20.dp)) - Spacer(Modifier.width(8.dp)) - Text("Select \"$currentFolderName\"", - style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold) - } + Column { + Button( + onClick = { onSelect(currentPath.absolutePath) }, + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, top = 12.dp, end = 16.dp, bottom = 12.dp) + .height(52.dp), + shape = RoundedCornerShape(14.dp), + ) { + Icon(Icons.Default.CheckCircle, null, Modifier.size(20.dp)) + Spacer(Modifier.width(8.dp)) + Text("Select \"$currentFolderName\"", + style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold) + } + Spacer(Modifier.height(bottomInset)) } } } diff --git a/app/src/main/kotlin/com/syncflow/ui/browser/RemoteBrowserDialog.kt b/app/src/main/kotlin/com/syncflow/ui/browser/RemoteBrowserDialog.kt index b25ce1f..e6ef4fd 100644 --- a/app/src/main/kotlin/com/syncflow/ui/browser/RemoteBrowserDialog.kt +++ b/app/src/main/kotlin/com/syncflow/ui/browser/RemoteBrowserDialog.kt @@ -25,9 +25,13 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import androidx.hilt.navigation.compose.hiltViewModel import com.syncflow.domain.model.RemoteFile import kotlinx.coroutines.launch @@ -77,8 +81,25 @@ fun RemoteBrowserDialog( onDismissRequest = onDismiss, properties = DialogProperties(usePlatformDefaultWidth = false, decorFitsSystemWindows = false), ) { + val view = LocalView.current + val density = LocalDensity.current + var topInset by remember { mutableStateOf(0.dp) } + var bottomInset by remember { mutableStateOf(56.dp) } + DisposableEffect(view) { + ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> + val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + with(density) { + topInset = bars.top.toDp() + bottomInset = maxOf(bars.bottom.toDp(), 56.dp) + } + insets + } + ViewCompat.requestApplyInsets(view) + onDispose { ViewCompat.setOnApplyWindowInsetsListener(view, null) } + } + Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.surface) { - Column(modifier = Modifier.fillMaxSize().statusBarsPadding()) { + Column(modifier = Modifier.fillMaxSize().padding(top = topInset)) { // ── Top bar ────────────────────────────────────────────────── TopAppBar( @@ -208,7 +229,7 @@ fun RemoteBrowserDialog( // ── Select button ──────────────────────────────────────────── Surface(tonalElevation = 4.dp, modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.navigationBarsPadding()) { + Column { Button( onClick = { onSelect(state.currentPath) }, modifier = Modifier @@ -225,6 +246,7 @@ fun RemoteBrowserDialog( fontWeight = FontWeight.SemiBold, ) } + Spacer(Modifier.height(bottomInset)) } } } diff --git a/version.properties b/version.properties index fcbf9f7..22acddf 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -VERSION_NAME=1.0.47 -VERSION_CODE=48 +VERSION_NAME=1.0.52 +VERSION_CODE=53