Demo project
Dispatcher's panel on the phone: Grafin
Orders, drivers and overdue work in a dispatcher's pocket; two changes to someone else's screen, each built in two states.
mobile development · Flutter · Dart · Android · iOS

The task
Grafin delivers drinking water and services coolers: 19-litre bottles, pumps, empties to collect, delivery windows. This is a demo project - the company, the drivers and the orders are invented, the job itself is ordinary for the trade. A dispatcher is not always at the monitor. He walks to the warehouse, to the vans, to a phone in the corridor, and the shift keeps moving while he is away. A panel on the phone is for exactly those minutes: see what is burning, hand an order to another driver, and do it without walking back to the desk.
The project has a second purpose: to show work on someone else’s code rather than a finished app. A client on a freelance marketplace rarely arrives with “build it from scratch” - far more often it is “the app exists, fix this screen”. So two screens were built in two states: before the change and after it.
The solution
Flutter, seven screens: shift summary, orders, drivers, an order card, a driver card, the full event feed, and an About page. Dark theme only - that is what the brief asked for, and this demo has no light one.
- Change one, “the list does not show what is burning”. Before, an overdue order carries no mark of its own: order 1031 sits seventh in the flat list, labelled “en route” in the same muted grey as the “delivered” rows around it, so on the first screen it reads as an ordinary line. The other two overdue orders sit 9th and 12th and never reach the first screen at all. After, overdue orders are lifted into a section with a sticky header, rows of orders still in work gain a countdown to the end of the delivery window, the status gains colour, and an overdue row gains a red bar down its left edge. Under the title runs a strip of five filter chips with counts (all 24, new 5, en route 9, overdue 3, delivered 7); it is wider than the screen and scrolls sideways.
- Change two, “the empty screen explains nothing”. The Overdue filter with nothing overdue used to show bare background. Now it shows a drawn check mark inside a circle, the heading “No overdue orders”, the line “All 24 orders are on time”, and a button back to the full list.
- Both states live in one code tree and are chosen by a build variable:
--dart-define=DEMO=k11-before. No git branches, no before/after folders - any frame is re-shot with one command, and “before” does not rot while “after” is being edited. - Overdue is a computed property, not a status: the window has closed while the order is still in work - assigned, picked up or en route. As a status it would make an order “en route” and “overdue” at once - all three overdue orders in the demo are exactly that, en route.
- The dispatcher’s actions work rather than being drawn: an order is handed to another driver through a picker sheet and marked delivered, and the five-step timeline fills in the times.
Details that are easy to miss
- The data is deterministic. “Now” is a constant - 25 August 2026, 15:14 - and neither
RandomnorDateTime.now()appears in it. “14 minutes late” and “-00:14” are the result of a subtraction, not a hard-coded string, so the same frame can be re-shot identically six months from now. - Actions never damage the source data: the lists are unmodifiable and an update builds a new order. A test asserts that the previous object is untouched after an order is marked delivered.
- Russian word endings are computed, not pasted: one minute, two minutes and fourteen minutes each take a different form, handled by a single function.
- An unknown preset does not let the app open with whatever it can manage: instead of the panel it shows an error screen listing the values it accepts. The build itself still succeeds -
String.fromEnvironmenttakes any string, the check lives at runtime, and there is a test for it. - The data holds no phone number and no street address: an order shows its zone, its district and the floor. The demo nature is stated plainly - a line at the foot of the shift screen and a separate About screen.
- Fonts ship inside the repository;
google_fontsis deliberately left out, because it pulls files over the network and a substitute typeface can end up in the frame. - Screenshots are scripted: the status bar is neutralised (9:41, no carrier) and frames come only from
simctlandadb. Android frames are additionally checked for the png signature, the closing chunk and a minimum size -adb exec-outcan truncate a file silently. - A contradiction inside the brief was left visible rather than papered over: the tile caption “12 of them closed” does not agree with the chip counts, which imply 10. Both numbers and where they come from are recorded in the README, and the call is the owner’s.
What is left
The screenshot gallery for this page has not been assembled: there are only raw captures, twelve unprocessed frames from iOS and Android. There are no before/after pairs on the page yet. The code is not affected: all five presets were captured on both platforms, so both builds compile and run.
Screenshots




