Engineering docs
Mobile app spec (v1) — one app, two roles
Last updated Oct 20, 2018 · 4 sections
Decision: ONE Flutter app for both advertisers and ops, role-gated at runtime from
GET /v1/me (role: advertiser | ops) — never two apps. Ops is 1–2 people; a second codebase
would double the maintenance to serve them. Same pattern the web portal already uses.
What the app is FOR (and what it is not)
The responsive website already does booking, uploads, and dashboards on a phone. The app exists for the one thing the web cannot do: push notifications, plus a faster daily-glance surface.
In scope (v1):
- Advertiser: login/signup · campaigns list (lifecycle chips) · campaign detail (stat row, funnel, age/gender, by-day, by-screen) · live view · creatives list with status · push notifications.
- Ops (same app, role-gated): approvals queue with media preview + approve/reject · fleet health with freshness dots · device pairing (type the 6-char code standing at the screen) · push when a creative lands in the queue.
- Deep links from pushes into the relevant screen.
Out of scope (v1) — deep-link to the website instead:
- The 4-step booking wizard, creative upload/replace, venue/zone/screen registry editing, CSV export, payments. These are rare, form-heavy, or desktop-natural.
Architecture
mobile/
lib/
api/ generated-by-hand client for docs/API_CONTRACT.md (dio), token storage
models/ plain Dart models mirroring contract payloads
features/
auth/ login, signup, session
campaigns/ list + detail (advertiser)
live/ live screens grid (both roles)
creatives/ list + status (advertiser, read-only in v1)
ops/ approvals, fleet, pairing
design/ tokens ported from packages/shared/design-tokens/tokens.json
push/ FCM registration + handlers (behind ENABLE_PUSH flag)
test/ widget + unit tests
- State: Riverpod (or provider) — keep it boring, no code-gen heavy stacks.
- API base:
--dart-define=API_BASE=https://api.freebeeads.com, defaulting to prod so a TestFlight build "just works". - Auth: same email+password JWT as web; token in flutter_secure_storage; 401 → logout.
- Design: port the light token set — paper
#F7F6F3, card white, ink#16181A, accent#D85A30, data ramp green/blue/amber/red, mono for numbers only, weights 400/500. The app must look like the same product as the website.
Push notifications (the reason the app exists)
Events → notification (payload carries generic text + ids only, never audience numbers):
- Advertiser: creative approved / rejected (with reason) · campaign went live · weekly summary ("Your week on Gody — tap to see plays and watchers").
- Ops: new creative awaiting approval · screen offline > 30 min (fleet alarm).
Backend work required (phase B): device_tokens table (user_id, fcm_token, platform),
POST /v1/me/push-token, and a small notifier invoked at approve/reject, campaign-start, and
fleet-alarm points. Firebase project + APNs key needed before real sends; until then the app
registers behind a flag and logs.
Phases
- A — shell + read surfaces (this build): auth, role-gated navigation, campaigns list/detail, live view, creatives list, ops approvals/fleet/pairing, tokens/design parity, tests, runs on iOS simulator + Android emulator against production.
- B — push: backend token endpoint + notifier, FCM/APNs wiring, deep links.
- C — distribution: TestFlight (Apple Developer account exists) + Android APK/internal track.
- D — later: in-app booking wizard, upload/replace, offline caching of dashboards.