mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-27 13:39:28 +00:00
android, desktop: fix Submit button not visible in passcode view in split screen (#7497)
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
co-authored by
Evgeny Poberezkin
parent
176e55895a
commit
ecb9d87157
+4
-4
@@ -64,7 +64,7 @@ fun PasscodeView(
|
||||
@Composable
|
||||
fun VerticalLayout() {
|
||||
Column(
|
||||
Modifier.handleKeyboard().focusRequester(focusRequester),
|
||||
Modifier.systemBarsPadding().handleKeyboard().focusRequester(focusRequester),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
@@ -74,7 +74,7 @@ fun PasscodeView(
|
||||
Text(reason, Modifier.padding(top = 5.dp), style = MaterialTheme.typography.subtitle1)
|
||||
}
|
||||
}
|
||||
PasscodeEntry(passcode, true)
|
||||
PasscodeEntry(passcode, true, Modifier.weight(1f, fill = false))
|
||||
Row(Modifier.heightIn(min = 70.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
SimpleButton(generalGetString(MR.strings.cancel_verb), icon = painterResource(MR.images.ic_close), disabled = !buttonsEnabled.value, click = cancel)
|
||||
Spacer(Modifier.size(20.dp))
|
||||
@@ -85,9 +85,9 @@ fun PasscodeView(
|
||||
|
||||
@Composable
|
||||
fun HorizontalLayout() {
|
||||
Row(Modifier.padding(horizontal = DEFAULT_PADDING).handleKeyboard().focusRequester(focusRequester), horizontalArrangement = Arrangement.Center) {
|
||||
Row(Modifier.systemBarsPadding().padding(horizontal = DEFAULT_PADDING).handleKeyboard().focusRequester(focusRequester), horizontalArrangement = Arrangement.Center) {
|
||||
Column(
|
||||
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, top = DEFAULT_PADDING),
|
||||
Modifier.weight(1f, fill = false).padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, top = DEFAULT_PADDING),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
|
||||
+2
-1
@@ -22,8 +22,9 @@ import chat.simplex.res.MR
|
||||
fun PasscodeEntry(
|
||||
password: MutableState<String>,
|
||||
vertical: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Column(modifier, horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
PasscodeView(password)
|
||||
BoxWithConstraints {
|
||||
if (vertical) {
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# Fix Submit button not visible in passcode view in split screen
|
||||
|
||||
## Problem
|
||||
|
||||
On Android in **split screen**, the "Enter Passcode" view shows the title and the
|
||||
keypad but **no Cancel/Submit buttons** — the app cannot be unlocked with a passcode
|
||||
without leaving split screen.
|
||||
|
||||
The same `PasscodeView` also backs `SetAppPasscodeView`, so "New Passcode" /
|
||||
"Confirm passcode" (Privacy & security → passcode, and the passcode onboarding in
|
||||
`AppLock.setPasscode`) are affected identically.
|
||||
|
||||
Desktop is affected too whenever the window is short (below ~590dp of height at a
|
||||
typical width), since desktop always uses the vertical layout.
|
||||
|
||||
## Cause
|
||||
|
||||
Two independent defects, both invisible at full screen.
|
||||
|
||||
### 1. The keypad consumes the space the buttons need
|
||||
|
||||
`PasscodeView.VerticalLayout` is a `Column` of three children — title, keypad,
|
||||
buttons `Row` — and the keypad's size is derived from the constraints it is offered
|
||||
(`PasswordEntry.kt`):
|
||||
|
||||
```kotlin
|
||||
val s = if (appPlatform.isAndroid) minOf(maxWidth, maxHeight) / 4 - 1.dp else ...
|
||||
```
|
||||
|
||||
`Column` measures unweighted children in order, each with `maxHeight` reduced by what
|
||||
the previous ones took, so the keypad — the **second** child — is offered *all*
|
||||
remaining height and a 4-row grid then occupies exactly that. The buttons `Row` is
|
||||
measured last, from what is left, and `Modifier.heightIn(min = 70.dp)` cannot rescue
|
||||
it: `heightIn` constrains its target into the incoming constraints, so a 70dp minimum
|
||||
against a 1dp maximum yields 1dp.
|
||||
|
||||
At full screen the bug is dormant because `maxWidth` is the smaller term there — the
|
||||
keypad is sized by width and the leftover height goes to the buttons. It appears as
|
||||
soon as the remaining height drops below the width, which every split-screen half does.
|
||||
|
||||
Measured with a Compose harness that renders these composables at fixed window sizes
|
||||
(411dp wide, density 1, `Surface(Modifier.fillMaxSize())` as in `LocalAuthentication.kt`):
|
||||
|
||||
| window | keypad `maxHeight` | key size | buttons Row height | Submit |
|
||||
|---|---|---|---|---|
|
||||
| 411×914 (full screen) | 805 | 101.75 | 70 @ y=763 | 108×40 ✓ |
|
||||
| 411×520 | 411 | 101.75 | **0** | 108×0 |
|
||||
| 411×480 | 371 | 91.75 | **0** | 108×0 |
|
||||
| 411×445 (split half) | 336 | 83.0 | **1** @ y=444 | 108×**1** |
|
||||
| 411×400 | 291 | 71.75 | **0** | 108×0 |
|
||||
|
||||
`HorizontalLayout` has the same defect in the width axis: its keypad is 4 keys wide
|
||||
and sized `minOf(maxWidth, maxHeight) / 3.5f`, so the keypad's *width* grows with the
|
||||
available *height*, and the Cancel/Submit `Column` — the **second** child of the `Row`
|
||||
— gets the remainder. At 411×365 it is offered `maxWidth = 24dp` and Submit measures
|
||||
**0×0**; at 411×445 it is offered **0dp** and Submit is placed at **x=441**, outside a
|
||||
411dp-wide window.
|
||||
|
||||
This branch matters in split screen because `windowOrientation()` reads
|
||||
`Configuration.orientation`, which is computed from the *activity window* bounds, not
|
||||
the device: a split half of a tall phone is `PORTRAIT` (~411×445) while a half of a
|
||||
16:9 phone is `LANDSCAPE` (~411×365). Both layouts are reachable, and both were broken.
|
||||
|
||||
### 2. No window insets
|
||||
|
||||
`MainActivity` calls `enableEdgeToEdge()`, and this screen never applied insets — at
|
||||
full screen `SpaceEvenly` happened to leave the buttons ~80dp clear of the navigation
|
||||
bar. Once the buttons are laid out correctly in a split half, they land at the window
|
||||
edge: with fix 1 alone, Submit occupies y=390…430 of a 445dp window, and a 48dp
|
||||
3-button navigation bar covers y=397…445 — **33 of its 40dp**, so the button is drawn
|
||||
but not reliably tappable in the bottom split window.
|
||||
|
||||
## Fix
|
||||
|
||||
`Modifier.weight(1f, fill = false)` on the keypad in the vertical layout and on the
|
||||
keypad column in the horizontal layout (`PasscodeView.kt`), which requires a `modifier`
|
||||
parameter on `PasscodeEntry` (`PasswordEntry.kt`):
|
||||
|
||||
```kotlin
|
||||
PasscodeEntry(passcode, true, Modifier.weight(1f, fill = false))
|
||||
```
|
||||
|
||||
`Column`/`Row` measure **weighted children after all unweighted ones**, so the title
|
||||
and the buttons are measured first at their natural size and the keypad receives what
|
||||
is left — the inverse of the current order, with no size constant to keep in sync.
|
||||
`fill = false` keeps the keypad at its own size rather than stretching it, and because
|
||||
a weighted child makes the container expand to the incoming bounded maximum, the
|
||||
`SpaceEvenly` / `SpaceBetween` distribution at full screen is unchanged.
|
||||
|
||||
Plus `Modifier.systemBarsPadding()` on both layout roots.
|
||||
|
||||
Measured after the fix:
|
||||
|
||||
| window | branch | key size | Submit |
|
||||
|---|---|---|---|
|
||||
| 411×445, 24dp status bar (top split half) | vertical | 59.5 | 108×40 @ y=390…430 ✓ |
|
||||
| 411×445, 48dp nav bar (bottom split half) | vertical | 53.5 | 108×40 @ y=342…382, clear of the bar ✓ |
|
||||
| 411×365, 48dp nav bar | horizontal | 29.3 | 108×40 @ x=263…371 ✓ |
|
||||
| 411×914 (full screen) | vertical | **101.75 (unchanged)** | 108×40, column y=24…866 |
|
||||
|
||||
The insets go on the `Column`/`Row` themselves rather than a wrapping `Box`: Material
|
||||
`Surface` lays its content out with `propagateMinConstraints = true`, so the layout root
|
||||
receives the window size as a *minimum*. A plain `Box` in between drops that minimum
|
||||
(its own default is `false`), the `Column` becomes wrap-content, `SpaceEvenly` has no
|
||||
slack left to distribute, and the whole screen top-aligns — measured as the buttons
|
||||
moving from y=763 to y=544 at full screen. Keeping the padding on the root preserves
|
||||
the propagation.
|
||||
|
||||
## Scope / non-goals
|
||||
|
||||
- Both files are `commonMain`, so the fix covers Android and desktop; on desktop
|
||||
`systemBarsPadding()` resolves to zero insets and only the `weight` change has an
|
||||
effect (short desktop windows).
|
||||
- The keypad necessarily gets smaller in a small window: 53.5–59.5dp keys in a portrait
|
||||
split half, 29.3dp in the 411×365 horizontal case, where the title, reason and
|
||||
passcode text consume ~170dp before the keypad is measured. Making the passcode
|
||||
screen itself more compact below some height (smaller title, dropping the reason
|
||||
line) is a separate design change and is not attempted here.
|
||||
- `systemBarsPadding()` also shrinks the keypad in full-screen landscape (79.6 → 59.0dp).
|
||||
That is the same 72dp the bars always occupied — previously the keypad's bottom row
|
||||
extended ~8dp under the navigation bar — but it is a visible change on a screen that
|
||||
did not show the reported bug.
|
||||
- Not changed: `windowOrientation()` branching on `Configuration.orientation`. Selecting
|
||||
the layout from the measured aspect ratio (as iOS does) would be a better fit for
|
||||
resizable windows, but it changes behaviour on every device rather than fixing this bug.
|
||||
|
||||
## iOS
|
||||
|
||||
Not affected, and for a structural reason worth recording: iOS picks its layout from the
|
||||
*measured* geometry rather than an orientation flag —
|
||||
`if g.size.width < g.size.height * 2 / 3` (`PasscodeView.swift`) — so a short, wide
|
||||
window (the geometry that breaks Android's vertical layout) selects iOS's horizontal
|
||||
layout, whose keypad is sized from height (`s = height / 5`) with the buttons column
|
||||
explicitly bounded to `height / 5 * 3 * 0.97`. The vertical layout's width-driven keypad
|
||||
(`s = width / 3`) is only ever used when the window is at least 1.5× taller than wide,
|
||||
where it fits by construction. No iOS change required.
|
||||
|
||||
## Verification
|
||||
|
||||
- Compose measurement harness at the window sizes tabulated above, before and after,
|
||||
for both layout branches and for 24dp/48dp/72dp inset combinations.
|
||||
- Android arm64 debug APK (`bash ~/build/android.sh`) — manual check of Submit in both
|
||||
the top and the bottom split-screen half, and no visual change at full screen.
|
||||
- Linux x86_64 AppImage (`bash ~/build/linux.sh`) — manual check with the window resized
|
||||
short, confirming the desktop vertical layout keeps Cancel/Submit visible.
|
||||
- Also exercise "New Passcode" / "Confirm passcode" (`SetAppPasscodeView`) in split
|
||||
screen, which share `PasscodeView`.
|
||||
Reference in New Issue
Block a user