Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/config/map_tile_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ class MapTileConfig {
static List<String> get effectiveSubdomains {
final raw = dotenv.maybeGet('MAP_TILE_SUBDOMAINS')?.trim();
if (raw != null && raw.isNotEmpty) {
return raw.split(',').map((s) => s.trim()).where((s) => s.isNotEmpty).toList();
return raw
.split(',')
.map((s) => s.trim())
.where((s) => s.isNotEmpty)
.toList();
}
final template = effectiveUrlTemplate;
if (template.contains('{s}') &&
Expand Down
4 changes: 3 additions & 1 deletion lib/config/runtime_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ class RuntimeConfig {
case 'SMS_DISPATCH_ANON_KEY':
return const String.fromEnvironment('SMS_DISPATCH_ANON_KEY');
case 'SMS_RELAY_COUNTS_AS_PRIMARY_DISPATCH':
return const String.fromEnvironment('SMS_RELAY_COUNTS_AS_PRIMARY_DISPATCH');
return const String.fromEnvironment(
'SMS_RELAY_COUNTS_AS_PRIMARY_DISPATCH',
);
case 'INDIA_SOS_DISPATCH_URL':
return const String.fromEnvironment('INDIA_SOS_DISPATCH_URL');
case 'INDIA_ERSS_API_URL':
Expand Down
6 changes: 5 additions & 1 deletion lib/database/app_database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ Future<void> ensureSupabaseAnonymousSession(SupabaseClient client) async {
return;
}
} catch (e, st) {
appLog.w('Session refresh failed; re-authenticating', error: e, stackTrace: st);
appLog.w(
'Session refresh failed; re-authenticating',
error: e,
stackTrace: st,
);
}
}
await client.auth.signInAnonymously();
Expand Down
28 changes: 21 additions & 7 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ void main() async {
await RuntimeConfig.bootstrap();
} catch (e, st) {
// Non-fatal: all services check for missing config and degrade gracefully.
appLog.w('[boot] RuntimeConfig.bootstrap() failed — proceeding without config', error: e, stackTrace: st);
appLog.w(
'[boot] RuntimeConfig.bootstrap() failed — proceeding without config',
error: e,
stackTrace: st,
);
}

// ← First frame renders here. The loading spinner in _RoadSOSAppState is
Expand Down Expand Up @@ -170,8 +174,9 @@ class _RoadSOSAppState extends ConsumerState<RoadSOSApp>
await Future.wait<void>([
initializeFirstAidRepository(),
initializeFmtcMapCache(),
EmergencyBackgroundService.initialize()
.then((_) => EmergencyBackgroundService.ensureNotificationChannel()),
EmergencyBackgroundService.initialize().then(
(_) => EmergencyBackgroundService.ensureNotificationChannel(),
),
]);

// Phase 4: Kick off remote crash-config fetch (non-blocking).
Expand All @@ -184,7 +189,11 @@ class _RoadSOSAppState extends ConsumerState<RoadSOSApp>
appLog.i('[boot] All services bootstrapped successfully.');
} catch (e, st) {
// Non-fatal: app runs in offline/degraded mode.
appLog.e('[boot] Service bootstrap error — running in degraded mode', error: e, stackTrace: st);
appLog.e(
'[boot] Service bootstrap error — running in degraded mode',
error: e,
stackTrace: st,
);
} finally {
if (mounted) {
setState(() => _servicesReady = true);
Expand Down Expand Up @@ -222,8 +231,9 @@ class _RoadSOSAppState extends ConsumerState<RoadSOSApp>
ref.watch(inactivityCrashDetectorProvider);
ref.watch(sosLocationTrackerProvider);

final sosPhase =
ref.watch(emergencyOrchestratorProvider.select((s) => s.phase));
final sosPhase = ref.watch(
emergencyOrchestratorProvider.select((s) => s.phase),
);
final appLocale = ref.watch(appLocaleProvider);

ref.listen(appLocaleProvider, (_, next) {
Expand Down Expand Up @@ -334,7 +344,11 @@ class _LogoMark extends StatelessWidget {
color: const Color(0xFFE8281A),
borderRadius: BorderRadius.circular(18),
),
child: const Icon(Icons.emergency_share, color: Colors.white, size: 40),
child: const Icon(
Icons.emergency_share,
color: Colors.white,
size: 40,
),
),
const SizedBox(height: 16),
const Text(
Expand Down
19 changes: 7 additions & 12 deletions lib/models/dispatch_channel_status.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
/// Lifecycle of one emergency dispatch channel shown in honest status UI.
enum DispatchChannelLifecycle {
pending,
inProgress,
success,
failed,
skipped,
}
enum DispatchChannelLifecycle { pending, inProgress, success, failed, skipped }

/// One row in the dispatch confirmation list (SMS, mesh, cloud, etc.).
class DispatchChannelRow {
final String id;
final String title;
final DispatchChannelLifecycle lifecycle;

/// Short line for accessibility and panic readability (WCAG-minded contrast in UI).
final String detail;

Expand All @@ -35,11 +30,11 @@ class DispatchChannelRow {
}

Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'lifecycle': lifecycle.name,
'detail': detail,
};
'id': id,
'title': title,
'lifecycle': lifecycle.name,
'detail': detail,
};

DispatchChannelRow copyWith({
DispatchChannelLifecycle? lifecycle,
Expand Down
1 change: 1 addition & 0 deletions lib/models/facility.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Facility {
final double longitude;
final String? contactNumber;
final String? capabilities;

/// gov_nhm | gov_ayushman | osm | merged
final String? dataSource;

Expand Down
26 changes: 13 additions & 13 deletions lib/models/sos_activity_record.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,19 @@ class SosActivityRecord {
}

Map<String, dynamic> toJson() => {
'incident_id': incidentId,
'completed_at': completedAtUtc.toIso8601String(),
'latitude': latitude,
'longitude': longitude,
'accuracy_m': accuracyM,
'location_source': locationSource,
'triage_severity': triageSeverity,
'triage_source': triageSourceName,
'required_services': requiredServices,
'channels': channels.map((e) => e.toJson()).toList(),
'sync_status': syncStatusLine,
'is_bystander': isBystander,
};
'incident_id': incidentId,
'completed_at': completedAtUtc.toIso8601String(),
'latitude': latitude,
'longitude': longitude,
'accuracy_m': accuracyM,
'location_source': locationSource,
'triage_severity': triageSeverity,
'triage_source': triageSourceName,
'required_services': requiredServices,
'channels': channels.map((e) => e.toJson()).toList(),
'sync_status': syncStatusLine,
'is_bystander': isBystander,
};

String formattedGpsIndia() =>
'${latitude.toStringAsFixed(6)}, ${longitude.toStringAsFixed(6)} '
Expand Down
22 changes: 12 additions & 10 deletions lib/services/agent_health_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@ class AgentHealthSnapshot {
AgentReadiness? sms,
AgentReadiness? ble,
}) => AgentHealthSnapshot(
gemmaCloud: gemmaCloud ?? this.gemmaCloud,
gemmaOnDevice: gemmaOnDevice ?? this.gemmaOnDevice,
gps: gps ?? this.gps,
sms: sms ?? this.sms,
ble: ble ?? this.ble,
checkedAt: DateTime.now(),
);
gemmaCloud: gemmaCloud ?? this.gemmaCloud,
gemmaOnDevice: gemmaOnDevice ?? this.gemmaOnDevice,
gps: gps ?? this.gps,
sms: sms ?? this.sms,
ble: ble ?? this.ble,
checkedAt: DateTime.now(),
);
}

class AgentHealthService {
Expand Down Expand Up @@ -135,9 +135,9 @@ class AgentHealthService {
Future<AgentReadiness> _checkGemmaCloud() async {
final connectivity = _ref.read(connectivityServiceProvider);
return switch (connectivity.currentQuality) {
NetworkQuality.wifi => AgentReadiness.ready,
NetworkQuality.wifi => AgentReadiness.ready,
NetworkQuality.cellular => AgentReadiness.ready,
NetworkQuality.none => AgentReadiness.unavailable,
NetworkQuality.none => AgentReadiness.unavailable,
};
}

Expand Down Expand Up @@ -168,7 +168,9 @@ class AgentHealthService {
// Primary: server relay (Twilio / Edge) — no Android SEND_SMS required.
final relayUrl = dotenv.env['SMS_DISPATCH_URL']?.trim() ?? '';
final relayKey = dotenv.env['SMS_DISPATCH_ANON_KEY']?.trim() ?? '';
if (relayUrl.isNotEmpty && relayKey.isNotEmpty) return AgentReadiness.ready;
if (relayUrl.isNotEmpty && relayKey.isNotEmpty) {
return AgentReadiness.ready;
}

// Fallback: open SMS app intent (no permission). If relay isn't configured,
// we mark this as degraded (still usable, but requires user interaction).
Expand Down
Loading
Loading