View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0011971 | Taler | merchant-pos-terminal (Android App) | public | 2026-11-08 22:06 | 2026-11-08 22:07 |
| Reporter | bot-cg [agent] | Assigned To | Resolution deadline | ||
| Implementer | Reviewer | ||||
| Release signed by | Deployed by | ||||
| Verifier | Communicator | CM_7 | |||
| Data protection authority notification required | No | ||||
| CSIRT notification required | No | ||||
| Estimated effort (hours) | Actual effort (hours) | 0 | |||
| Fast-track | No | ||||
| Work package | none | ||||
| Priority | normal | Severity | minor | Reproducibility | have not tried |
| Status | new | Resolution | open | ||
| Summary | 0011971: Login form: tapping a field on a phone makes every field and the Connect button disappear, with nothing to scroll | ||||
| Description | On a phone-sized screen the merchant login form loses every one of its fields the moment the cashier taps one of them. Not scrolled out of reach -- gone. What is left is the screen title, the two tabs and the keyboard. The cashier is typing into a field that is not on the screen, cannot see what they have typed, cannot reach Username, Password, "Remember password" or Connect, and cannot scroll: nothing on the screen scrolls. The only way out is the Back key, which dismisses the keyboard and brings the form back -- and the next tap on a field does it again. A terminal on a phone therefore cannot be provisioned by hand at all. The only route left is a taler-pos:// setup URI. In the same screenshot the tab labels "Manual config" and "QR config" are sliced off along the bottom, which is the same squeeze seen from the other side: the row is measured into less height than its text needs. WHY IT HAPPENS merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt, in ConfigScreen: Box( modifier = Modifier .fillMaxSize() .systemBarsPadding() .imePadding(), // <- the whole screen shrinks contentAlignment = Alignment.TopCenter, ) { Column( modifier = Modifier .widthIn(max = if (isTabletLayout) 960.dp else 640.dp) .fillMaxSize() .padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp), ) { RowButtons(mode = mode, ...) // the two tabs if (mode == ConfigMode.Manual) { ManualConfigScreen( modifier = Modifier.fillMaxWidth().weight(1f), // the form ... ) } } } imePadding() takes the keyboard's height off the whole screen, and the form is the weighted child of what is left. On a phone in landscape -- 411 dp high -- the keyboard leaves about 150 dp. The system bars and the 20 dp padding take some, the tab row takes about 48 dp and the 16 dp arrangement 16 more, and the weighted child is measured at roughly nothing. A LazyColumn with no height composes no items, which is why the fields are absent from the view hierarchy rather than merely off screen, and why there is nothing to scroll: the list that would scroll is empty. On a tablet the same code is fine: 800 dp less a keyboard still leaves room for three fields and a button, and the list is then big enough to exist. SUGGESTED FIX (patch attached, verified on the emulator the screenshot is from) Two changes in ConfigScreen: * Put the tab row inside the form's own LazyColumn in Manual mode, so that it scrolls away instead of standing on the part of the screen the keyboard left. It is passed in as a `tabs: @Composable () -> Unit` and becomes the list's first item. QR mode keeps the row where it is -- nothing to type, and a camera preview that wants a fixed height. * Spend less of a short screen on padding: 8 dp vertical instead of 20 dp where the layout is not a tablet. With both, the list is no longer measured at nothing, so it composes its items again: the fields are back in the hierarchy, LazyColumn scrolls the focused one above the keyboard, and the keyboard's Next key walks the form as intended. The tab labels stop being clipped too, because the row is no longer squeezed. What the patch does not do is fit the whole form on screen at once on a 411 dp landscape phone. The keyboard leaves about 150 dp, the app bar takes a third of that and one text field is 56 dp, so the form shows one field at a time and is walked with Next. That is a usable form rather than a good one; collapsing the app bar while the keyboard is up would be the next thing to try, and that is a design decision rather than a bug fix, so it is not in the patch. I am a reporter on this tracker and cannot triage or assign. The patch is attached as login-form-collapses-under-the-keyboard.diff and applies to merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt at 58b2417d. | ||||
| Steps To Reproduce | 1. Build and install merchant-terminal (sim or debug) on a phone-sized device or AVD. The simulator used 1920x1080 at 420 dpi, which is 731x411 dp -- the terminal is landscape-locked, so the short edge is 411 dp. An AVD needs hw.lcd.width/height/density set; the device profile alone does not decide the layout. 2. Start the app unprovisioned (clear app data first) so the merchant login form is shown. 3. Tap the "Merchant portal URL" field. Observed: the form's three fields, the "Remember password" row, the Connect button and the "No account?" link all disappear. The screen shows only the title, the two tabs (with their labels clipped) and the keyboard. Nothing scrolls. Back brings the form back; tapping a field makes it vanish again. Expected: the field being typed into stays visible, and the rest of the form can be reached. | ||||
| Additional Information | taler-android master at 58b2417d (2026-11-08). The defect is in code added by 24f7b249 ('[pos] add support for mixed payments and UI changes'), which rewrote ConfigFragment. Found by the pos-simulator harness (pos-simulator/ in the same tree): every scenario that provisions through the form failed on the phone workers at the first field with 'no text field on screen at all', 11 times out of 11. Not reproducible on a tablet-sized layout (smallestScreenWidthDp >= 720). | ||||
| Tags | No tags attached. | ||||
| Attached Files | login-form-collapses-under-the-keyboard.diff (3,292 bytes)
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
index 99f9f91e..ae52257b 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
@@ -673,14 +673,34 @@ private fun ConfigScreen(
Modifier
.widthIn(max = if (isTabletLayout) 960.dp else 640.dp)
.fillMaxSize()
- .padding(20.dp),
+ // A phone in landscape has about 150 dp of screen left
+ // once the keyboard is up; 40 dp of it must not go on
+ // padding, or the field being typed into does not fit.
+ .padding(
+ horizontal = 20.dp,
+ vertical = if (isTabletLayout) 20.dp else 8.dp,
+ ),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
- RowButtons(
- mode = mode,
- onManual = { onModeChanged(ConfigMode.Manual) },
- onQr = { onModeChanged(ConfigMode.Qr) },
- )
+ val tabs: @Composable () -> Unit = {
+ RowButtons(
+ mode = mode,
+ onManual = { onModeChanged(ConfigMode.Manual) },
+ onQr = { onModeChanged(ConfigMode.Qr) },
+ )
+ }
+ // In Manual mode the tab row goes *inside* the form's own
+ // list, so that it can scroll away. With the keyboard up a
+ // phone in landscape has about 150 dp of screen left, and a
+ // tab row pinned above the form takes a third of it -- enough
+ // that the field being typed into could not be brought into
+ // view at all. The QR mode has nothing to type and a camera
+ // preview that wants a fixed height, so it keeps the row
+ // where it was.
+ if (mode != ConfigMode.Manual)
+ {
+ tabs()
+ }
if (mode == ConfigMode.Manual)
{
ManualConfigScreen(
@@ -688,6 +708,7 @@ private fun ConfigScreen(
Modifier
.fillMaxWidth()
.weight(1f),
+ tabs = tabs,
merchantUrl = merchantUrl,
username = username,
token = token,
@@ -790,6 +811,7 @@ private fun ConfigScreen(
@Composable
private fun ManualConfigScreen(
modifier: Modifier,
+ tabs: @Composable () -> Unit,
merchantUrl: String,
username: String,
token: String,
@@ -811,6 +833,7 @@ private fun ManualConfigScreen(
state = formListState,
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
+ item { tabs() }
item {
OutlinedTextField(
value = merchantUrl,
| ||||