feat: add localisation
This commit is contained in:
@@ -10,6 +10,8 @@ import '../models/settings.dart';
|
||||
import '../utils/app_update_util.dart';
|
||||
import '../viewmodels/pin_lock_view_model.dart';
|
||||
import '../viewmodels/settings_view_model.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../l10n/app_localizations_helpers.dart';
|
||||
|
||||
class SettingsScreenView extends StatefulWidget {
|
||||
const SettingsScreenView({super.key});
|
||||
@@ -58,8 +60,9 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<String?> _promptPin(String title, {String hint = 'Enter PIN (4 digits)'}) {
|
||||
Future<String?> _promptPin(String title, {String? hint}) {
|
||||
final controller = TextEditingController();
|
||||
final l10n = AppLocalizations.of(context);
|
||||
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
@@ -81,12 +84,12 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Cancel'),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, controller.text),
|
||||
child: const Text('Confirm'),
|
||||
child: Text(l10n.confirm),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -101,11 +104,12 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
}
|
||||
|
||||
Future<void> _enablePin() async {
|
||||
final pin = await _promptPin('Set PIN Code', hint: '4 digits');
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final pin = await _promptPin(l10n.setPinCode, hint: l10n.fourDigits);
|
||||
if (pin == null) return;
|
||||
|
||||
if (pin.length != 4) {
|
||||
_showError('PIN must be exactly 4 digits');
|
||||
_showError(l10n.pinExactlyFour);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -113,7 +117,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
final success = await pinLockViewModel.setPin(pin);
|
||||
|
||||
if (!success) {
|
||||
_showError(pinLockViewModel.errorMessage ?? 'Could not set PIN.');
|
||||
_showError(l10n.localizedError(pinLockViewModel.errorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -123,19 +127,20 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
await context.read<SettingsViewModel>().updatePinRequired(true);
|
||||
} catch (e, stack) {
|
||||
Sentry.captureException(e, stackTrace: stack);
|
||||
_showError('Could not save PIN setting.');
|
||||
_showError(l10n.couldNotSavePin);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _changePin() async {
|
||||
final current = await _promptPin('Enter Current PIN');
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final current = await _promptPin(l10n.enterCurrentPin);
|
||||
if (current == null) return;
|
||||
|
||||
final newPin = await _promptPin('Enter New PIN', hint: '4 digits');
|
||||
final newPin = await _promptPin(l10n.enterNewPin, hint: l10n.fourDigits);
|
||||
if (newPin == null) return;
|
||||
|
||||
if (newPin.length != 4) {
|
||||
_showError('PIN must be exactly 4 digits');
|
||||
_showError(l10n.pinExactlyFour);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -146,19 +151,20 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
_showError(pinLockViewModel.errorMessage ?? 'Could not change PIN.');
|
||||
_showError(l10n.localizedError(pinLockViewModel.errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _disablePin() async {
|
||||
final current = await _promptPin('Enter Current PIN to Disable');
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final current = await _promptPin(l10n.enterCurrentPin);
|
||||
if (current == null) return;
|
||||
|
||||
final pinLockViewModel = context.read<PinLockViewModel>();
|
||||
final success = await pinLockViewModel.disablePin(current);
|
||||
|
||||
if (!success) {
|
||||
_showError(pinLockViewModel.errorMessage ?? 'Could not disable PIN.');
|
||||
_showError(l10n.localizedError(pinLockViewModel.errorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -168,12 +174,13 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
await context.read<SettingsViewModel>().updatePinRequired(false);
|
||||
} catch (e, stack) {
|
||||
Sentry.captureException(e, stackTrace: stack);
|
||||
_showError('Could not save PIN setting.');
|
||||
_showError(l10n.couldNotSavePin);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final settingsViewModel = context.watch<SettingsViewModel>();
|
||||
final pinLockViewModel = context.watch<PinLockViewModel>();
|
||||
|
||||
@@ -186,7 +193,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
const Text('Settings'),
|
||||
Text(l10n.settingsTitle),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -209,11 +216,11 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
children: [
|
||||
_SettingsSection(
|
||||
title: 'Security',
|
||||
title: l10n.security,
|
||||
children: [
|
||||
_SettingsSwitchTile(
|
||||
icon: Icons.lock_outline_rounded,
|
||||
title: 'PIN Required',
|
||||
title: l10n.pinRequired,
|
||||
value: settings.pinRequired,
|
||||
onChanged: (value) {
|
||||
if (value) {
|
||||
@@ -226,22 +233,58 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
if (settings.pinRequired)
|
||||
_SettingsTile(
|
||||
icon: Icons.pin_rounded,
|
||||
title: 'Change PIN',
|
||||
title: l10n.changePin,
|
||||
onTap: _changePin,
|
||||
),
|
||||
],
|
||||
),
|
||||
_SettingsSection(
|
||||
title: 'Appearance',
|
||||
title: l10n.appearance,
|
||||
children: [
|
||||
_SettingsTile(
|
||||
icon: Icons.language_rounded,
|
||||
title: l10n.language,
|
||||
subtitle: switch (settings.language) {
|
||||
AppLanguage.system => l10n.languageSystem,
|
||||
AppLanguage.english => l10n.languageEnglish,
|
||||
AppLanguage.dutch => l10n.languageDutch,
|
||||
},
|
||||
onTap: () async {
|
||||
final selected = await showModalBottomSheet<AppLanguage>(
|
||||
context: context,
|
||||
builder: (context) => SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: AppLanguage.values.map((language) {
|
||||
return RadioListTile<AppLanguage>(
|
||||
value: language,
|
||||
groupValue: settings.language,
|
||||
title: Text(switch (language) {
|
||||
AppLanguage.system => l10n.languageSystem,
|
||||
AppLanguage.english => l10n.languageEnglish,
|
||||
AppLanguage.dutch => l10n.languageDutch,
|
||||
}),
|
||||
onChanged: (value) =>
|
||||
Navigator.pop(context, value),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (selected != null) {
|
||||
await settingsViewModel.updateLanguage(selected);
|
||||
}
|
||||
},
|
||||
),
|
||||
_SettingsTile(
|
||||
icon: Icons.brightness_6_rounded,
|
||||
title: 'Theme',
|
||||
title: l10n.theme,
|
||||
subtitle: switch (settings.themeMode) {
|
||||
AppThemeMode.system => 'System',
|
||||
AppThemeMode.light => 'Light',
|
||||
AppThemeMode.dark => 'Dark',
|
||||
AppThemeMode.ugly => 'Ugly',
|
||||
AppThemeMode.system => l10n.system,
|
||||
AppThemeMode.light => l10n.light,
|
||||
AppThemeMode.dark => l10n.dark,
|
||||
AppThemeMode.ugly => l10n.ugly,
|
||||
},
|
||||
onTap: () async {
|
||||
final selected = await showModalBottomSheet<AppThemeMode>(
|
||||
@@ -254,10 +297,10 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
value: mode,
|
||||
groupValue: settings.themeMode,
|
||||
title: Text(switch (mode) {
|
||||
AppThemeMode.system => 'System',
|
||||
AppThemeMode.light => 'Light',
|
||||
AppThemeMode.dark => 'Dark',
|
||||
AppThemeMode.ugly => 'Ugly',
|
||||
AppThemeMode.system => l10n.system,
|
||||
AppThemeMode.light => l10n.light,
|
||||
AppThemeMode.dark => l10n.dark,
|
||||
AppThemeMode.ugly => l10n.ugly,
|
||||
}),
|
||||
onChanged: (value) =>
|
||||
Navigator.pop(context, value),
|
||||
@@ -276,13 +319,13 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
),
|
||||
if (Platform.isAndroid)
|
||||
_SettingsSection(
|
||||
title: 'Updates',
|
||||
title: l10n.updates,
|
||||
children: [
|
||||
_SettingsTile(
|
||||
icon: Icons.system_update_alt_rounded,
|
||||
title: settingsViewModel.checkingForUpdates
|
||||
? 'Checking for updates...'
|
||||
: 'Check for updates',
|
||||
? l10n.checkingForUpdates
|
||||
: l10n.checkForUpdates,
|
||||
onTap: settingsViewModel.checkingForUpdates
|
||||
? null
|
||||
: _checkForUpdates,
|
||||
@@ -294,7 +337,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
onTap: _onVersionTap,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Version $_appVersion',
|
||||
l10n.version(_appVersion),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(
|
||||
context,
|
||||
@@ -313,6 +356,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
|
||||
Future<void> _checkForUpdates() async {
|
||||
if (!Platform.isAndroid) return;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final vm = context.read<SettingsViewModel>();
|
||||
|
||||
try {
|
||||
@@ -321,11 +365,9 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
if (!mounted) return;
|
||||
|
||||
if (update == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('You are already on the latest version.'),
|
||||
),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(l10n.latestVersion)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -336,21 +378,23 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Update check failed: $e')));
|
||||
).showSnackBar(SnackBar(content: Text(l10n.updateCheckFailed)));
|
||||
}
|
||||
}
|
||||
|
||||
void _showUpdateDialog(UpdateInfo update) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: !update.mandatory,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: const Text("Update available"),
|
||||
title: Text(l10n.updateAvailable),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Version ${update.version} is available."),
|
||||
Text(l10n.versionAvailable(update.version)),
|
||||
const SizedBox(height: 12),
|
||||
Text(update.notes),
|
||||
],
|
||||
@@ -359,14 +403,14 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
if (!update.mandatory)
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(dialogContext),
|
||||
child: const Text("Later"),
|
||||
child: Text(l10n.later),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(dialogContext);
|
||||
context.push('/update-progress', extra: update);
|
||||
},
|
||||
child: const Text("Update"),
|
||||
child: Text(l10n.update),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user