SA
Sanchalan Docs
Mobile app

Mobile app

Cross-platform Flutter 3.22+ client. Source repo at /home/kushal/apps/sanchalan_setu/attendance_mobile/. Targets Android (primary, BYOD majority) + iOS.

Source layout

attendance_mobile/
├── pubspec.yaml                   # deps: dio, riverpod, go_router, hive, geolocator, ...
├── analysis_options.yaml
├── scripts/
│   ├── bootstrap.sh               # idempotent: flutter create + pub get + manifest patches
│   ├── patch_android_manifest.py  # adds permissions + sanchalan-attendance:// intent filter
│   └── patch_ios_plist.py         # NSLocationWhenInUseUsage + URL scheme
├── lib/
│   ├── main.dart                  # entry: Hive init, runApp
│   ├── app.dart                   # router + bottom-nav shell
│   ├── config.dart                # API_BASE, AUTH_MODE, deep-link scheme — all dart-define
│   ├── theme.dart                 # Rajya-Sabha-blue Material 3 theme
│   ├── api/                       # Dio clients per backend domain
│   │   ├── api_client.dart        # auth-injecting interceptor
│   │   ├── device_api.dart
│   │   ├── leave_api.dart
│   │   ├── profile_api.dart
│   │   ├── punch_api.dart
│   │   └── tour_api.dart
│   ├── auth/
│   │   ├── pkce.dart              # code_verifier + code_challenge generation
│   │   ├── auth_repo.dart         # SSO mobile flow (browser + deep-link)
│   │   ├── token_store.dart       # flutter_secure_storage wrapper
│   │   └── login_screen.dart
│   ├── device/
│   │   ├── device_id.dart         # stable UUIDv4 + sha256-salted installation hash
│   │   ├── keypair.dart           # P-256 keypair, persisted, signs in raw r||s
│   │   └── attestation.dart       # rooted/emulator/mock-location signals
│   ├── geo/
│   │   └── location_service.dart  # high-accuracy fix + Wi-Fi SSID
│   ├── punch/
│   │   ├── punch_signer.dart
│   │   ├── offline_queue.dart     # Hive box, 48h max-age, replay-on-reconnect
│   │   └── punch_screen.dart
│   ├── attendance/
│   │   ├── today_screen.dart
│   │   └── month_screen.dart      # calendar grid (NIC-style)
│   ├── leave/
│   │   ├── leave_home_screen.dart
│   │   └── leave_apply_screen.dart
│   ├── tour/
│   │   └── tour_screens.dart      # list + apply
│   ├── holidays/
│   │   └── holidays_screen.dart
│   ├── devices/
│   │   └── devices_screen.dart
│   ├── profile/
│   │   └── profile_screen.dart
│   └── dashboard/
│       └── dashboard_screen.dart  # NIC-style home
└── test/
    ├── pkce_test.dart
    └── punch_signer_test.dart

Screen graph

flowchart LR Login["/login
SSO Parichay"] --> Shell subgraph Shell["bottom-nav shell"] Home["/home
Dashboard"] Punch["/punch"] Today["/today"] Month["/month
(calendar grid)"] Leave["/leave"] end Home --> Tour["/tour"] Home --> Hol["/holidays"] Home --> Dev["/devices"] Home --> Prof["/profile"] Leave --> LeaveApply["/leave/apply"] Tour --> TourApply["/tour/apply"] Prof --> Login

Punch signing flow

sequenceDiagram participant U as User participant App as Flutter app participant SS as flutter_secure_storage participant Geo as Geolocator participant Be as backend U->>App: tap "Punch IN" App->>SS: read keypair (or generate P-256 on first use) App->>Geo: getCurrentPosition (high accuracy) Geo-->>App: lat, lng, accuracy, ssid, mocked App->>App: nonce = hex(rand(16))
punched_at = now (UTC iso)
msg = nonce + device_uuid + punched_at
sig = ECDSA P-256 SHA-256 raw r||s std-base64 App->>Be: POST /api/v1/punch (Bearer)
{device_uuid, punch_type:IN, punched_at,
lat, lng, accuracy_m, ssid_seen,
nonce, signature, anti_spoof} alt network up Be-->>App: 201 + verdict App->>U: show verdict pill else network down App->>App: OfflineQueue.enqueue(payload) App->>U: show "queued, will sync" Note over App: drainOffline fires on connectivity event end

Build steps

# pre-reqs: Flutter 3.22+, Android Studio (SDK API 34+), Xcode 15+ for iOS

cd /home/kushal/apps/sanchalan_setu/attendance_mobile
./scripts/bootstrap.sh                    # generates android/, ios/, runs pub get, patches manifests

flutter run \
  --dart-define=API_BASE=https://attendance.rajyasabha.digital/api \
  --dart-define=IMEI_SALT_FRONTEND=$(openssl rand -hex 16) \
  --dart-define=AUTH_MODE=sso

flutter build apk --release \
  --dart-define=API_BASE=https://attendance.rajyasabha.digital/api \
  --dart-define=IMEI_SALT_FRONTEND=<provisioned-from-vault>

Permissions requested

PlatformPermissionWhy
AndroidINTERNETAPI calls.
ACCESS_FINE_LOCATIONGeofence + Wi-Fi SSID join.
ACCESS_COARSE_LOCATIONFallback when fine permission denied.
ACCESS_WIFI_STATERead SSID for indoor geofence allowlist.
ACCESS_NETWORK_STATEConnectivity_plus offline-queue trigger.
USE_BIOMETRICFuture: biometric prompt before punch.
iOSNSLocationWhenInUseUsageDescription + URL schemeSame as above; sanchalan-attendance:// for SSO callback.

What's deliberately not in the app

  • Raw IMEI — Android 10+ blocks it for non-system apps. We hash (android_id || fingerprint || serial) with a frontend salt before sending; backend re-salts and stores the digest.
  • Aadhaar AUA/KUA — separate kernel service per /infra/ §17 (sensitive VLAN).
  • Bundled biometric SDK — would bloat APK; rely on platform biometric prompt for the punch action (TODO).
  • Hardcoded backend URL — passed via --dart-define at build time so dev/uat/prod APKs differ only in env.