Skip to content

Commit 3b11496

Browse files
authored
Merge pull request #138 from MostroP2P/chebizarro/issue67
Create a button to change the language
2 parents 4e47bb1 + f66da7d commit 3b11496

File tree

9 files changed

+186
-8
lines changed

9 files changed

+186
-8
lines changed

lib/core/app.dart

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import 'dart:ui' as ui;
1+
22
import 'package:flutter/material.dart';
33
import 'package:flutter_localizations/flutter_localizations.dart';
44
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -10,6 +10,8 @@ import 'package:mostro_mobile/generated/l10n.dart';
1010
import 'package:mostro_mobile/features/auth/notifiers/auth_state.dart';
1111
import 'package:mostro_mobile/services/lifecycle_manager.dart';
1212
import 'package:mostro_mobile/shared/providers/app_init_provider.dart';
13+
import 'package:mostro_mobile/features/settings/settings_provider.dart';
14+
import 'package:mostro_mobile/shared/notifiers/locale_notifier.dart';
1315

1416
class MostroApp extends ConsumerStatefulWidget {
1517
const MostroApp({super.key});
@@ -47,15 +49,19 @@ class _MostroAppState extends ConsumerState<MostroApp> {
4749
});
4850
});
4951

50-
final systemLocale = ui.PlatformDispatcher.instance.locale;
52+
// Watch both system locale and settings for changes
53+
final systemLocale = ref.watch(systemLocaleProvider);
54+
final settings = ref.watch(settingsProvider);
5155

5256
return MaterialApp.router(
5357
title: 'Mostro',
5458
theme: AppTheme.theme,
5559
darkTheme: AppTheme.theme,
5660
routerConfig: goRouter,
57-
// Force Spanish locale for testing if device is Spanish
58-
locale: systemLocale.languageCode == 'es' ? const Locale('es') : null,
61+
// Use language override from settings if available, otherwise let callback handle detection
62+
locale: settings.selectedLanguage != null
63+
? Locale(settings.selectedLanguage!)
64+
: systemLocale,
5965
localizationsDelegates: const [
6066
S.delegate,
6167
GlobalMaterialLocalizations.delegate,
@@ -64,6 +70,7 @@ class _MostroAppState extends ConsumerState<MostroApp> {
6470
],
6571
supportedLocales: S.supportedLocales,
6672
localeResolutionCallback: (locale, supportedLocales) {
73+
// Use the current system locale from our provider
6774
final deviceLocale = locale ?? systemLocale;
6875

6976
// Check for Spanish language code (es) - includes es_AR, es_ES, etc.
@@ -78,8 +85,8 @@ class _MostroAppState extends ConsumerState<MostroApp> {
7885
}
7986
}
8087

81-
// If no match found, return English as fallback
82-
return const Locale('en');
88+
// If no match found, return Spanish as fallback
89+
return const Locale('es');
8390
},
8491
);
8592
},

lib/features/settings/settings.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,29 @@ class Settings {
33
final List<String> relays;
44
final String mostroPublicKey;
55
final String? defaultFiatCode;
6+
final String? selectedLanguage; // null means use system locale
67

78
Settings({
89
required this.relays,
910
required this.fullPrivacyMode,
1011
required this.mostroPublicKey,
1112
this.defaultFiatCode,
13+
this.selectedLanguage,
1214
});
1315

1416
Settings copyWith({
1517
List<String>? relays,
1618
bool? privacyModeSetting,
1719
String? mostroInstance,
1820
String? defaultFiatCode,
21+
String? selectedLanguage,
1922
}) {
2023
return Settings(
2124
relays: relays ?? this.relays,
2225
fullPrivacyMode: privacyModeSetting ?? fullPrivacyMode,
2326
mostroPublicKey: mostroInstance ?? mostroPublicKey,
2427
defaultFiatCode: defaultFiatCode ?? this.defaultFiatCode,
28+
selectedLanguage: selectedLanguage,
2529
);
2630
}
2731

@@ -30,6 +34,7 @@ class Settings {
3034
'fullPrivacyMode': fullPrivacyMode,
3135
'mostroPublicKey': mostroPublicKey,
3236
'defaultFiatCode': defaultFiatCode,
37+
'selectedLanguage': selectedLanguage,
3338
};
3439

3540
factory Settings.fromJson(Map<String, dynamic> json) {
@@ -38,6 +43,7 @@ class Settings {
3843
fullPrivacyMode: json['fullPrivacyMode'] as bool,
3944
mostroPublicKey: json['mostroPublicKey'],
4045
defaultFiatCode: json['defaultFiatCode'],
46+
selectedLanguage: json['selectedLanguage'],
4147
);
4248
}
4349
}

lib/features/settings/settings_notifier.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class SettingsNotifier extends StateNotifier<Settings> {
1616
relays: Config.nostrRelays,
1717
fullPrivacyMode: Config.fullPrivacyMode,
1818
mostroPublicKey: Config.mostroPubKey,
19+
selectedLanguage: null,
1920
);
2021
}
2122

@@ -52,6 +53,11 @@ class SettingsNotifier extends StateNotifier<Settings> {
5253
await _saveToPrefs();
5354
}
5455

56+
Future<void> updateSelectedLanguage(String? newValue) async {
57+
state = state.copyWith(selectedLanguage: newValue);
58+
await _saveToPrefs();
59+
}
60+
5561
Future<void> _saveToPrefs() async {
5662
final jsonString = jsonEncode(state.toJson());
5763
await _prefs.setString(_storageKey, jsonString);

lib/features/settings/settings_screen.dart

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:mostro_mobile/features/relays/widgets/relay_selector.dart';
77
import 'package:mostro_mobile/features/settings/settings_provider.dart';
88
import 'package:mostro_mobile/shared/widgets/currency_combo_box.dart';
99
import 'package:mostro_mobile/shared/widgets/custom_card.dart';
10+
import 'package:mostro_mobile/shared/widgets/language_selector.dart';
1011
import 'package:mostro_mobile/generated/l10n.dart';
1112

1213
class SettingsScreen extends ConsumerWidget {
@@ -44,7 +45,32 @@ class SettingsScreen extends ConsumerWidget {
4445
crossAxisAlignment: CrossAxisAlignment.start,
4546
spacing: 24,
4647
children: [
47-
// General Settings
48+
// Language Settings
49+
CustomCard(
50+
color: AppTheme.dark2,
51+
padding: const EdgeInsets.all(16),
52+
child: Column(
53+
spacing: 16,
54+
crossAxisAlignment: CrossAxisAlignment.start,
55+
children: [
56+
Row(
57+
spacing: 8,
58+
children: [
59+
const Icon(
60+
Icons.language,
61+
color: AppTheme.mostroGreen,
62+
),
63+
Text(S.of(context)!.language, style: textTheme.titleLarge),
64+
],
65+
),
66+
Text(S.of(context)!.chooseLanguageDescription,
67+
style: textTheme.bodyMedium
68+
?.copyWith(color: AppTheme.grey2)),
69+
const LanguageSelector(),
70+
],
71+
),
72+
),
73+
// Currency Settings
4874
CustomCard(
4975
color: AppTheme.dark2,
5076
padding: const EdgeInsets.all(16),

lib/l10n/intl_en.arb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,5 +399,10 @@
399399
"share": "Share",
400400
"failedToShareInvoice": "Failed to share invoice. Please try copying instead.",
401401
"openWallet": "OPEN WALLET",
402-
"done": "DONE"
402+
"language": "Language",
403+
"systemDefault": "System Default",
404+
"english": "English",
405+
"spanish": "Spanish",
406+
"italian": "Italian",
407+
"chooseLanguageDescription": "Choose your preferred language or use system default"
403408
}

lib/l10n/intl_es.arb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,5 +399,11 @@
399399
"share": "Compartir",
400400
"failedToShareInvoice": "Error al compartir factura. Por favor intenta copiarla en su lugar.",
401401
"openWallet": "ABRIR BILLETERA",
402+
"language": "Idioma",
403+
"systemDefault": "Predeterminado del sistema",
404+
"english": "Inglés",
405+
"spanish": "Español",
406+
"italian": "Italiano",
407+
"chooseLanguageDescription": "Elige tu idioma preferido o usa el predeterminado del sistema",
402408
"done": "HECHO"
403409
}

lib/l10n/intl_it.arb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,5 +399,11 @@
399399
"share": "Condividi",
400400
"failedToShareInvoice": "Errore nel condividere la fattura. Per favore prova a copiarla invece.",
401401
"openWallet": "APRI PORTAFOGLIO",
402+
"language": "Lingua",
403+
"systemDefault": "Predefinito di sistema",
404+
"english": "Inglese",
405+
"spanish": "Spagnolo",
406+
"italian": "Italiano",
407+
"chooseLanguageDescription": "Scegli la tua lingua preferita o usa il predefinito di sistema",
402408
"done": "FATTO"
403409
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import 'dart:ui' as ui;
2+
import 'package:flutter/widgets.dart';
3+
import 'package:flutter_riverpod/flutter_riverpod.dart';
4+
5+
/// Notifier that tracks system locale changes
6+
class LocaleNotifier extends StateNotifier<Locale> {
7+
LocaleNotifier() : super(ui.PlatformDispatcher.instance.locale) {
8+
// Listen to system locale changes
9+
ui.PlatformDispatcher.instance.onLocaleChanged = _onLocaleChanged;
10+
}
11+
12+
void _onLocaleChanged() {
13+
final newLocale = ui.PlatformDispatcher.instance.locale;
14+
if (state != newLocale) {
15+
state = newLocale;
16+
}
17+
}
18+
19+
@override
20+
void dispose() {
21+
// Clean up the listener
22+
ui.PlatformDispatcher.instance.onLocaleChanged = null;
23+
super.dispose();
24+
}
25+
}
26+
27+
/// Provider for system locale changes
28+
final systemLocaleProvider = StateNotifierProvider<LocaleNotifier, Locale>((ref) {
29+
return LocaleNotifier();
30+
});
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_riverpod/flutter_riverpod.dart';
3+
import 'package:mostro_mobile/core/app_theme.dart';
4+
import 'package:mostro_mobile/features/settings/settings_provider.dart';
5+
import 'package:mostro_mobile/generated/l10n.dart';
6+
7+
class LanguageSelector extends ConsumerWidget {
8+
const LanguageSelector({super.key});
9+
10+
static const Map<String?, String> _languageKeys = {
11+
null: 'systemDefault',
12+
'en': 'english',
13+
'es': 'spanish',
14+
'it': 'italian',
15+
};
16+
17+
@override
18+
Widget build(BuildContext context, WidgetRef ref) {
19+
final settings = ref.watch(settingsProvider);
20+
final currentLanguage = settings.selectedLanguage;
21+
22+
return Container(
23+
decoration: BoxDecoration(
24+
color: AppTheme.dark1,
25+
borderRadius: BorderRadius.circular(8),
26+
),
27+
child: DropdownButtonHideUnderline(
28+
child: DropdownButton<String?>(
29+
value: currentLanguage,
30+
isExpanded: true,
31+
dropdownColor: AppTheme.dark1,
32+
style: const TextStyle(color: AppTheme.cream1),
33+
icon: const Icon(Icons.arrow_drop_down, color: AppTheme.cream1),
34+
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
35+
items: _languageKeys.entries.map((entry) {
36+
final languageCode = entry.key;
37+
final languageKey = entry.value;
38+
39+
final displayName = _getLocalizedLanguageName(context, languageKey);
40+
41+
return DropdownMenuItem<String?>(
42+
value: languageCode,
43+
child: Row(
44+
children: [
45+
Icon(
46+
languageCode == null ? Icons.phone_android : Icons.language,
47+
color: AppTheme.mostroGreen,
48+
size: 20,
49+
),
50+
const SizedBox(width: 12),
51+
Text(
52+
displayName,
53+
style: const TextStyle(
54+
color: AppTheme.cream1,
55+
fontSize: 16,
56+
),
57+
),
58+
],
59+
),
60+
);
61+
}).toList(),
62+
onChanged: (String? newLanguage) {
63+
ref
64+
.read(settingsProvider.notifier)
65+
.updateSelectedLanguage(newLanguage);
66+
},
67+
),
68+
),
69+
);
70+
}
71+
72+
String _getLocalizedLanguageName(BuildContext context, String key) {
73+
switch (key) {
74+
case 'systemDefault':
75+
return S.of(context)!.systemDefault;
76+
case 'english':
77+
return S.of(context)!.english;
78+
case 'spanish':
79+
return S.of(context)!.spanish;
80+
case 'italian':
81+
return S.of(context)!.italian;
82+
default:
83+
return key;
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)