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
This commit is contained in:
@@ -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<SettingsScreen> {
|
||||
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<SettingsScreen> {
|
||||
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<void> _logout() async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
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<LocaleProvider>(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<SettingsScreen> {
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user