Skip to content

Fix #5152: respect bottom safe area for picker BELOW_SPINNER buttons#5156

Merged
liannacasper merged 2 commits into
masterfrom
fix/picker-below-spinner-safe-area-5152
Jun 3, 2026
Merged

Fix #5152: respect bottom safe area for picker BELOW_SPINNER buttons#5156
liannacasper merged 2 commits into
masterfrom
fix/picker-below-spinner-safe-area-5152

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Problem

Fixes #5152. On devices with a home indicator (e.g. iPhone 15 Pro Max / iOS 26), buttons added to the lightweight picker popup with LightweightPopupButtonPlacement.BELOW_SPINNER are drawn underneath the home indicator and can activate it.

The popup (an InteractionDialog) is anchored to the very bottom of the screen. Its content pane lays out as:

  • NORTH — the Cancel/Done bar (top of the popup)
  • CENTER — the spinner section, whose SOUTH slot holds the BELOW_SPINNER buttons

So the bottom-most row of the popup is the BELOW_SPINNER button bar, and nothing reserved the bottom safe-area inset for it.

Fix

When building the popup, query Form#getSafeArea() and, when there is a positive bottom inset, add it as bottom padding:

  • With BELOW_SPINNER buttons — the padding goes on their bar, so the bar's background extends through the inset and the buttons remain tappable above the home indicator (the native iOS look, and what the reporter suggested).
  • Without bottom buttons — the padding falls back to the content pane, so a plain picker also clears the indicator.

The inset is applied before the popup height is measured, so it sizes and positions correctly, and is guarded on bottomInset > 0 so non-notched devices/simulators are unaffected. A fresh popup is built on every open, so padding never accumulates.

Testing

  • mvn -pl core compile -Plocal-dev-javase → BUILD SUCCESS.
  • Logic follows the existing safe-area pattern used by Sheet. Not yet verified against a notched simulator skin / device — visual confirmation on device is welcome.

🤖 Generated with Claude Code

The lightweight picker popup is anchored to the bottom of the screen, but
nothing reserved the bottom safe-area inset. On devices with a home
indicator (iPhone X family) the bottom-most row of the popup -- the
BELOW_SPINNER custom buttons, or the spinner wheels when no such buttons
exist -- was drawn underneath the home indicator and could activate it.

Query the form safe area when building the popup and, when there is a
bottom inset, add it as bottom padding. When BELOW_SPINNER buttons are
present the padding goes on their bar so its background extends through the
inset and the buttons stay tappable above it; otherwise it goes on the
content pane so a plain picker also clears the indicator. Applied before
the popup height is measured so it sizes and positions correctly, and
guarded on a positive inset so non-notched devices are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

Cloudflare Preview

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 3, 2026

Compared 122 screenshots: 122 matched.

Native Android coverage

  • 📊 Line coverage: 12.93% (7710/59625 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 10.48% (38252/364914), branch 4.50% (1548/34402), complexity 5.51% (1816/32974), method 9.65% (1488/15413), class 15.79% (341/2159)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 12.93% (7710/59625 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 10.48% (38252/364914), branch 4.50% (1548/34402), complexity 5.51% (1816/32974), method 9.65% (1488/15413), class 15.79% (341/2159)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1044.000 ms
Base64 CN1 encode 210.000 ms
Base64 encode ratio (CN1/native) 0.201x (79.9% faster)
Base64 native decode 1131.000 ms
Base64 CN1 decode 165.000 ms
Base64 decode ratio (CN1/native) 0.146x (85.4% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 3, 2026

Compared 122 screenshots: 122 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 132 seconds

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 928.000 ms
Base64 CN1 encode 1444.000 ms
Base64 encode ratio (CN1/native) 1.556x (55.6% slower)
Base64 native decode 448.000 ms
Base64 CN1 decode 1113.000 ms
Base64 decode ratio (CN1/native) 2.484x (148.4% slower)
Base64 SIMD encode 477.000 ms
Base64 encode ratio (SIMD/native) 0.514x (48.6% faster)
Base64 encode ratio (SIMD/CN1) 0.330x (67.0% faster)
Base64 SIMD decode 452.000 ms
Base64 decode ratio (SIMD/native) 1.009x (0.9% slower)
Base64 decode ratio (SIMD/CN1) 0.406x (59.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 70.000 ms
Image createMask (SIMD on) 23.000 ms
Image createMask ratio (SIMD on/off) 0.329x (67.1% faster)
Image applyMask (SIMD off) 171.000 ms
Image applyMask (SIMD on) 82.000 ms
Image applyMask ratio (SIMD on/off) 0.480x (52.0% faster)
Image modifyAlpha (SIMD off) 226.000 ms
Image modifyAlpha (SIMD on) 94.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.416x (58.4% faster)
Image modifyAlpha removeColor (SIMD off) 175.000 ms
Image modifyAlpha removeColor (SIMD on) 142.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.811x (18.9% faster)
Image PNG encode (SIMD off) 1188.000 ms
Image PNG encode (SIMD on) 896.000 ms
Image PNG encode ratio (SIMD on/off) 0.754x (24.6% faster)
Image JPEG encode 521.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 3, 2026

Compared 86 screenshots: 86 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 241 seconds

Build and Run Timing

Metric Duration
Simulator Boot 96000 ms
Simulator Boot (Run) 1000 ms
App Install 29000 ms
App Launch 9000 ms
Test Execution 1500000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented Jun 3, 2026

Compared 122 screenshots: 122 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 259 seconds

Build and Run Timing

Metric Duration
Simulator Boot 94000 ms
Simulator Boot (Run) 2000 ms
App Install 18000 ms
App Launch 9000 ms
Test Execution 277000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1206.000 ms
Base64 CN1 encode 1732.000 ms
Base64 encode ratio (CN1/native) 1.436x (43.6% slower)
Base64 native decode 395.000 ms
Base64 CN1 decode 1159.000 ms
Base64 decode ratio (CN1/native) 2.934x (193.4% slower)
Base64 SIMD encode 566.000 ms
Base64 encode ratio (SIMD/native) 0.469x (53.1% faster)
Base64 encode ratio (SIMD/CN1) 0.327x (67.3% faster)
Base64 SIMD decode 531.000 ms
Base64 decode ratio (SIMD/native) 1.344x (34.4% slower)
Base64 decode ratio (SIMD/CN1) 0.458x (54.2% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 76.000 ms
Image createMask (SIMD on) 11.000 ms
Image createMask ratio (SIMD on/off) 0.145x (85.5% faster)
Image applyMask (SIMD off) 406.000 ms
Image applyMask (SIMD on) 213.000 ms
Image applyMask ratio (SIMD on/off) 0.525x (47.5% faster)
Image modifyAlpha (SIMD off) 328.000 ms
Image modifyAlpha (SIMD on) 81.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.247x (75.3% faster)
Image modifyAlpha removeColor (SIMD off) 307.000 ms
Image modifyAlpha removeColor (SIMD on) 107.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.349x (65.1% faster)
Image PNG encode (SIMD off) 1567.000 ms
Image PNG encode (SIMD on) 1313.000 ms
Image PNG encode ratio (SIMD on/off) 0.838x (16.2% faster)
Image JPEG encode 1035.000 ms

The bottom safe-area inset now shifts the lightweight picker popup (and its
BELOW_SPINNER buttons) above the home indicator / gesture bar, so the Android
and iOS UI-test references that exercise the picker change. Refreshed the new
references from the CI actuals for the platforms that report a bottom inset:
Android, iOS (GL) and iOS (Metal). mac-native and javascript report no inset,
so their goldens are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@liannacasper liannacasper merged commit 02b93a2 into master Jun 3, 2026
27 of 28 checks passed
@liannacasper liannacasper deleted the fix/picker-below-spinner-safe-area-5152 branch June 3, 2026 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Picker: buttons added to the bottom BELOW_SPINNER do not respect the safe area on iOS

2 participants