A way out of the numeric keyboard, and a Swap form that empties after a swap - #232
Merged
EduMenges merged 6 commits intoAug 10, 2026
Conversation
On a phone the news grid resolved to two 181px columns, which gave a 16px headline 147px of line against a 62-character median headline. Nearly every card was truncated, and 30 articles cost 4,248px of grid for very little readable text. The narrow presentation is now a lead hero followed by one digest panel: the same DashboardScrollContainer every other list page uses, holding the search field, a story count and every remaining article as a 72px-thumbnail row with a two-line headline and a one-line dek. "Next up" is gone as a separate band; its three stories are simply the first three rows. The wide presentation is untouched. Two smaller corrections to the header while it was open: The refresh button now renders on desktop only. It exists because pull-to-refresh is unreachable with a mouse, which is a reason that only holds where there is a mouse; on a phone RefreshIndicator is already mounted over this same feed, so the glyph was a second door into a room that has one. Its 40px tap target, not the 32px title, was what set the header row's height, so removing it takes the header from 56 to 48. The freshness stamp is nudged down 1px. GWPageHeader centres its trailing by line box, and a line box is not where the letters are: Inter's proportional leading puts the 24/32 title's ink at 7.93..30.61 and this 14/20 stamp's at 11.49..25.03, so the stamp read a pixel high. Baseline alignment overshoots 2.6px the other way. Sketches 189 (scheme C) and 194 (scheme A).
The phone page inherited the dashboard panel's filter bar: a track of four icon-only chips with the remaining five filters behind an overflow menu, and no labels anywhere, because a chip that grows on tap shoves its neighbours sideways. It cost the panel a 44px row plus its gap, and a filter chosen from the menu left no mark on the page at all. The trigger is now a funnel in GWPageHeader.trailing, the slot Crypto News already fills, which costs the page no vertical space. It opens the app's own ResponsiveDrawer with a GWSelectRow per filter, each carrying the same badge mark the rows below use and its live count, so all nine filters and All are one gesture away and every one of them is named. A live filter paints the trigger and adds a dismissible chip row above the list carrying the filter's name and "3 of 10", which is the count Assets prints and this page never had. Icons.filter_alt_outlined rather than Icons.tune: tune is already Swap's settings glyph, and filter_alt_outlined is already this screen's own filtered-empty mark. The page's filter now lives on TransactionsScreen rather than inside TransactionsSlimView's State, because the trigger that sets it sits two widgets above the list. Both pass-throughs take it as an optional parameter, so the dashboard panel keeps its own internal filter and its call sites are unchanged. Three corrections from reviewing it on device: The page title sat 8px lower than every other page title, and the list 16px lower, because a 48x48 IconButton in the header rows sets that row's height against the 32px title line. The trigger is 48x32 now and the page measures 48/0/48, identical to Assets. That trades tap height: 32 clears WCAG 2.2 SC 2.5.8's 24x24 floor but is under Apple's 44pt recommendation vertically. The phone background drops GWMeshBackground for the plain surfaceBase that Assets and Crypto News paint. The "No more transactions" terminus is gone. The cap it used to distinguish is now asserted by row counts and the presence of View all. Sketch 195, scheme G2.
Seven sketches behind the two changes above, kept because each records a decision that is not visible in the diff. 189 measured why the phone news grid failed and put five schemes against real CoinTelegraph data. 190 put the Transactions page in the Assets page's language. 191 and 192 are two rounds of filter treatments; 193 corrected three assumptions the earlier rounds had made about the app's own drawer, its icon table and whether the count line earned its space. 194 covered the news freshness stamp. 195 is the design that shipped. Three corrections recorded there rather than silently fixed: sketches 188, 190, 191 and 192 drew hand-made filter glyphs instead of the shared badgeSpec table, so their marks were stroked where the app's are filled and vertical where the app's are diagonal; 188 and 190 drew a count on each filter chip that the code has never rendered; and 191 and 192 invented a bottom sheet rather than using ResponsiveDrawer. The layout findings stand, the impressions they invited were made against the wrong marks. Also noted for later, not fixed here: Mint is the one filter whose glyph is not a Material icon but a stroked SVG asset, so it reads heavier than everything beside it at any size, and that is true in the shipped app.
The team's standup note for the Crypto News feed and the Transactions filter, in the flat-bullet chat format, plus what was deliberately left open and why.
… has one iOS gives its number and decimal pads no return key, so a field using one can only be escaped by tapping somewhere else. Nothing on screen says so. Six surfaces had that trap: Swap, Bridge, Swap settings, Banxa, the token page and Settings. `GWKeyboardDoneBar` wraps a field and floats a toolbar above the keyboard for as long as that field holds focus. It is drawn in the idiom iOS 26 uses rather than the one iOS 18 used: an island inset from both edges, 8pt clear of the keyboard rather than flush against it, rounded, blurred, with a shadow so the gap reads as clearance instead of a hole. Leading chevrons move focus between fields, trailing is a tick rather than the word "Done", which is the direction Apple's own accessory bar went. Tapping outside dismisses too - the bar is the affordance, tapping away is the shortcut, and doing it inside the component means no call site can ship half the pair. A truly native accessory view is not available to us. Flutter has no API for it (flutter#124784, open since 2023, P3), the popular package draws a Flutter widget exactly as this does, and the one claiming native has 314 downloads, which is not a dependency a wallet should take. On iOS 26 the point is close to moot: the system keyboard grew a margin and rounded corners of its own, so no accessory view sits flush any more, native or not. The chevrons are real, not decoration. Every field wrapped with the bar enabled is a navigation stop, so they light up wherever a surface has more than one - today that is Settings, with four numeric rows. A field wrapped with the bar disabled, like Swap's read-only "You Receive", is deliberately not a stop: focusing it opens no keyboard and the bar would vanish mid-navigation. Building them surfaced a latent bug in the bar itself. It holds its overlay until the keyboard inset reaches zero, which is what makes it ride the keyboard down, but on a hop between sibling fields the stale entry stays on top - so by the third hop one field's controls would paint over another field's bar. Never visible without the chevrons. A bar now drops every other bar's entry before inserting its own, and bars are scoped so one inside a drawer cannot step onto the screen behind it. Contrast measured against the real translucent fill rather than the token: the fill is 94% opaque because the honest worst case is arbitrary content behind it, and at 88% a dark label falls to 4.26:1 over dark content. The blur buys motion and a soft edge, not transparency.
… its title Two things Jakub hit testing the Swap screen on a phone. **The form kept its values.** Pressing the CTA showed a receipt and recorded a transaction, then left "You Pay", "You Receive" and the fetched route exactly as they were, so closing the receipt returned to a fully populated form and one stray tap repeated the whole thing. The amounts, both controllers and the route are cleared once the submit path has succeeded. The token selections are kept on purpose: the wallet-change listener nulls them because the chain moved and the catalogue is being reloaded, but after a swap nothing about the chain changed and the pair just traded is plausibly the pair wanted again. The debounce timer is cancelled and the route-error flag reset, so the screen is genuinely at rest rather than only looking it. There is a caveat recorded next to the API TODO, where whoever wires Squid will see it: this screen does not execute anything yet, so the clear hangs off the local record. When the real call lands the clear has to move behind a genuine success, or a rejected swap will wipe the amounts the user typed. **The settings icon sat 22px below the title it belongs to.** `GWPageHeader` centres a trailing against the whole identity block, which on this screen is title plus subtitle, so the icon drifted down by half the subtitle. It now sits on the title's own centre line, at the right edge, the same relationship the filter trigger has on the Transactions page. `GWPageHeader` gained an opt-in for that; the eight other callers do not pass it and render exactly what they did before. The title and subtitle stay centred at both widths. Left-aligning them to match Assets and News was tried on 2026-08-10 and rejected on device. The trigger is a 48x32 box rather than a plain 48x48 button, and that is load-bearing rather than cosmetic: measured, a 48-tall control on the title line takes the header from 92 to 108 and drops the title 8px below where Assets and Transactions put theirs. Same trade as the Transactions trigger - 32 clears WCAG 2.2 SC 2.5.8 and Android's 48dp horizontally, and misses Apple's 44pt vertically.
braianxde
marked this pull request as ready for review
August 10, 2026 11:34
Base automatically changed from
redesign/news-digest-tx-filters-260810
to
redesign/assets-boxed-panels-260808
August 10, 2026 18:06
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three things a person hits on the Swap screen, two of which were traps rather than rough edges.
You could not get out of the numeric keyboard
iOS gives its number and decimal pads no return key. A field using one can only be escaped by tapping somewhere else, and nothing on screen says so. Jakub hit this on Swap and could not tell how to dismiss it. Six surfaces had the same trap: Swap, Bridge, Swap settings, Banxa, the token page and Settings.
GWKeyboardDoneBarwraps a field and floats a toolbar above the keyboard while that field holds focus. Leading chevrons move focus between fields, trailing is a tick. Tapping outside dismisses too - the bar is the affordance, tapping away is the shortcut, and doing both inside the component means no call site can ship half the pair.It is drawn in the idiom iOS 26 uses, not the one iOS 18 used. The first version was an edge-to-edge opaque band flush against the keyboard, which is the old
UIToolbarlook, and on a device running 26.6 it read as foreign. iOS 26 gave the system keyboard a margin and rounded corners of its own, so nothing sits flush against it any more, and Apple's accessory bars became floating rounded translucent islands with glyphs instead of words - Safari's now shows a key, a card, a pin and a checkmark. So: an island inset from both edges, 8pt clear of the keyboard, rounded, blurred, with a shadow so the gap reads as clearance rather than a hole, and a tick rather than the word "Done".A truly native accessory view is not available to us, and this was checked rather than assumed. Flutter has no API for it (flutter#124784, open since 2023, P3).
keyboard_actions, the popular package, draws a Flutter widget exactly as this does. The one package claiming native is version 0.0.4 with 314 downloads, which is not a dependency a wallet should take, and AGENTS.md says no new dependency if it can be avoided. Reaching into the engine to findFlutterTextInputViewand attach a realUIToolbardepends on undocumented internals. On iOS 26 the gap between native and drawn is small anyway, because Apple broke the seamless blend for everyone.The chevrons are real, not decoration. Every field wrapped with the bar enabled is a navigation stop, so they light up wherever a surface genuinely has more than one. Today that is Settings, with four numeric rows; its text rows are excluded for free. On the other five there is one field and both chevrons are correctly dead. A field wrapped with the bar disabled - Swap's read-only "You Receive" - is deliberately not a stop, because focusing it opens no keyboard and the bar would vanish mid-navigation.
Building the chevrons surfaced a latent bug in the bar itself. It holds its overlay entry until the keyboard inset reaches zero, which is what makes it ride the keyboard down instead of popping. Correct when the keyboard is leaving, wrong when a sibling field took the focus: by the third hop the stale entry is on top, and one field's controls would paint over another field's bar. Invisible without the chevrons. A bar now drops every other bar's entry before inserting its own, and bars are scoped by focus scope so one inside a drawer cannot step onto the screen behind it.
The form kept its values after a swap
Pressing the CTA showed a receipt and recorded a transaction, then left "You Pay", "You Receive" and the fetched route exactly as they were. Closing the receipt returned to a fully populated form, and one stray tap on the CTA repeated the whole thing.
The amounts, both controllers and the route are cleared once the submit path succeeds. The token selections are kept on purpose: the wallet-change listener nulls them because the chain moved and the catalogue is being reloaded, but after a swap nothing about the chain changed, and re-picking both from a drawer is the expensive half of the form. The debounce timer is cancelled and the route-error flag reset, so the screen is genuinely at rest rather than only looking it.
The settings icon sat below the title it belongs to
Measured 22px below the title's centre line.
GWPageHeadercentres a trailing against the whole identity block, which on this screen is title plus subtitle, so the icon drifted down by half the subtitle. It now sits on the title's own centre line at the right edge, the same relationship the filter trigger has on Transactions.The title and subtitle stay centred at both widths. Left-aligning them to match Assets and News was tried and rejected on device.
Verification
dart format lib testflutter analyzeflutter test+1221 ~3, all passed, run twicecheck_brace_stylecheck_raw_colorscheck_onboarding_seed_safetycheck_no_new_key_logging --scan-treecheck_agent_rules_synccheck_agent_rules_syncis red and was red before this branch existed: the drift is betweenAGENTS.mdand.github/copilot-instructions.md, in prose about the lazy-developer ladder. Neither file is touched here, and it is red on PR #231 for the same reason.Nineteen tests are added: fourteen on the keyboard bar, four on the Swap header, and the header set compares a really-pumped Swap screen against a really-pumped Assets screen rather than against a copied constant.
Numbers in this description are measured, not estimated. The 22px icon offset, the 92-to-108 header growth quoted below, the tap targets and every contrast figure come from pumped widget tests.
Things worth a reviewer's attention
Deliberately not here
a chevron with nowhere to go is disabled to a screen readerpasses in isolation and in the full suite, but failed once in a multi-file subset run on a semanticsfocusaction. It is new code in this PR, not a pre-existing flake. Left standing rather than quietly deleted or loosened.BackdropFilterexists only while the bar is on screen and is clipped to a 44pt island rather than the layer, but a real frame number needs a profiling pass.// TODO: invoke Squid APIis untouched; the CTA records a transaction locally and shows a receipt. The form clear therefore hangs off the local record, and the code says next to that TODO that it must move behind a real success when Squid is wired, or a rejected swap will wipe the amounts the user typed.gwPageHeaderContentInsetand thecenteredfield both still say the centred form is "(Swap, Feedback)". Feedback stopped passingcenteredwhen 153-B moved its title to the frame's left edge, so Swap is now the only one. Predates this branch.Base branch
Stacked on
redesign/news-digest-tx-filters-260810(PR #231), which is onredesign/assets-boxed-panels-260808(PR #230), which is onredesign/row-rhythm-260808(PR #227). Basing ondevelopwould drag every commit already under review into this diff. Retarget once the parents land.