9.0 KiB
KROW Worker
A Flutter app for shift workers: find hospitality shifts, clock in on site, get timecards filed automatically, and keep compliance documents in order.
Built from the workflow in the KROW worker-app prototype — the HTML was used as the reference for screens and flow, not as a pixel spec.
Running it
flutter pub get
flutter run
Targets Android, iOS and web. To try it in a browser:
flutter run -d chrome
Demo sign-in: any 10-digit mobile number, then the code 123456.
What works
Everything below is wired to real state, not static screens. Shifts, timecards
and compliance live in memory for the session; onboarding, sign-in,
availability, skills and preferences persist via shared_preferences.
Getting in — splash routes by how far you got last time · 3-screen onboarding with skip · phone entry with validation · OTP with a resend countdown, a wrong-code path and a 3-try budget · 3-step permission run whose grants are recorded and reflected in Settings.
Today — greeting, period earnings and hours logged, a compliance nudge while anything is outstanding, your next shift, quick tiles and matched open shifts.
Shifts — Booked / Open / History. The week strip filters booked shifts by day and dots the days that have work. Open supports search and filters, and Apply books the shift immediately. History carries completed, cancelled and no-show shifts with a stat rail.
Shift detail — full schedule and pay breakdown, plus a requirements check that compares what the business asks for against what you actually have on file. Actions change with status: apply, cancel (with confirmation), or jump to the clock.
Clock — the button unlocks 30 minutes before the shift, and only once you are at the venue. Clocking in starts a live stopwatch against a progress ring; clocking out completes the shift, deducts the unpaid break and files a timecard.
Timecards — every filed card with its scheduled vs clocked window, variance in minutes, and pay. Review one to approve it or raise a dispute with a note. The tab badge counts what is still waiting on you.
Pay — gross pre-tax earnings across week / month / year / all, a trend chart split paid vs pending, per-payment detail, and a semi-monthly payroll schedule that computes the current and next period from today's date.
Profile & compliance — live stats and a trust index derived from the shifts you have actually worked. The compliance hub tracks documents, certificates, tax forms and attire; uploading sends an item back for review, removing it drops it to missing, and expiry dates are picked from a calendar. Plus emergency contacts, experience and skills, availability, benefits, direct deposit, settings, support, FAQs and privacy.
Motion
Motion is used to explain what changed, not to decorate. The rules:
- Going deeper pushes. Detail screens slide in from the right while the screen behind eases back and dims, so the stack reads as a stack. Going sideways — sign-up steps, replacing a screen — cross-fades instead.
- Selection travels. The bottom nav indicator and the segmented controls slide between positions and squash slightly at speed. Labels light up as the indicator arrives over them, driven by the same animated value, so nothing changes ahead of the thing causing it.
- Numbers grow. Progress bars and chart bars tween to new values, so an uploaded document or a switched pay range is something you watch happen.
- State changes animate together. Availability blocks, week-strip days and the clock dial move every part of themselves in one gesture rather than flipping several properties at once.
- Consequential actions are felt. Clocking in and out give a medium impact; selecting a tab, segment, or applying for a shift gives a selection click.
Headers stay put. On the five tabs the blue header is pinned: content scrolls beneath it rather than pushing it off the top. A tall header held at full size would cost a quarter of a phone screen for the whole session, so it collapses as you scroll and cross-fades to a compact bar that keeps what still matters — your name and the period figure on Today, the gross total on Pay.
Where a header holds a control, the collapsed state keeps it. Pay stops shrinking while the range switcher is still on screen; collapsing that away would mean scrolling back to the top just to change what you are looking at. Shifts keeps its whole header fixed for the same reason — the Booked / Open / History switch has to stay reachable.
Scroll physics is left to the platform. Forcing bouncing everywhere looked
smoother on a long list and broke every short one: ScrollView hands any
vertical list without a controller an AlwaysScrollableScrollPhysics, applied
over whatever the behaviour returns, so the drag is accepted however little
content there is. Bouncing then let a list that already fits be dragged away
from the header, leaving a gap down the screen. Android's clamping accepts the
same drag and pins it to the boundary; iOS bounces, which is its own
convention. KrowScrollBehavior now only adds mouse and trackpad dragging for
the web and desktop builds.
Two deliberate exceptions:
- Tab content does not slide. Switching bottom tabs fades and lifts the
content instead. An
AnimatedSwitcherwould hold two copies of the shell in the tree, and the branch navigators are keyed byGlobalKey, so duplicating them throws._TabTransitionanimates one subtree in place, which keeps each tab's scroll position. - No
Herobetween list and detail. The tab branches all stay alive in anIndexedStack, so the same shift can be on screen in two branches at once and duplicate hero tags would throw.
Layout
lib/
core/ theme, router, formatters
models/ shift, timecard, availability, compliance, worker
state/ Riverpod controllers + seed data
screens/ one file per area of the app
widgets/ shift card, week strip, shared pieces
Packages
| Package | Why |
|---|---|
flutter_riverpod |
State: shifts, timecards, availability, compliance, profile, session |
go_router |
Routing, including the 5-tab shell where each tab keeps its own stack |
google_fonts |
Poppins, the brand typeface |
lucide_icons_flutter |
Icon set matching the prototype's line icons |
intl |
Currency, date and time formatting |
shared_preferences |
Persisting session, availability and preferences |
Tests
flutter test
Covers pay calculation (break deduction, clocked vs scheduled hours, zero-pay states), timecard variance, and the formatters — plus the custom nav and segmented control: that the indicator actually travels rather than jumping, that taps report the right index, and that each tab reaches the semantics tree with its selected state (the dot alone is not readable).
Device permissions
Location, notifications and camera are real requests through
permission_handler, wrapped in lib/core/permissions.dart so nothing else
imports the plugin. Two rules that layer enforces:
- The OS is the source of truth. The flags on
SessionStateare a cache for the UI. A permission can be revoked in system settings while the app is closed, so any screen that shows their state callsrefresh()to re-read. - Platforms the plugin does not implement degrade to granted. On web and
desktop it returns
truerather than throwing — the browser prompts at the point of use, so asking up front is neither possible nor useful.
There is no API to revoke a permission, and re-requesting a granted one is a no-op, so switching one off in Settings opens the system settings page instead. Same when a permission is permanently denied and the OS will no longer show its dialog.
Native declarations live in AndroidManifest.xml and ios/Runner/Info.plist;
iOS rejects builds that request a permission without a usage string.
permission_handler_androidcompiles against Android API 37, and its AAR metadata requires dependents to match, soandroid/app/build.gradle.ktspinscompileSdk = 37rather than followingflutter.compileSdkVersion. Building needs theplatforms;android-37SDK package installed.
Wiring it to a backend
The seams are deliberate. lib/state/seed.dart is the only source of demo data;
swap each controller's build() for an API call and the screens are unchanged.
What still stands in for something real:
- Geofence —
ClockScreenhas an "at the venue" switch in place of GPS. Location permission is real, but nothing measures distance yet. Addgeolocatorand a 150 m radius check against the venue. - PDF upload — compliance items take a photo or a gallery image through
image_picker. Picking a document needsfile_picker, so that option is not offered rather than offered and faked. - Upload — a picked file updates local state only; nothing is sent anywhere yet.
- Notifications — permission is requested, but nothing is scheduled or received. Needs a messaging service and a local-notifications plugin.
- SMS verification — the OTP is always
123456.