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
4 changes: 2 additions & 2 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@
"blackBoxSnack": "Generating signed black box PDF…",
"dataPrivacyTitle": "Data privacy",
"dataPrivacySubtitle": "Manage local encryption and cloud sync",
"vitalScanTitle": "Vital scan",
"vitalAlignFinger": "Align index finger with rear camera and flash for PPG reading.",
"vitalScanTitle": "Vital entry",
"vitalAlignFinger": "Bystander-entered values are relayed to 108/112 dispatchers. Not a medical device.",
"settingsLanguage": "Language",
"settingsLanguageSubtitle": "UI, triage prompts, and voice output",
"incidentAssistantAnalyzed": "Scene note: frontal impact logged. Check for smoke or fire.",
Expand Down
4 changes: 2 additions & 2 deletions lib/l10n/app_hi.arb
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
"blackBoxSnack": "हस्ताक्षरित PDF बन रहा है…",
"dataPrivacyTitle": "डेटा गोपनीयता",
"dataPrivacySubtitle": "एन्क्रिप्शन और सिंक",
"vitalScanTitle": "वाइटल स्कैन",
"vitalAlignFinger": "PPG के लिए उंगली कैमरे और फ्लैश पर रखें।",
"vitalScanTitle": "वाइटल प्रविष्टि",
"vitalAlignFinger": "दर्शक द्वारा दर्ज मान 108/112 डिस्पैचर को भेजे जाते हैं। यह चिकित्सा उपकरण नहीं है।",
"settingsLanguage": "भाषा",
"settingsLanguageSubtitle": "इंटरफ़ेस, ट्राइएज और आवाज़",
"incidentAssistantAnalyzed": "दृश्य: अगला प्रहार दर्ज। धुआँ या आग जाँचें।",
Expand Down
18 changes: 13 additions & 5 deletions lib/services/ai_triage_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,19 @@ class AiTriageService {
int severityHint = 3,
}) async {
final locationString = '${location.latitude},${location.longitude}';
final ctx = transcript.trim().isEmpty
? (isBystander
? 'Bystander reporting roadside emergency'
: 'Emergency SOS triggered')
: transcript;
String ctx;
if (transcript.trim().isNotEmpty) {
ctx = transcript;
} else if (isBystander) {
ctx = 'Bystander reporting roadside emergency at $locationString. '
'Severity hint from sensors: $severityHint/5. '
'Unknown number of victims. Assess and triage.';
} else {
ctx = 'Automatic crash SOS triggered at $locationString. '
'Accelerometer severity: $severityHint/5. '
'Driver may be incapacitated — no verbal input available. '
'Assume worst case: possible unconscious victim in vehicle.';
}

return triageEmergency(
audioTranscript: ctx,
Expand Down
69 changes: 57 additions & 12 deletions lib/services/emergency_orchestrator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,8 @@ class EmergencyOrchestrator extends StateNotifier<SOSState> {
'mesh',
meshOk ? DispatchChannelLifecycle.success : DispatchChannelLifecycle.failed,
meshOk
? 'Mesh beacon active — nearby app users can detect you ✓'
: 'Mesh did not start — Bluetooth off, unsupported, or failed.',
? 'BLE beacon active — nearby RoadSOS users within ~30m can detect you ✓'
: 'BLE beacon did not start — Bluetooth off, unsupported, or failed.',
);
return meshOk;
});
Expand Down Expand Up @@ -572,7 +572,11 @@ class EmergencyOrchestrator extends StateNotifier<SOSState> {

final nearbyFuture = guard<bool>(
id: 'nearby_services',
future: Future.delayed(const Duration(seconds: 3), () => true),
future: _broadcastToNearbyServices(
incidentId: state.incidentId ?? '',
location: location,
triage: triage,
),
fallback: false,
timeoutDetail: 'Nearby services broadcast timed out.',
failureDetail: 'Nearby services broadcast failed.',
Expand All @@ -581,8 +585,8 @@ class EmergencyOrchestrator extends StateNotifier<SOSState> {
'nearby_services',
ok ? DispatchChannelLifecycle.success : DispatchChannelLifecycle.failed,
ok
? 'Emergency alert broadcasted to nearby facilities and responders ✓'
: 'Could not complete nearby services broadcast.',
? 'Emergency alert sent to Supabase + nearby responders ✓'
: 'Nearby broadcast skipped — no Supabase session or network.',
);
return ok;
});
Expand Down Expand Up @@ -650,13 +654,16 @@ class EmergencyOrchestrator extends StateNotifier<SOSState> {
}

// Phase 7: post-dispatch voice briefing — the driver hears what was sent.
// CRITICAL: tell the driver whether SMS actually succeeded. Never falsely
// reassure an injured person that help is coming when dispatch failed.
if (state.wasInDrivingMode) {
final voice = _ref.read(voiceAssistantServiceProvider);
unawaited(voice.speakTriageSummary(
severity: triage.severityLevel,
services: triage.requiredServices,
locationCoords: '${location.latitude.toStringAsFixed(2)}, '
'${location.longitude.toStringAsFixed(2)}',
smsSucceeded: anyConfirmed,
));
}
}
Expand Down Expand Up @@ -693,7 +700,7 @@ class EmergencyOrchestrator extends StateNotifier<SOSState> {
return const [
DispatchChannelRow(
id: 'mesh',
title: 'Mesh beacon (BLE)',
title: 'BLE beacon (nearby alert)',
lifecycle: DispatchChannelLifecycle.pending,
detail: 'Waiting…',
),
Expand All @@ -717,7 +724,7 @@ class EmergencyOrchestrator extends StateNotifier<SOSState> {
),
DispatchChannelRow(
id: 'nearby_services',
title: 'Nearby Services',
title: 'Nearby services (Supabase)',
lifecycle: DispatchChannelLifecycle.pending,
detail: 'Waiting…',
),
Expand Down Expand Up @@ -799,23 +806,61 @@ class EmergencyOrchestrator extends StateNotifier<SOSState> {
void cancelSOS() => cancelSos();

Future<void> _callEmergencyContact() async {
// Always attempt to dial 112 (India ERSS) first — this is the real
// emergency dispatch path that connects to police/ambulance/fire.
// The user's saved contact is secondary (family notification).
if (!kIsWeb) {
await _dialNumber('112', desc: 'ERSS 112');
}

final profile = _ref.read(userProfileProvider);
final contact = profile.emergencyContact.trim();
if (contact.isEmpty) {
appLog.w('[Orchestrator] No emergency contact found to call.');
appLog.w('[Orchestrator] No personal emergency contact — 112 dialed.');
return;
}

final uri = Uri.parse('tel:$contact');
await _dialNumber(contact, desc: 'emergency contact');
}

Future<void> _dialNumber(String number, {required String desc}) async {
final uri = Uri.parse('tel:$number');
try {
if (await canLaunchUrl(uri)) {
appLog.i('[Orchestrator] Initiating automated call to $contact');
appLog.i('[Orchestrator] Dialing $desc ($number)');
await launchUrl(uri);
} else {
appLog.w('[Orchestrator] Could not launch dialer for $contact');
appLog.w('[Orchestrator] Could not launch dialer for $desc');
}
} catch (e, st) {
appLog.e('[Orchestrator] Error launching dialer', error: e, stackTrace: st);
appLog.e('[Orchestrator] Error dialing $desc', error: e, stackTrace: st);
}
}

Future<bool> _broadcastToNearbyServices({
required String incidentId,
required LocationFix location,
required TriageResult triage,
}) async {
if (!_hasSupabaseSession()) return false;
try {
final client = Supabase.instance.client;
final userId = client.auth.currentUser?.id;
await client.from('sos_broadcasts').insert({
'incident_id': incidentId,
'user_id': userId,
'latitude': location.latitude,
'longitude': location.longitude,
'severity': triage.severityLevel,
'services_needed': triage.requiredServices.join(','),
'status': 'active',
'created_at': DateTime.now().toUtc().toIso8601String(),
});
appLog.i('[Orchestrator] SOS broadcast inserted into Supabase');
return true;
} catch (e, st) {
appLog.w('[Orchestrator] Nearby services broadcast failed', error: e, stackTrace: st);
return false;
}
}

Expand Down
62 changes: 0 additions & 62 deletions lib/services/gemini_http.dart

This file was deleted.

100 changes: 0 additions & 100 deletions lib/services/india_government_crash_contribution_service.dart

This file was deleted.

Loading
Loading