From 66ee9ef1a64844834172f730438bc409a7899d8d Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 7 Apr 2023 17:40:47 +0530 Subject: [PATCH] Add support for changing language --- lib/app/view/app.dart | 16 ++- lib/l10n/arb/app_de.arb | 3 +- lib/l10n/arb/app_en.arb | 4 +- lib/l10n/arb/app_es.arb | 3 +- lib/l10n/arb/app_fr.arb | 3 +- lib/l10n/arb/app_it.arb | 3 +- lib/locale.dart | 8 +- lib/onboarding/view/onboarding_page.dart | 29 ++++ lib/ui/components/separators.dart | 13 ++ lib/ui/settings/account_section_widget.dart | 29 ++++ lib/ui/settings/language_picker.dart | 140 ++++++++++++++++++++ 11 files changed, 242 insertions(+), 9 deletions(-) create mode 100644 lib/ui/components/separators.dart create mode 100644 lib/ui/settings/language_picker.dart diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index f4a0a6c5e0..09e1fd7765 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -22,6 +22,11 @@ class App extends StatefulWidget { final Locale locale; const App({Key key, this.locale = const Locale("en")}) : super(key: key); + static void setLocale(BuildContext context, Locale newLocale) { + _AppState state = context.findAncestorStateOfType<_AppState>(); + state.setLocale(newLocale); + } + @override State createState() => _AppState(); } @@ -29,6 +34,12 @@ class App extends StatefulWidget { class _AppState extends State { StreamSubscription _signedOutEvent; StreamSubscription _signedInEvent; + Locale locale; + setLocale(Locale newLocale) { + setState(() { + locale = newLocale; + }); + } @override void initState() { @@ -42,6 +53,7 @@ class _AppState extends State { setState(() {}); } }); + locale = widget.locale; UpdateService.instance.shouldUpdate().then((shouldUpdate) { if (shouldUpdate) { Future.delayed(Duration.zero, () { @@ -80,7 +92,7 @@ class _AppState extends State { theme: lightTheme, darkTheme: dartTheme, debugShowCheckedModeBanner: false, - locale: widget.locale, + locale: locale, supportedLocales: appSupportedLocales, localeListResolutionCallback: localResolutionCallBack, localizationsDelegates: const [ @@ -99,7 +111,7 @@ class _AppState extends State { theme: lightThemeData, darkTheme: darkThemeData, debugShowCheckedModeBanner: false, - locale: widget.locale, + locale: locale, supportedLocales: appSupportedLocales, localeListResolutionCallback: localResolutionCallBack, localizationsDelegates: const [ diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index c02ccb3349..a41750b2d3 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -108,5 +108,6 @@ "passwordStrengthModerate": "Mittel", "confirmPassword": "Bestätigen Sie das Passwort", "close": "Schließen", - "oopsSomethingWentWrong": "Ups, da ist etwas schief gelaufen." + "oopsSomethingWentWrong": "Ups, da ist etwas schief gelaufen.", + "selectLanguage": "Sprache auswählen" } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 308a74699d..aa0cc4f576 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -118,6 +118,8 @@ "passwordStrengthModerate": "Moderate", "confirmPassword": "Confirm password", "close": "Close", - "oopsSomethingWentWrong": "Oops, Something went wrong." + "oopsSomethingWentWrong": "Oops, Something went wrong.", + "selectLanguage": "Select language", + "language": "Language" } diff --git a/lib/l10n/arb/app_es.arb b/lib/l10n/arb/app_es.arb index 293e961133..94a030a260 100644 --- a/lib/l10n/arb/app_es.arb +++ b/lib/l10n/arb/app_es.arb @@ -2,5 +2,6 @@ "counterAppBarTitle": "Contador", "@counterAppBarTitle": { "description": "Text shown in the AppBar of the Counter Page" - } + }, + "selectLanguage": "Valitse kieli" } \ No newline at end of file diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index d204527a98..33901fb255 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -111,5 +111,6 @@ "passwordStrengthModerate": "Modéré", "confirmPassword": "Confirmer le mot de passe", "close": "Fermer", - "oopsSomethingWentWrong": "Oops ! Une erreur s'est produite." + "oopsSomethingWentWrong": "Oops ! Une erreur s'est produite.", + "selectLanguage": "Sélectionnez la langue" } \ No newline at end of file diff --git a/lib/l10n/arb/app_it.arb b/lib/l10n/arb/app_it.arb index 8e50d39529..456573c5c1 100644 --- a/lib/l10n/arb/app_it.arb +++ b/lib/l10n/arb/app_it.arb @@ -112,5 +112,6 @@ "passwordStrengthModerate": "Mediocre", "confirmPassword": "Conferma la password", "close": "Chiudi", - "oopsSomethingWentWrong": "Oops, qualcosa è andato storto." + "oopsSomethingWentWrong": "Oops, qualcosa è andato storto.", + "selectLanguage": "Seleziona lingua" } \ No newline at end of file diff --git a/lib/locale.dart b/lib/locale.dart index 215631e652..e9f78d4805 100644 --- a/lib/locale.dart +++ b/lib/locale.dart @@ -8,7 +8,7 @@ const List appSupportedLocales = [ Locale('en'), Locale('de'), Locale('fr'), - Locale('fi'), + Locale('it'), ]; Locale localResolutionCallBack(locales, supportedLocales) { @@ -25,13 +25,17 @@ Locale localResolutionCallBack(locales, supportedLocales) { Future getLocale() async { final String? savedLocale = (await SharedPreferences.getInstance()).getString('locale'); - if (savedLocale != null) { + if (savedLocale != null && + appSupportedLocales.contains(Locale(savedLocale))) { return Locale(savedLocale); } return const Locale('en'); } Future setLocale(Locale locale) async { + if (!appSupportedLocales.contains(locale)) { + throw Exception('Locale $locale is not supported by the app'); + } await (await SharedPreferences.getInstance()) .setString('locale', locale.languageCode); } diff --git a/lib/onboarding/view/onboarding_page.dart b/lib/onboarding/view/onboarding_page.dart index 5ed86dbeaa..b7733e441e 100644 --- a/lib/onboarding/view/onboarding_page.dart +++ b/lib/onboarding/view/onboarding_page.dart @@ -2,11 +2,13 @@ import 'dart:async'; +import 'package:ente_auth/app/view/app.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/ente_theme_data.dart'; import 'package:ente_auth/events/trigger_logout_event.dart'; import "package:ente_auth/l10n/l10n.dart"; +import 'package:ente_auth/locale.dart'; import 'package:ente_auth/ui/account/email_entry_page.dart'; import 'package:ente_auth/ui/account/login_page.dart'; import 'package:ente_auth/ui/account/logout_dialog.dart'; @@ -14,6 +16,9 @@ import 'package:ente_auth/ui/account/password_entry_page.dart'; import 'package:ente_auth/ui/account/password_reentry_page.dart'; import 'package:ente_auth/ui/common/gradient_button.dart'; import 'package:ente_auth/ui/home_page.dart'; +import 'package:ente_auth/ui/settings/language_picker.dart'; +import 'package:ente_auth/utils/navigation_util.dart'; +import 'package:flutter/foundation.dart'; import "package:flutter/material.dart"; class OnboardingPage extends StatefulWidget { @@ -59,6 +64,30 @@ class _OnboardingPageState extends State { children: [ Column( children: [ + kDebugMode + ? GestureDetector( + child: const Align( + alignment: Alignment.topRight, + child: Text("Lang"), + ), + onTap: () async { + final locale = await getLocale(); + routeToPage( + context, + DeviceLimitPickerPage( + appSupportedLocales, + (locale) async { + await setLocale(locale); + App.setLocale(context, locale); + }, + locale, + ), + ).then((value) { + setState(() {}); + }); + }, + ) + : const SizedBox(), Image.asset( "assets/sheild-front-gradient.png", width: 200, diff --git a/lib/ui/components/separators.dart b/lib/ui/components/separators.dart new file mode 100644 index 0000000000..bc4b1346ad --- /dev/null +++ b/lib/ui/components/separators.dart @@ -0,0 +1,13 @@ +//This method returns a newly declared list with separators. It will not +//modify the original list +import 'package:flutter/widgets.dart'; + +List addSeparators(List listOfWidgets, Widget separator) { + final int initialLength = listOfWidgets.length; + final listOfWidgetsWithSeparators = []; + listOfWidgetsWithSeparators.addAll(listOfWidgets); + for (var i = 1; i < initialLength; i++) { + listOfWidgetsWithSeparators.insert((2 * i) - 1, separator); + } + return listOfWidgetsWithSeparators; +} diff --git a/lib/ui/settings/account_section_widget.dart b/lib/ui/settings/account_section_widget.dart index 217b903d67..c7be65ca12 100644 --- a/lib/ui/settings/account_section_widget.dart +++ b/lib/ui/settings/account_section_widget.dart @@ -1,6 +1,9 @@ // @dart=2.9 +import 'package:ente_auth/app/view/app.dart'; import 'package:ente_auth/core/configuration.dart'; +import 'package:ente_auth/l10n/l10n.dart'; +import 'package:ente_auth/locale.dart'; import 'package:ente_auth/services/local_authentication_service.dart'; import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/ui/account/change_email_dialog.dart'; @@ -10,6 +13,7 @@ import 'package:ente_auth/ui/components/captioned_text_widget.dart'; import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart'; import 'package:ente_auth/ui/components/menu_item_widget.dart'; import 'package:ente_auth/ui/settings/common_settings.dart'; +import 'package:ente_auth/ui/settings/language_picker.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/navigation_util.dart'; import 'package:flutter/material.dart'; @@ -20,6 +24,7 @@ class AccountSectionWidget extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = context.l10n; return ExpandableMenuItemWidget( title: "Account", selectionOptionsWidget: _getSectionOptions(context), @@ -28,6 +33,7 @@ class AccountSectionWidget extends StatelessWidget { } Column _getSectionOptions(BuildContext context) { + final l10n = context.l10n; List children = []; children.addAll([ sectionOptionSpacing, @@ -119,6 +125,29 @@ class AccountSectionWidget extends StatelessWidget { }, ), sectionOptionSpacing, + MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: l10n.language, + ), + pressedColor: getEnteColorScheme(context).fillFaint, + trailingIcon: Icons.chevron_right_outlined, + trailingIconIsMuted: true, + onTap: () async { + final locale = await getLocale(); + routeToPage( + context, + DeviceLimitPickerPage( + appSupportedLocales, + (locale) async { + await setLocale(locale); + App.setLocale(context, locale); + }, + locale, + ), + ); + }, + ), + sectionOptionSpacing, ]); return Column( children: children, diff --git a/lib/ui/settings/language_picker.dart b/lib/ui/settings/language_picker.dart new file mode 100644 index 0000000000..baf7a8e40d --- /dev/null +++ b/lib/ui/settings/language_picker.dart @@ -0,0 +1,140 @@ +import 'package:ente_auth/l10n/l10n.dart'; +import 'package:ente_auth/theme/ente_theme.dart'; +import 'package:ente_auth/ui/components/captioned_text_widget.dart'; +import 'package:ente_auth/ui/components/divider_widget.dart'; +import 'package:ente_auth/ui/components/menu_item_widget.dart'; +import 'package:ente_auth/ui/components/separators.dart'; +import 'package:ente_auth/ui/components/title_bar_title_widget.dart'; +import 'package:ente_auth/ui/components/title_bar_widget.dart'; +import 'package:flutter/material.dart'; + +class DeviceLimitPickerPage extends StatelessWidget { + final List supportedLocales; + final ValueChanged onLocaleChanged; + final Locale currentLocale; + + const DeviceLimitPickerPage( + this.supportedLocales, + this.onLocaleChanged, + this.currentLocale, { + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return Scaffold( + body: CustomScrollView( + primary: false, + slivers: [ + TitleBarWidget( + flexibleSpaceTitle: TitleBarTitleWidget( + title: l10n.selectLanguage, + ), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 20, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ClipRRect( + borderRadius: + const BorderRadius.all(Radius.circular(8)), + child: ItemsWidget( + supportedLocales, + onLocaleChanged, + currentLocale, + ), + ), + // MenuSectionDescriptionWidget( + // content: S.of(context).maxDeviceLimitSpikeHandling(50), + // ) + ], + ), + ); + }, + childCount: 1, + ), + ), + const SliverPadding(padding: EdgeInsets.symmetric(vertical: 12)), + ], + ), + ); + } +} + +class ItemsWidget extends StatefulWidget { + final List supportedLocales; + final ValueChanged onLocaleChanged; + final Locale currentLocale; + + const ItemsWidget( + this.supportedLocales, + this.onLocaleChanged, + this.currentLocale, { + Key? key, + }) : super(key: key); + + @override + State createState() => _ItemsWidgetState(); +} + +class _ItemsWidgetState extends State { + late Locale currentDeviceLimit; + List items = []; + + @override + void initState() { + currentDeviceLimit = widget.currentLocale; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + items.clear(); + for (Locale deviceLimit in widget.supportedLocales) { + items.add( + _menuItemForPicker(deviceLimit), + ); + } + items = addSeparators( + items, + DividerWidget( + dividerType: DividerType.menuNoIcon, + bgColor: getEnteColorScheme(context).fillFaint, + ), + ); + return Column( + mainAxisSize: MainAxisSize.min, + children: items, + ); + } + + Widget _menuItemForPicker(Locale locale) { + return MenuItemWidget( + key: ValueKey(locale.toString()), + menuItemColor: getEnteColorScheme(context).fillFaint, + captionedTextWidget: CaptionedTextWidget( + title: locale.languageCode, + ), + trailingIcon: currentDeviceLimit == locale ? Icons.check : null, + alignCaptionedTextToLeft: true, + isTopBorderRadiusRemoved: true, + isBottomBorderRadiusRemoved: true, + showOnlyLoadingState: true, + onTap: () async { + widget.onLocaleChanged(locale); + + currentDeviceLimit = locale; + setState(() {}); + }, + ); + } +}