Apps and portal source
The four phone apps are Flutter apps that share one structure. Each keeps its own screens and legal flows, and talks to the backend only through the Dart SDK (digifir_sdk). Each also has a demo implementation that runs entirely on the phone. The citizen web portal is a React app in the citizen repository and uses the TypeScript SDK.
Structure of an app
| Folder | What is inside |
|---|---|
lib/main.dart | Starts the app: loads translations, connects to the backend, or demo mode, or the "Service not configured" screen |
lib/app.dart | The shell: header, offline banner, current screen, navigation, sheets, toasts; sends the outbox when the phone is back online |
lib/config.dart | Build switches (--dart-define) |
lib/core/ | Design tokens, motion, icons, translations, small helpers |
lib/widgets/ | Reusable buttons, inputs, sheets and navigation |
lib/state/ | Which screen is showing, language, overlays, the app lock; the work in progress, saved as it goes |
lib/models/ | Plain data objects |
lib/services/ | Everything that talks to the outside world, behind interfaces. sdk/ is the DigiFIR backend; demo/ is simulated on the phone. |
lib/screens/ | One file per screen |
assets/i18n/ | All texts, one file per language |
android/, ios/ | Permissions, icons, backup settings, signing, and small native channels for the device key, attestation and screen privacy |
test/ | Automated tests, with a scripted backend |
tool/ | The translation merge tool, the screenshot tests, and contract checkers that hold what the app signs to the backend's contract and the chaincode's catalogue |
docs/ | Step-by-step guides |
What the SDK layer does
| Piece | Job |
|---|---|
| Bootstrap | Decides live, demo or "Service not configured" from BACKEND and API_BASE_URL |
| Device key | The phone's P-256 key in Android Keystore (StrongBox where available) or the Secure Enclave, signing only while the phone is unlocked; a keystore error never falls back to a weaker key |
| Attestation | Play Integrity on Android, App Attest on iPhone, for calls before a session |
| Services | Build, sign and relay the role's actions, keep the encrypted outbox, upload encrypted files, read private parts with signed reads |
| Event hub | One server-sent event stream for the whole app; screens hold the channels they need |
| Push | Registers the FCM token with the backend; a notification only wakes the app |
| Errors | Maps the backend's problem codes to the app's own error messages |
Firebase is used only to deliver push notifications (firebase_core, firebase_messaging).
Build switches
| Switch | Effect |
|---|---|
BACKEND=live with API_BASE_URL=https://... | Always the backend. A missing or non-HTTPS address in a release build shows "Service not configured" instead of switching to demo. Use this for every build given to other people. |
BACKEND=demo | Always demo |
| Neither | The backend when API_BASE_URL is given, otherwise demo; only debug builds fall back to demo |
IDENTITY_SIMULATOR=true (citizen app) | Adds a test identity for development backends; a pilot backend refuses it |
ANDROID_SMS_MODE=composer (court app) | Opens the Messages app for the SIM check instead of sending the SMS silently, for Google Play builds |
The citizen web portal
The portal (portal/ in the citizen repository) is a Vite, React 19 and TypeScript app on the TypeScript SDK, with no router or UI library. It is a static site, deployed as its own nginx image next to the backend in the pilot, or on Vercel for development and previews. It also publishes the public "Delete your account" page the app stores ask for. Its browser key is a WebCrypto P-256 key that cannot be exported, kept in IndexedDB.
Testing
flutter test # in each app
npm test # in portal/Each app's screenshot harness (tool/screenshots/) renders every state of the design for comparison with it. Contract checkers in tool/contract/ hold the signed actions the app builds to the backend's OpenAPI contract and the chaincode's catalogue. Each security finding with a reproduction has a regression test under test/review/.
Publishing
| App | Distribution |
|---|---|
| Citizen | Google Play and the App Store, publicly |
| Police | Managed Google Play as a private app, or installed through the department's device management; Android only for the SIM check |
| Lab, court | Google Play and the App Store. The SIM check's SMS needs Android's restricted SEND_SMS permission, so Play builds may use the composer mode, or the app is distributed privately to managed devices. |
WARNING
The app logos contain the State Emblem of India, whose use is restricted by the State Emblem of India (Prohibition of Improper Use) Act, 2005. Publish only from the authorised department's developer account, or replace the logo.
Android and iOS release builds have not yet been compiled in the build environment. Native changes are listed in each app for the build stage.
Sources: each app's README.md, docs/02-run-the-app.md, docs/07-publish-play-store.md (police: docs/06-publish.md), docs/09-understand-the-code.md on the migration branch; the citizen app's docs/11-web-portal.md; digifir-backend docs/architecture/DECISIONS.md ADR-19, docs/PROJECT-STATUS.md.