A barcode on a phone screen has to be bright. Store scanners are cameras, and a camera reading a dark screen under fluorescent shop lighting either takes several attempts or fails entirely. So a wallet app raises the brightness when it shows a code.

That is one line. The rest of this post is about the ways we got the second half wrong — putting it back.

Window brightness, not system brightness

There are two ways to change screen brightness on Android and only one of them is acceptable here.

Changing the system brightness requires a special permission, affects every app, and persists after yours is gone. An app that does this and fails to restore it leaves the user's phone at full brightness until they notice and fix it themselves. This is a genuinely unpleasant thing to do to someone and we have all experienced it.

The other way is a window attribute. An activity can set a brightness value for its own window, and the system applies it while that window is in front and reverts when it is not. No permission, no global effect, and — importantly — the revert is the system's job rather than ours.

We use the window attribute. This makes the whole class of "the app left my phone bright" bugs impossible to write, which is the correct way to handle a bug class you cannot be trusted to remember.

It is still not one line

Even scoped to a window, the boost has to be conditional, because full brightness is not always right.

Only while a code is on screen. The list of cards does not need to be bright. The boost applies when you open a specific card and ends when you leave it, which means it is tied to the card screen rather than to the app.

Not in the dark, at least not all the way. A wallet opened at night, in a dim shop or a car park, at maximum brightness is physically unpleasant, and scanners do not need maximum in low ambient light — they need contrast, which is easier to achieve when the surroundings are dark. We scale the boost against the ambient level rather than always going to the top.

Never for card details. Numbers, names, and notes on a card do not need to be bright, and making them bright makes them readable to whoever is standing behind you. Only the code region gets the boost.

It has to survive the screen timing out. A code held up at a counter while a queue moves is a screen that will hit its timeout. The card screen keeps the display on while it is showing, and stops when you leave. Without this, the most common interaction — hold the phone out, wait — fails.

Dark mode and barcodes do not mix

This one produced actual scan failures rather than aesthetic complaints.

A wallet app that follows the system theme, with a barcode drawn as a normal foreground element, will render a light-on-dark barcode in dark mode. Scanners expect dark bars on a light background. An inverted barcode is unreadable to most scanners, and some will read it as a completely different value rather than failing cleanly, which is worse.

Codes are always rendered dark on white regardless of theme. The card around it can be dark. The code cannot be. We also keep a white margin around the code — the quiet zone — because scanners use it to find the code's boundaries, and a barcode drawn edge to edge on a card is a barcode that only reads about half the time.

The rest of the counter problem

Brightness and rendering handle the scanner. The rest is about the two seconds before the scanner is involved.

The interaction we design for is: someone is at a till, the queue is moving, and they need a specific card on screen. Anything that adds a step to that is the whole cost of the app.

So the list is ordered by what you actually use rather than alphabetically or by when it was added. Frequently used cards rise. A card you have not opened in months sinks. This is not clever and it removes most of the searching.

Card names matter more than they should. The name you type when adding a card is the name you scan under pressure, and "membership" is useless if you have four. The shop's name, in whatever form you would recognise at a glance, is the right answer. We do not enforce this and we do prompt on it.

Keeping the wallet accurate

The app is only fast if the list is honest, and wallet contents rot quietly. A card expires, a shop closes, a membership lapses, a coupon is used, a payment habit changes and the old card stays because it is familiar.

Add only what you actually present. A card you have never shown to anyone is a card that makes the list longer.

Name for recognition under pressure, not for tidiness.

Set expiry dates on anything temporary. Coupons, event passes, promotional cards. An expired item that is still in the main list makes the whole list less trustworthy, and trust is what makes you open the app rather than fumbling for the physical card.

Review when something changes. Not on a schedule — nobody keeps a schedule for this. When a card expires, a main payment method changes, or you stop going somewhere, that is the moment. It takes ten seconds and it is the only maintenance the app needs.

Remove rather than archive, unless you have a reason. An archive you never look at is a slower way of deleting.

Where a phone wallet is genuinely worse

We should be straightforward about this. A physical card works when your phone is dead, works when the shop's scanner cannot read a screen, and works when you are in a queue and the app decides to update itself.

For anything where failing is expensive — a ticket at a gate, a pass you need to get into a building — the phone is a convenience and not a plan. Keep the fallback available. A wallet app that pretends otherwise is selling you a single point of failure.