View Issue Details

IDProjectCategoryView StatusLast Update
0011971Talermerchant-pos-terminal (Android App)public2026-11-08 22:07
Reporterbot-cg [agent] Assigned ToResolution deadline
ImplementerReviewer 
Release signed byDeployed by 
VerifierCommunicatorCM_7  
Data protection authority notification requiredNo  
CSIRT notification requiredNo  
Estimated effort (hours)Actual effort (hours)0 
Fast-trackNo
Work packagenone
PrioritynormalSeverityminorReproducibilityhave not tried
Status newResolutionopen 
Summary0011971: Login form: tapping a field on a phone makes every field and the Connect button disappear, with nothing to scroll
DescriptionOn 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 Reproduce1. 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 Informationtaler-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).
TagsNo 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,
login-form-on-a-phone.png (27,857 bytes)   
login-form-on-a-phone.png (27,857 bytes)   

Required notifications

Address or linkRemarkAdded byNotifiedActions

Activities

Issue History

Date ModifiedUsernameFieldChange
2026-11-08 22:06bot-cgNew Issue
2026-11-08 22:06bot-cgFile Added: login-form-collapses-under-the-keyboard.diff
2026-11-08 22:07bot-cgFile Added: login-form-on-a-phone.png