Skip to content
Merged
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
3 changes: 2 additions & 1 deletion backend/grumphp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ grumphp:
tasks:
git_commit_message:
enforce_capitalized_subject: false
max_subject_width: 72
max_subject_width: 120
max_body_width: 0
type_scope_conventions:
- types:
- build
Expand Down
72 changes: 72 additions & 0 deletions caregiver_app/lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'app_localizations_en.dart';
import 'app_localizations_fr.dart';

abstract class AppLocalizations {
static AppLocalizations of(BuildContext context) =>
Localizations.of<AppLocalizations>(context, AppLocalizations)!;

static AppLocalizations forLocale(Locale locale) {
return switch (locale.languageCode) {
'fr' => AppLocalizationsFr(),
_ => AppLocalizationsEn(),
};
}

static const delegate = _AppLocalizationsDelegate();

static const supportedLocales = [Locale('en'), Locale('fr')];

// ── Generic ───────────────────────────────────────────────────────────────
String get appTitle;

// ── Home ──────────────────────────────────────────────────────────────────
String get statusLinkedTitle;
String get statusUnlinkedTitle;
String get statusLinkedBody;
String get statusUnlinkedBody;
String get linkedSnackbar;
String get linkButton;
String get statusCardTitle;
String get howItWorksTitle;
String get statusCardBody;
String get howItWorksBody;
String get importantTitle;
String get importantBody;
String get homeFootnote;

// ── Active Alert ──────────────────────────────────────────────────────────
String get fallDetectedTitle;
String detectedAt(String time);
String get locationTitle;
String get alertIdTitle;
String get acknowledge;

// ── Link ──────────────────────────────────────────────────────────────────
String get linkScreenTitle;
String get enterInviteCodeTitle;
String get inviteCodeInstructions;
String get codeFieldLabel;
String get codeFieldValidation;
String get codeNotFound;
String inviteFailed(int code);
String get connectionError;
String get linkAsCaregiverButton;
}

class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();

@override
bool isSupported(Locale locale) => AppLocalizations.supportedLocales.any(
(l) => l.languageCode == locale.languageCode,
);

@override
Future<AppLocalizations> load(Locale locale) async =>
AppLocalizations.forLocale(locale);

@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
79 changes: 79 additions & 0 deletions caregiver_app/lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'app_localizations.dart';

class AppLocalizationsEn extends AppLocalizations {
// ── Generic ───────────────────────────────────────────────────────────────
@override
String get appTitle => 'Fall Guardian Caregiver';

// ── Home ──────────────────────────────────────────────────────────────────
@override
String get statusLinkedTitle => 'Monitoring Active';
@override
String get statusUnlinkedTitle => 'Not Linked Yet';
@override
String get statusLinkedBody =>
'You will receive push alerts if a fall is detected on the protected person\'s device.';
@override
String get statusUnlinkedBody =>
'Link with a protected person to start receiving fall alerts.';
@override
String get linkedSnackbar =>
'Linked successfully! You will now receive fall alerts.';
@override
String get linkButton => 'Link with Protected Person';
@override
String get statusCardTitle => 'Status';
@override
String get howItWorksTitle => 'How it works';
@override
String get statusCardBody =>
'Push notifications are active. Keep this app installed.';
@override
String get howItWorksBody =>
'1. Ask the protected person to generate a code in their Fall Guardian app.\n'
'2. Tap "Link" above and enter the code.\n'
'3. You\'ll receive push alerts on every detected fall.';
@override
String get importantTitle => 'Important';
@override
String get importantBody =>
'Keep notifications enabled for this app. Fall alerts are delivered as '
'data-only messages — your phone must be on and connected.';
@override
String get homeFootnote =>
'Separate apps keep the protected-person and caregiver flows cleaner, '
'safer, and easier to maintain.';

// ── Active Alert ──────────────────────────────────────────────────────────
@override
String get fallDetectedTitle => 'FALL DETECTED';
@override
String detectedAt(String time) => 'Detected at $time';
@override
String get locationTitle => 'Location';
@override
String get alertIdTitle => 'Alert ID';
@override
String get acknowledge => 'Acknowledge';

// ── Link ──────────────────────────────────────────────────────────────────
@override
String get linkScreenTitle => 'Link with Protected Person';
@override
String get enterInviteCodeTitle => 'Enter Invite Code';
@override
String get inviteCodeInstructions =>
'Ask the protected person to generate a code in their Fall Guardian app.';
@override
String get codeFieldLabel => '8-character code';
@override
String get codeFieldValidation => 'Enter the full 8-character code';
@override
String get codeNotFound => 'Code not found or expired. Ask for a new code.';
@override
String inviteFailed(int code) => 'Failed to accept invite ($code).';
@override
String get connectionError => 'Connection error. Check the backend.';
@override
String get linkAsCaregiverButton => 'Link as Caregiver';
}
80 changes: 80 additions & 0 deletions caregiver_app/lib/l10n/app_localizations_fr.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import 'app_localizations.dart';

class AppLocalizationsFr extends AppLocalizations {
// ── Generic ───────────────────────────────────────────────────────────────
@override
String get appTitle => 'Fall Guardian Aidant';

// ── Home ──────────────────────────────────────────────────────────────────
@override
String get statusLinkedTitle => 'Surveillance active';
@override
String get statusUnlinkedTitle => 'Non lié';
@override
String get statusLinkedBody =>
'Vous recevrez des alertes si une chute est détectée sur l\'appareil de la personne protégée.';
@override
String get statusUnlinkedBody =>
'Liez-vous à une personne protégée pour commencer à recevoir les alertes de chute.';
@override
String get linkedSnackbar =>
'Lien établi ! Vous recevrez désormais les alertes de chute.';
@override
String get linkButton => 'Se lier à une personne protégée';
@override
String get statusCardTitle => 'Statut';
@override
String get howItWorksTitle => 'Comment ça fonctionne';
@override
String get statusCardBody =>
'Les notifications push sont actives. Gardez cette application installée.';
@override
String get howItWorksBody =>
'1. Demandez à la personne protégée de générer un code dans son application Fall Guardian.\n'
'2. Appuyez sur "Se lier" ci-dessus et entrez le code.\n'
'3. Vous recevrez des alertes push à chaque chute détectée.';
@override
String get importantTitle => 'Important';
@override
String get importantBody =>
'Gardez les notifications activées pour cette application. Les alertes de chute sont '
'envoyées comme messages silencieux — votre téléphone doit être allumé et connecté.';
@override
String get homeFootnote =>
'Des applications séparées rendent les flux aidé/aidant plus clairs, plus sûrs et plus faciles à maintenir.';

// ── Active Alert ──────────────────────────────────────────────────────────
@override
String get fallDetectedTitle => 'CHUTE DÉTECTÉE';
@override
String detectedAt(String time) => 'Détectée à $time';
@override
String get locationTitle => 'Position';
@override
String get alertIdTitle => 'ID alerte';
@override
String get acknowledge => 'Acquitter';

// ── Link ──────────────────────────────────────────────────────────────────
@override
String get linkScreenTitle => 'Se lier à une personne protégée';
@override
String get enterInviteCodeTitle => 'Entrer le code d\'invitation';
@override
String get inviteCodeInstructions =>
'Demandez à la personne protégée de générer un code dans son application Fall Guardian.';
@override
String get codeFieldLabel => 'Code à 8 caractères';
@override
String get codeFieldValidation => 'Entrez le code complet à 8 caractères';
@override
String get codeNotFound =>
'Code introuvable ou expiré. Demandez un nouveau code.';
@override
String inviteFailed(int code) => 'Échec de l\'invitation ($code).';
@override
String get connectionError =>
'Erreur de connexion. Vérifiez le backend.';
@override
String get linkAsCaregiverButton => 'Se lier en tant qu\'aidant';
}
16 changes: 15 additions & 1 deletion caregiver_app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ import 'dart:developer' as developer;

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

import 'l10n/app_localizations.dart';
import 'screens/active_alert_screen.dart';
import 'screens/home_screen.dart';
import 'services/caregiver_backend_service.dart';
import 'services/push_notification_service.dart';

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
try {
await Firebase.initializeApp();
} catch (e) {
developer.log('Firebase init skipped (no config): $e', name: 'main');
}
runApp(const CaregiverApp());
}

Expand All @@ -22,6 +28,14 @@ class CaregiverApp extends StatelessWidget {
return MaterialApp(
title: 'Fall Guardian Caregiver',
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
locale: null,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF2D6A4F),
Expand Down
28 changes: 18 additions & 10 deletions caregiver_app/lib/screens/active_alert_screen.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:developer' as developer;

import 'package:flutter/material.dart';
import '../l10n/app_localizations.dart';
import '../services/caregiver_backend_service.dart';

/// Full-screen alert shown when a fall notification is received.
Expand All @@ -25,7 +26,8 @@ class _ActiveAlertScreenState extends State<ActiveAlertScreen> {
bool _acknowledging = false;

String get _alertId => widget.alertData['alertId'] as String? ?? '';
String get _fallTimestamp => widget.alertData['fallTimestamp'] as String? ?? '';
String get _fallTimestamp =>
widget.alertData['fallTimestamp'] as String? ?? '';
String? get _latitude => widget.alertData['latitude'] as String?;
String? get _longitude => widget.alertData['longitude'] as String?;

Expand All @@ -48,13 +50,18 @@ class _ActiveAlertScreenState extends State<ActiveAlertScreen> {
await _api.acknowledgeFallAlert(_alertId);
}
} catch (e) {
developer.log('Failed to acknowledge alert: $e', name: '_ActiveAlertScreenState');
developer.log(
'Failed to acknowledge alert: $e',
name: '_ActiveAlertScreenState',
);
}
if (mounted) widget.onDismiss();
}

@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);

return Scaffold(
backgroundColor: const Color(0xFFB00020),
body: SafeArea(
Expand All @@ -70,10 +77,10 @@ class _ActiveAlertScreenState extends State<ActiveAlertScreen> {
size: 96,
),
const SizedBox(height: 24),
const Text(
'FALL DETECTED',
Text(
l10n.fallDetectedTitle,
textAlign: TextAlign.center,
style: TextStyle(
style: const TextStyle(
color: Colors.white,
fontSize: 32,
fontWeight: FontWeight.w900,
Expand All @@ -82,22 +89,22 @@ class _ActiveAlertScreenState extends State<ActiveAlertScreen> {
),
const SizedBox(height: 12),
Text(
'Detected at $_formattedTime',
l10n.detectedAt(_formattedTime),
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white70, fontSize: 18),
),
const SizedBox(height: 32),
if (_hasLocation) ...[
_InfoCard(
icon: Icons.location_on,
title: 'Location',
title: l10n.locationTitle,
body: 'Lat: $_latitude\nLng: $_longitude',
),
const SizedBox(height: 16),
],
_InfoCard(
icon: Icons.info_outline,
title: 'Alert ID',
title: l10n.alertIdTitle,
body: _alertId,
),
const Spacer(),
Expand All @@ -113,7 +120,7 @@ class _ActiveAlertScreenState extends State<ActiveAlertScreen> {
),
)
: const Icon(Icons.check_circle_outline),
label: const Text('Acknowledge'),
label: Text(l10n.acknowledge),
style: FilledButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: const Color(0xFFB00020),
Expand Down Expand Up @@ -171,7 +178,8 @@ class _InfoCard extends StatelessWidget {
const SizedBox(height: 4),
Text(
body,
style: const TextStyle(color: Colors.white70, fontSize: 13),
style:
const TextStyle(color: Colors.white70, fontSize: 13),
),
],
),
Expand Down
Loading
Loading