From c75fe556382872280a8255ead40522c0541d736d Mon Sep 17 00:00:00 2001 From: johnstef99 Date: Tue, 12 Nov 2024 00:20:45 +0200 Subject: [PATCH 1/4] fix(mobile): make widgets rebuild on locale changes This will make the make the pages to instantly refresh the correct translated string, without the need to pop and push the settings page. --- mobile/lib/pages/common/settings.page.dart | 2 ++ mobile/lib/pages/library/library.page.dart | 1 + 2 files changed, 3 insertions(+) diff --git a/mobile/lib/pages/common/settings.page.dart b/mobile/lib/pages/common/settings.page.dart index 117b0aedc0cbc..a6ca239962df3 100644 --- a/mobile/lib/pages/common/settings.page.dart +++ b/mobile/lib/pages/common/settings.page.dart @@ -46,6 +46,7 @@ class SettingsPage extends StatelessWidget { @override Widget build(BuildContext context) { + context.locale; return Scaffold( appBar: AppBar( centerTitle: false, @@ -129,6 +130,7 @@ class SettingsSubPage extends StatelessWidget { @override Widget build(BuildContext context) { + context.locale; return Scaffold( appBar: AppBar( centerTitle: false, diff --git a/mobile/lib/pages/library/library.page.dart b/mobile/lib/pages/library/library.page.dart index 48d2c685ba1e7..1161f068cf64d 100644 --- a/mobile/lib/pages/library/library.page.dart +++ b/mobile/lib/pages/library/library.page.dart @@ -23,6 +23,7 @@ class LibraryPage extends ConsumerWidget { const LibraryPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + context.locale; final trashEnabled = ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash)); From 2936b5e1f3a517268c093792cb3081ea3d0397c2 Mon Sep 17 00:00:00 2001 From: johnstef99 Date: Tue, 12 Nov 2024 00:26:02 +0200 Subject: [PATCH 2/4] fix(mobile): set the default intl locale This is needed because across the app, you don't pass the context.locale to DateFormat, so by default it uses the system's locale. This will fix the issue without the need to refactor a lot of code. --- mobile/lib/main.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 3b582e336ca17..477e142e9374b 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -191,6 +191,12 @@ class ImmichAppState extends ConsumerState await ref.read(localNotificationService).setup(); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + Intl.defaultLocale = context.locale.toLanguageTag(); + } + @override initState() { super.initState(); From 9653354df92c50e7b22dd2a6d7b602a615eb839b Mon Sep 17 00:00:00 2001 From: johnstef99 Date: Tue, 12 Nov 2024 00:28:26 +0200 Subject: [PATCH 3/4] feat(mobile): create localeProvider This provider can be used to refresh providers that provide UI elements and get cached. --- mobile/lib/main.dart | 34 +++++++++++++---------- mobile/lib/providers/locale_provider.dart | 4 +++ 2 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 mobile/lib/providers/locale_provider.dart diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 477e142e9374b..c58cc10ffd668 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/utils/download.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:timezone/data/latest.dart'; @@ -218,20 +219,25 @@ class ImmichAppState extends ConsumerState final router = ref.watch(appRouterProvider); final immichTheme = ref.watch(immichThemeProvider); - return MaterialApp( - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - debugShowCheckedModeBanner: true, - home: MaterialApp.router( - title: 'Immich', - debugShowCheckedModeBanner: false, - themeMode: ref.watch(immichThemeModeProvider), - darkTheme: getThemeData(colorScheme: immichTheme.dark), - theme: getThemeData(colorScheme: immichTheme.light), - routeInformationParser: router.defaultRouteParser(), - routerDelegate: router.delegate( - navigatorObservers: () => [TabNavigationObserver(ref: ref)], + return ProviderScope( + overrides: [ + localeProvider.overrideWithValue(context.locale), + ], + child: MaterialApp( + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + debugShowCheckedModeBanner: true, + home: MaterialApp.router( + title: 'Immich', + debugShowCheckedModeBanner: false, + themeMode: ref.watch(immichThemeModeProvider), + darkTheme: getThemeData(colorScheme: immichTheme.dark), + theme: getThemeData(colorScheme: immichTheme.light), + routeInformationParser: router.defaultRouteParser(), + routerDelegate: router.delegate( + navigatorObservers: () => [TabNavigationObserver(ref: ref)], + ), ), ), ); diff --git a/mobile/lib/providers/locale_provider.dart b/mobile/lib/providers/locale_provider.dart new file mode 100644 index 0000000000000..5de3fa009a2d2 --- /dev/null +++ b/mobile/lib/providers/locale_provider.dart @@ -0,0 +1,4 @@ +import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final localeProvider = Provider((_) => throw UnimplementedError()); From 5fe2531c935b28897a03b339d93ce035f658dd66 Mon Sep 17 00:00:00 2001 From: johnstef99 Date: Tue, 12 Nov 2024 00:34:35 +0200 Subject: [PATCH 4/4] fix(mobile): refresh asset providers on locale change This is necessary to update the locale on the already evaluated DateFormat. --- mobile/lib/providers/asset.provider.dart | 44 ++++++++++++++---------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/mobile/lib/providers/asset.provider.dart b/mobile/lib/providers/asset.provider.dart index 3855a00b76a61..9252de01bfa72 100644 --- a/mobile/lib/providers/asset.provider.dart +++ b/mobile/lib/providers/asset.provider.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/providers/memory.provider.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/services/album.service.dart'; @@ -328,24 +329,31 @@ final assetWatcher = return db.assets.watchObject(asset.id, fireImmediately: true); }); -final assetsProvider = StreamProvider.family((ref, userId) { - if (userId == null) return const Stream.empty(); - final query = _commonFilterAndSort( - _assets(ref).where().ownerIdEqualToAnyChecksum(userId), - ); - return renderListGenerator(query, ref); -}); - -final multiUserAssetsProvider = - StreamProvider.family>((ref, userIds) { - if (userIds.isEmpty) return const Stream.empty(); - final query = _commonFilterAndSort( - _assets(ref) - .where() - .anyOf(userIds, (q, u) => q.ownerIdEqualToAnyChecksum(u)), - ); - return renderListGenerator(query, ref); -}); +final assetsProvider = StreamProvider.family( + (ref, userId) { + if (userId == null) return const Stream.empty(); + ref.watch(localeProvider); + final query = _commonFilterAndSort( + _assets(ref).where().ownerIdEqualToAnyChecksum(userId), + ); + return renderListGenerator(query, ref); + }, + dependencies: [localeProvider], +); + +final multiUserAssetsProvider = StreamProvider.family>( + (ref, userIds) { + if (userIds.isEmpty) return const Stream.empty(); + ref.watch(localeProvider); + final query = _commonFilterAndSort( + _assets(ref) + .where() + .anyOf(userIds, (q, u) => q.ownerIdEqualToAnyChecksum(u)), + ); + return renderListGenerator(query, ref); + }, + dependencies: [localeProvider], +); QueryBuilder? getRemoteAssetQuery(WidgetRef ref) { final userId = ref.watch(currentUserProvider)?.isarId;