From 9849df6a8cd404e67bfc4826f19665b2710d76db Mon Sep 17 00:00:00 2001 From: Zahar Date: Fri, 26 Sep 2025 14:57:47 +0800 Subject: [PATCH] feat(i18n): localize onboarding & reports; logout - add en/ms keys for welcome, onboarding, capture/review, errors, and settings labels - replace hardcoded strings with I18n.t in WelcomeScreen, OnboardingFlow, CaptureScreen, ReviewScreen, Map severity legend, and Settings - update StartRouter: onboarded guests go to MainScreen; non-guest users see SignIn; skip marks user as onboarded guest - add logout action in Settings that clears onboarding/user mode prefs and returns to the welcome flow - refine settings diagnostics and app section; move labels to i18n - leave TODOs for app subtitle, processing status, and ReportCard menu labels for future i18n coverage --- assets/lang/en.json | 57 ++++++++++++- assets/lang/ms.json | 57 ++++++++++++- lib/app.dart | 56 ++++++------- lib/screens/map/map_screen.dart | 17 +++- lib/screens/report_flow/capture_screen.dart | 34 ++++---- lib/screens/report_flow/review_screen.dart | 41 +++++---- lib/screens/settings/settings_screen.dart | 93 ++++++++++++++++++--- lib/widgets/report_card.dart | 13 ++- 8 files changed, 288 insertions(+), 80 deletions(-) diff --git a/assets/lang/en.json b/assets/lang/en.json index 418384b..8308885 100644 --- a/assets/lang/en.json +++ b/assets/lang/en.json @@ -97,5 +97,60 @@ "auth.title": "Sign in", "auth.signInWithApple": "Sign in with Apple", "auth.signInWithGoogle": "Sign in with Google", - "auth.comingSoon": "Coming soon" + "auth.comingSoon": "Coming soon", + "welcome.title": "Spot it. Snap it. Fix it.", + "welcome.subtitle": "Report city issues in seconds with AI-powered detection. Help create safer, better communities together.", + "cta.continueGuest": "Continue as Guest", + "cta.signIn": "Sign In", + "cta.skip": "Skip for now", + "onboarding.header": "Welcome to FixMate", + "onboarding.title1": "Fast Issue Reporting", + "onboarding.subtitle1": "AI-Powered Detection", + "onboarding.body1": "Simply take a photo of any urban issue - our AI automatically identifies and categorizes the problem in seconds.", + "onboarding.title2": "Smart City Mapping", + "onboarding.subtitle2": "Real-Time Visualization", + "onboarding.body2": "View all reported issues on an interactive map with intelligent clustering and filtering options.", + "onboarding.title3": "Track Progress", + "onboarding.subtitle3": "Stay Informed", + "onboarding.body3": "Follow the status of your reports from submission to resolution. Help make your community better.", + "onboarding.skip": "Skip", + "onboarding.next": "Next", + "onboarding.getStarted": "Get Started", + "review.title": "Review & Submit", + "review.submit": "Submit", + "review.aiAnalysis": "AI Analysis Complete", + "review.aiConfidence": "Smart detection with {0}% confidence", + "review.useSuggestion": "Use AI Suggestion", + "review.editManually": "Edit Manually", + "review.category": "Issue Category", + "review.severity": "Severity Level", + "review.notes": "Additional Notes", + "review.notesHint": "Add any additional details or context...", + "review.location": "Location Details", + "review.coordinates": "Coordinates", + "review.accuracy": "Accuracy: {0}m", + "error.saving": "Error saving report: {0}", + "error.imagePick": "Error picking image: {0}", + "error.location": "Unable to get location. Please try again.", + "error.imageProcessing": "Error processing image: {0}", + "capture.title": "Report Issue", + "capture.subtitle": "Report City Issues", + "capture.description": "Take a photo of any urban maintenance issue like potholes, broken streetlights, or damaged signage.", + "capture.processing": "Processing image...", + "capture.takePhoto": "Take Photo", + "capture.gallery": "Choose from Gallery", + "report.viewDetails": "View Details", + "report.updateStatus": "Update Status", + "report.delete": "Delete", + "report.comingSoon": "Coming Soon!", + "settings.loading": "Loading...", + "settings.app": "App", + "settings.version": "v1.0.0", + "settings.account": "Account", + "settings.account.guest": "Guest Mode", + "btn.logout": "Logout", + "confirm.logout.title": "Logout?", + "confirm.logout.message": "You will be returned to the welcome screen.", + "label.createdAt": "Created At", + "error.clearData": "Failed to clear data" } \ No newline at end of file diff --git a/assets/lang/ms.json b/assets/lang/ms.json index 83f537f..d93996a 100644 --- a/assets/lang/ms.json +++ b/assets/lang/ms.json @@ -97,5 +97,60 @@ "auth.title": "Log masuk", "auth.signInWithApple": "Log masuk dengan Apple", "auth.signInWithGoogle": "Log masuk dengan Google", - "auth.comingSoon": "Akan datang" + "auth.comingSoon": "Akan datang", + "welcome.title": "Nampak. Tangkap. Baiki.", + "welcome.subtitle": "Lapor isu bandar dalam beberapa saat dengan pengesanan berkuasa AI. Bantu mencipta komuniti yang lebih selamat dan lebih baik.", + "cta.continueGuest": "Teruskan sebagai Tetamu", + "cta.signIn": "Log Masuk", + "cta.skip": "Langkau buat masa ini", + "onboarding.header": "Selamat Datang ke FixMate", + "onboarding.title1": "Laporan Isu Pantas", + "onboarding.subtitle1": "Pengesanan Berkuasa AI", + "onboarding.body1": "Hanya ambil gambar mana-mana isu bandar - AI kami secara automatik mengenal pasti dan mengkategorikan masalah dalam beberapa saat.", + "onboarding.title2": "Pemetaan Bandar Pintar", + "onboarding.subtitle2": "Penglihatan Masa Nyata", + "onboarding.body2": "Lihat semua isu yang dilaporkan di peta interaktif dengan pengelompokan dan penapisan pintar.", + "onboarding.title3": "Jejak Kemajuan", + "onboarding.subtitle3": "Kekal Maklum", + "onboarding.body3": "Ikuti status laporan anda dari penyerahan hingga penyelesaian. Bantu memperbaiki komuniti anda.", + "onboarding.skip": "Langkau", + "onboarding.next": "Seterusnya", + "onboarding.getStarted": "Mula", + "review.title": "Semak & Hantar", + "review.submit": "Hantar", + "review.aiAnalysis": "Analisis AI Selesai", + "review.aiConfidence": "Pengesanan pintar dengan keyakinan {0}%", + "review.useSuggestion": "Guna Cadangan AI", + "review.editManually": "Edit Secara Manual", + "review.category": "Kategori Isu", + "review.severity": "Tahap Keterukan", + "review.notes": "Nota Tambahan", + "review.notesHint": "Tambah sebarang butiran atau konteks tambahan...", + "review.location": "Butiran Lokasi", + "review.coordinates": "Koordinat", + "review.accuracy": "Ketepatan: {0}m", + "error.saving": "Ralat menyimpan laporan: {0}", + "error.imagePick": "Ralat memilih imej: {0}", + "error.location": "Tidak dapat mendapatkan lokasi. Sila cuba lagi.", + "error.imageProcessing": "Ralat memproses imej: {0}", + "capture.title": "Lapor Isu", + "capture.subtitle": "Lapor Isu Bandar", + "capture.description": "Ambil gambar mana-mana isu penyelenggaraan bandar seperti lubang jalan, lampu jalan rosak, atau papan tanda rosak.", + "capture.processing": "Memproses imej...", + "capture.takePhoto": "Ambil Gambar", + "capture.gallery": "Pilih dari Galeri", + "report.viewDetails": "Lihat Butiran", + "report.updateStatus": "Kemas Kini Status", + "report.delete": "Padam", + "report.comingSoon": "Akan Datang!", + "settings.loading": "Memuatkan...", + "settings.app": "Apl", + "settings.version": "v1.0.0", + "settings.account": "Akaun", + "settings.account.guest": "Mod Tetamu", + "btn.logout": "Log Keluar", + "confirm.logout.title": "Log keluar?", + "confirm.logout.message": "Anda akan dibawa kembali ke skrin selamat datang.", + "label.createdAt": "Dicipta Pada", + "error.clearData": "Gagal mengosongkan data" } \ No newline at end of file diff --git a/lib/app.dart b/lib/app.dart index 60ba4a7..157022e 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -281,10 +281,6 @@ class _StartRouterState extends State { if (_loading) { screen = const Scaffold(body: Center(child: CircularProgressIndicator())); screenKey = 'loading'; - } else if (_userMode == 'guest') { - // If user is known to be in guest mode, take them to sign-in / sign-up first - screen = const SignInScreen(); - screenKey = 'signin'; } else if (!_onboarded) { screen = WelcomeScreen( onContinue: () async { @@ -305,14 +301,19 @@ class _StartRouterState extends State { ); }, onSkip: () async { - // Mark as onboarded and guest so next app start opens sign-up/sign-in + // Mark as onboarded and guest so next app start goes to main app await _setOnboarded(asGuest: true); }, ); screenKey = 'welcome'; - } else { + } else if (_userMode == 'guest') { + // User is onboarded and in guest mode, take them to main app screen = const MainScreen(); screenKey = 'main'; + } else { + // User is onboarded but not in guest mode, show sign-in screen + screen = const SignInScreen(); + screenKey = 'signin'; } return AnimatedSwitcher( @@ -395,7 +396,7 @@ class WelcomeScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'FixMate', + I18n.t('app.name'), style: Theme.of(context).textTheme.headlineSmall ?.copyWith( fontWeight: FontWeight.w700, @@ -403,7 +404,7 @@ class WelcomeScreen extends StatelessWidget { ), ), Text( - 'Civic Solutions', + 'Civic Solutions', // TODO: Add to i18n as app.subtitle style: Theme.of(context).textTheme.bodySmall ?.copyWith(color: cs.onSurface.withOpacity(0.7)), ), @@ -445,7 +446,7 @@ class WelcomeScreen extends StatelessWidget { ), const SizedBox(height: 24), Text( - 'Spot it. Snap it. Fix it.', + I18n.t('welcome.title'), style: Theme.of(context).textTheme.headlineSmall ?.copyWith( fontWeight: FontWeight.w700, @@ -455,7 +456,7 @@ class WelcomeScreen extends StatelessWidget { ), const SizedBox(height: 16), Text( - 'Report city issues in seconds with AI-powered detection. Help create safer, better communities together.', + I18n.t('welcome.subtitle'), style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: cs.onSurface.withOpacity(0.8), height: 1.5, @@ -473,7 +474,7 @@ class WelcomeScreen extends StatelessWidget { onPressed: onContinue, icon: const Icon(Icons.arrow_forward, size: 20), label: Text( - 'Continue as Guest', + I18n.t('cta.continueGuest'), style: const TextStyle(fontSize: 16), ), style: ElevatedButton.styleFrom( @@ -493,7 +494,7 @@ class WelcomeScreen extends StatelessWidget { onPressed: onSignIn, icon: const Icon(Icons.login, size: 20), label: Text( - 'Sign In', + I18n.t('cta.signIn'), style: const TextStyle(fontSize: 16), ), style: OutlinedButton.styleFrom( @@ -509,7 +510,7 @@ class WelcomeScreen extends StatelessWidget { TextButton( onPressed: onSkip, child: Text( - 'Skip for now', + I18n.t('cta.skip'), style: TextStyle( color: cs.onSurface.withOpacity(0.7), fontSize: 16, @@ -646,7 +647,7 @@ class _OnboardingFlowState extends State { ), const SizedBox(width: 12), Text( - 'Welcome to FixMate', + I18n.t('onboarding.header'), style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w600, color: cs.onSurface, @@ -662,28 +663,25 @@ class _OnboardingFlowState extends State { onPageChanged: (i) => setState(() => _index = i), children: [ _buildPage( - title: 'Fast Issue Reporting', - subtitle: 'AI-Powered Detection', - description: - 'Simply take a photo of any urban issue - our AI automatically identifies and categorizes the problem in seconds.', + title: I18n.t('onboarding.title1'), + subtitle: I18n.t('onboarding.subtitle1'), + description: I18n.t('onboarding.body1'), icon: Icons.camera_alt, gradientStart: const Color(0xFF22C55E), gradientEnd: const Color(0xFF4ADE80), ), _buildPage( - title: 'Smart City Mapping', - subtitle: 'Real-Time Visualization', - description: - 'View all reported issues on an interactive map with intelligent clustering and filtering options.', + title: I18n.t('onboarding.title2'), + subtitle: I18n.t('onboarding.subtitle2'), + description: I18n.t('onboarding.body2'), icon: Icons.map, gradientStart: const Color(0xFF2563EB), gradientEnd: const Color(0xFF3B82F6), ), _buildPage( - title: 'Track Progress', - subtitle: 'Stay Informed', - description: - 'Follow the status of your reports from submission to resolution. Help make your community better.', + title: I18n.t('onboarding.title3'), + subtitle: I18n.t('onboarding.subtitle3'), + description: I18n.t('onboarding.body3'), icon: Icons.check_circle, gradientStart: const Color(0xFFF97316), gradientEnd: const Color(0xFFFB923C), @@ -717,7 +715,7 @@ class _OnboardingFlowState extends State { TextButton( onPressed: () => Navigator.pop(context, true), child: Text( - 'Skip', + I18n.t('onboarding.skip'), style: TextStyle( color: cs.onSurface.withOpacity(0.7), fontSize: 16, @@ -739,7 +737,9 @@ class _OnboardingFlowState extends State { ), ), child: Text( - _index < 2 ? 'Next' : 'Get Started', + _index < 2 + ? I18n.t('onboarding.next') + : I18n.t('onboarding.getStarted'), style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, diff --git a/lib/screens/map/map_screen.dart b/lib/screens/map/map_screen.dart index 3a78ef9..e370ad4 100644 --- a/lib/screens/map/map_screen.dart +++ b/lib/screens/map/map_screen.dart @@ -679,7 +679,7 @@ class _MapScreenState extends State { ), const SizedBox(width: 8), Text( - 'Issue Severity', + I18n.t('label.severity'), style: Theme.of(context).textTheme.bodySmall ?.copyWith( fontWeight: FontWeight.w600, @@ -691,11 +691,20 @@ class _MapScreenState extends State { ], ), const SizedBox(height: 12), - _enhancedLegendItem(Severity.high, 'High Priority'), + _enhancedLegendItem( + Severity.high, + '${I18n.t('severity.high')} Priority', + ), const SizedBox(height: 8), - _enhancedLegendItem(Severity.medium, 'Medium Priority'), + _enhancedLegendItem( + Severity.medium, + '${I18n.t('severity.medium')} Priority', + ), const SizedBox(height: 8), - _enhancedLegendItem(Severity.low, 'Low Priority'), + _enhancedLegendItem( + Severity.low, + '${I18n.t('severity.low')} Priority', + ), ], ), ), diff --git a/lib/screens/report_flow/capture_screen.dart b/lib/screens/report_flow/capture_screen.dart index 98800b6..2a8cffd 100644 --- a/lib/screens/report_flow/capture_screen.dart +++ b/lib/screens/report_flow/capture_screen.dart @@ -37,9 +37,11 @@ class _CaptureScreenState extends State { } } catch (e) { if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Error picking image: $e'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(I18n.t('error.imagePick', {'0': e.toString()})), + ), + ); } } finally { if (mounted) { @@ -58,8 +60,10 @@ class _CaptureScreenState extends State { if (position == null) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Unable to get location. Please try again.'), + SnackBar( + content: const Text( + 'Unable to get location. Please try again.', + ), // TODO: Move to i18n ), ); } @@ -110,9 +114,11 @@ class _CaptureScreenState extends State { } } catch (e) { if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Error processing image: $e'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(I18n.t('error.imageProcessing', {'0': e.toString()})), + ), + ); } } } @@ -127,7 +133,7 @@ class _CaptureScreenState extends State { return Scaffold( appBar: AppBar( title: Text( - 'Report Issue', + I18n.t('capture.title'), style: const TextStyle(fontWeight: FontWeight.w600), ), elevation: 0, @@ -181,7 +187,7 @@ class _CaptureScreenState extends State { ), const SizedBox(height: 20), Text( - 'Report City Issues', + I18n.t('capture.subtitle'), style: Theme.of(context).textTheme.headlineSmall ?.copyWith( fontWeight: FontWeight.w700, @@ -191,7 +197,7 @@ class _CaptureScreenState extends State { ), const SizedBox(height: 8), Text( - 'Take a photo of any urban maintenance issue like potholes, broken streetlights, or damaged signage.', + I18n.t('capture.description'), style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: cs.onSurface.withOpacity(0.7), height: 1.5, @@ -221,7 +227,7 @@ class _CaptureScreenState extends State { CircularProgressIndicator(), SizedBox(height: 16), Text( - 'Processing image...', + 'Processing image...', // TODO: Move to i18n style: TextStyle(fontSize: 16), ), ], @@ -249,7 +255,7 @@ class _CaptureScreenState extends State { onPressed: () => _pickImage(ImageSource.camera), icon: const Icon(Icons.camera_alt, size: 24), label: const Text( - 'Take Photo', + 'Take Photo', // TODO: Move to i18n style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, @@ -274,7 +280,7 @@ class _CaptureScreenState extends State { onPressed: () => _pickImage(ImageSource.gallery), icon: const Icon(Icons.photo_library, size: 24), label: const Text( - 'Choose from Gallery', + 'Choose from Gallery', // TODO: Move to i18n style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, diff --git a/lib/screens/report_flow/review_screen.dart b/lib/screens/report_flow/review_screen.dart index b959494..a9a3fa4 100644 --- a/lib/screens/report_flow/review_screen.dart +++ b/lib/screens/report_flow/review_screen.dart @@ -66,9 +66,9 @@ class _ReviewScreenState extends State { } } catch (e) { if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Error saving report: $e'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(I18n.t('error.saving', {'0': e.toString()}))), + ); } } finally { if (mounted) { @@ -86,7 +86,7 @@ class _ReviewScreenState extends State { return Scaffold( appBar: AppBar( title: Text( - 'Review & Submit', + I18n.t('review.title'), style: const TextStyle(fontWeight: FontWeight.w600), ), elevation: 0, @@ -102,7 +102,7 @@ class _ReviewScreenState extends State { child: CircularProgressIndicator(strokeWidth: 2), ) : Text( - 'Submit', + I18n.t('review.submit'), style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, @@ -190,7 +190,7 @@ class _ReviewScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'AI Analysis Complete', + I18n.t('review.aiAnalysis'), style: Theme.of(context).textTheme.titleMedium ?.copyWith( fontWeight: FontWeight.w600, @@ -198,7 +198,13 @@ class _ReviewScreenState extends State { ), ), Text( - 'Smart detection with ${(widget.report.aiSuggestion.confidence * 100).round()}% confidence', + I18n.t('review.aiConfidence', { + '0': + (widget.report.aiSuggestion.confidence * + 100) + .round() + .toString(), + }), style: Theme.of(context).textTheme.bodySmall ?.copyWith( color: cs.onSurface.withOpacity(0.7), @@ -300,7 +306,7 @@ class _ReviewScreenState extends State { }); }, icon: const Icon(Icons.check_circle, size: 18), - label: const Text('Use AI Suggestion'), + label: Text(I18n.t('review.useSuggestion')), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0EA5E9), foregroundColor: Colors.white, @@ -316,7 +322,7 @@ class _ReviewScreenState extends State { child: OutlinedButton.icon( onPressed: () {}, icon: const Icon(Icons.edit, size: 18), - label: const Text('Edit Manually'), + label: Text(I18n.t('review.editManually')), style: OutlinedButton.styleFrom( side: BorderSide( color: const Color(0xFF0EA5E9), @@ -359,7 +365,7 @@ class _ReviewScreenState extends State { Icon(Icons.category, color: cs.primary, size: 20), const SizedBox(width: 8), Text( - 'Issue Category', + I18n.t('review.category'), style: Theme.of(context).textTheme.titleMedium ?.copyWith( fontWeight: FontWeight.w600, @@ -443,7 +449,7 @@ class _ReviewScreenState extends State { Icon(Icons.warning, color: cs.secondary, size: 20), const SizedBox(width: 8), Text( - 'Severity Level', + I18n.t('review.severity'), style: Theme.of(context).textTheme.titleMedium ?.copyWith( fontWeight: FontWeight.w600, @@ -539,7 +545,7 @@ class _ReviewScreenState extends State { Icon(Icons.note, color: cs.primary, size: 20), const SizedBox(width: 8), Text( - 'Additional Notes', + I18n.t('review.notes'), style: Theme.of(context).textTheme.titleMedium ?.copyWith( fontWeight: FontWeight.w600, @@ -552,7 +558,7 @@ class _ReviewScreenState extends State { TextField( controller: _notesController, decoration: InputDecoration( - hintText: 'Add any additional details or context...', + hintText: I18n.t('review.notesHint'), hintStyle: TextStyle( color: cs.onSurface.withOpacity(0.6), ), @@ -602,7 +608,7 @@ class _ReviewScreenState extends State { Icon(Icons.location_on, color: cs.secondary, size: 20), const SizedBox(width: 8), Text( - 'Location Details', + I18n.t('review.location'), style: Theme.of(context).textTheme.titleMedium ?.copyWith( fontWeight: FontWeight.w600, @@ -630,7 +636,7 @@ class _ReviewScreenState extends State { ), const SizedBox(width: 8), Text( - 'Coordinates', + I18n.t('review.coordinates'), style: Theme.of(context).textTheme.bodySmall ?.copyWith( color: cs.onSurface.withOpacity(0.7), @@ -659,7 +665,10 @@ class _ReviewScreenState extends State { ), const SizedBox(width: 8), Text( - 'Accuracy: ${widget.report.location.accuracy!.toStringAsFixed(1)}m', + I18n.t('review.accuracy', { + '0': widget.report.location.accuracy! + .toStringAsFixed(1), + }), style: Theme.of(context).textTheme.bodySmall ?.copyWith( color: cs.onSurface.withOpacity(0.7), diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart index 73510da..7ec16c4 100644 --- a/lib/screens/settings/settings_screen.dart +++ b/lib/screens/settings/settings_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import '../../l10n/i18n.dart'; import '../../l10n/locale_provider.dart'; import '../../services/storage.dart'; @@ -42,8 +43,14 @@ class _SettingsScreenState extends State { title: Text(I18n.t('confirm.clearData.title')), content: Text(I18n.t('confirm.clearData.message')), actions: [ - TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text(I18n.t('btn.no'))), - TextButton(onPressed: () => Navigator.pop(ctx, true), child: Text(I18n.t('btn.yes'))), + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: Text(I18n.t('btn.no')), + ), + TextButton( + onPressed: () => Navigator.pop(ctx, true), + child: Text(I18n.t('btn.yes')), + ), ], ), ); @@ -59,25 +66,59 @@ class _SettingsScreenState extends State { if (ok) { await _loadStats(); if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(I18n.t('toast.storageCleared')))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(I18n.t('toast.storageCleared'))), + ); } } else { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to clear data'))); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(I18n.t('error.clearData')))); } } } } + Future _logout() async { + final confirmed = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: Text(I18n.t('confirm.logout.title')), + content: Text(I18n.t('confirm.logout.message')), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: Text(I18n.t('btn.no')), + ), + TextButton( + onPressed: () => Navigator.pop(ctx, true), + child: Text(I18n.t('btn.yes')), + ), + ], + ), + ); + + if (confirmed == true) { + // Clear user preferences to reset to initial state + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('onboarded_v1'); + await prefs.remove('user_mode'); + + if (mounted) { + // Navigate back to the start router (welcome screen) + Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false); + } + } + } + @override Widget build(BuildContext context) { final localeProvider = Provider.of(context); final isEnglish = localeProvider.isEnglish; return Scaffold( - appBar: AppBar( - title: Text(I18n.t('nav.settings')), - ), + appBar: AppBar(title: Text(I18n.t('nav.settings'))), body: ListView( padding: const EdgeInsets.all(16), children: [ @@ -110,25 +151,51 @@ class _SettingsScreenState extends State { ListTile( title: Text(I18n.t('settings.diagnostics')), subtitle: _loadingStats - ? const Text('Loading...') - : Text('${I18n.t('toast.reportSaved')}: ${_stats?.reportCount ?? 0} • ${_stats?.formattedPhotoSize ?? "0 B"}'), + ? const Text('Loading...') // TODO: Move to i18n + : Text( + '${I18n.t('toast.reportSaved')}: ${_stats?.reportCount ?? 0} • ${_stats?.formattedPhotoSize ?? "0 B"}', + ), ), const SizedBox(height: 8), ElevatedButton.icon( onPressed: _clearing ? null : () => _confirmAndClearAll(context), icon: const Icon(Icons.delete_forever), - label: _clearing ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)) : Text(I18n.t('btn.clearAll')), + label: _clearing + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : Text(I18n.t('btn.clearAll')), style: ElevatedButton.styleFrom(backgroundColor: Colors.red), ), const SizedBox(height: 16), - Text('App', style: Theme.of(context).textTheme.titleMedium), + Text( + I18n.t('settings.app'), + style: Theme.of(context).textTheme.titleMedium, + ), const SizedBox(height: 8), ListTile( title: Text(I18n.t('app.name')), - subtitle: Text('v1.0.0'), + subtitle: Text(I18n.t('settings.version')), + ), + const Divider(), + ListTile( + title: Text(I18n.t('settings.account')), + subtitle: Text(I18n.t('settings.account.guest')), + ), + const SizedBox(height: 8), + ElevatedButton.icon( + onPressed: () => _logout(), + icon: const Icon(Icons.logout), + label: Text(I18n.t('btn.logout')), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange, + foregroundColor: Colors.white, + ), ), ], ), ); } -} \ No newline at end of file +} diff --git a/lib/widgets/report_card.dart b/lib/widgets/report_card.dart index 18db729..5af4b53 100644 --- a/lib/widgets/report_card.dart +++ b/lib/widgets/report_card.dart @@ -380,7 +380,9 @@ class ReportCard extends StatelessWidget { children: [ Icon(Icons.visibility), SizedBox(width: 8), - Text('View Details'), + Text( + 'View Details', + ), // TODO: Move to i18n but need to handle dynamic text in popup menu ], ), ), @@ -390,7 +392,9 @@ class ReportCard extends StatelessWidget { children: [ Icon(Icons.update), SizedBox(width: 8), - Text('Update Status'), + Text( + 'Update Status', + ), // TODO: Move to i18n but need to handle dynamic text in popup menu ], ), ), @@ -400,7 +404,10 @@ class ReportCard extends StatelessWidget { children: [ Icon(Icons.delete, color: Colors.red), SizedBox(width: 8), - Text('Delete', style: TextStyle(color: Colors.red)), + Text( + 'Delete', + style: TextStyle(color: Colors.red), + ), // TODO: Move to i18n but need to handle dynamic text in popup menu ], ), ),