mirror of
https://github.com/immich-app/immich.git
synced 2024-12-29 15:11:58 +00:00
3a2e30e30e
* 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. * 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. * feat(mobile): create localeProvider This provider can be used to refresh providers that provide UI elements and get cached. * fix(mobile): refresh asset providers on locale change This is necessary to update the locale on the already evaluated DateFormat. --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
267 lines
8.6 KiB
Dart
267 lines
8.6 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:background_downloader/background_downloader.dart';
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
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';
|
|
import 'package:immich_mobile/constants/locales.dart';
|
|
import 'package:immich_mobile/services/background.service.dart';
|
|
import 'package:immich_mobile/entities/backup_album.entity.dart';
|
|
import 'package:immich_mobile/entities/duplicated_asset.entity.dart';
|
|
import 'package:immich_mobile/routing/router.dart';
|
|
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
|
|
import 'package:immich_mobile/utils/cache/widgets_binding.dart';
|
|
import 'package:immich_mobile/entities/album.entity.dart';
|
|
import 'package:immich_mobile/entities/android_device_asset.entity.dart';
|
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
|
import 'package:immich_mobile/entities/etag.entity.dart';
|
|
import 'package:immich_mobile/entities/exif_info.entity.dart';
|
|
import 'package:immich_mobile/entities/ios_device_asset.entity.dart';
|
|
import 'package:immich_mobile/entities/logger_message.entity.dart';
|
|
import 'package:immich_mobile/entities/store.entity.dart';
|
|
import 'package:immich_mobile/entities/user.entity.dart';
|
|
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
|
import 'package:immich_mobile/providers/db.provider.dart';
|
|
import 'package:immich_mobile/services/immich_logger.service.dart';
|
|
import 'package:immich_mobile/services/local_notification.service.dart';
|
|
import 'package:immich_mobile/utils/http_ssl_cert_override.dart';
|
|
import 'package:immich_mobile/utils/immich_app_theme.dart';
|
|
import 'package:immich_mobile/utils/migration.dart';
|
|
import 'package:isar/isar.dart';
|
|
import 'package:logging/logging.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
void main() async {
|
|
ImmichWidgetsBinding();
|
|
final db = await loadDb();
|
|
await initApp();
|
|
await migrateDatabaseIfNeeded(db);
|
|
HttpOverrides.global = HttpSSLCertOverride();
|
|
|
|
runApp(
|
|
ProviderScope(
|
|
overrides: [dbProvider.overrideWithValue(db)],
|
|
child: const MainWidget(),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> initApp() async {
|
|
await EasyLocalization.ensureInitialized();
|
|
await initializeDateFormatting();
|
|
|
|
if (kReleaseMode && Platform.isAndroid) {
|
|
try {
|
|
await FlutterDisplayMode.setHighRefreshRate();
|
|
debugPrint("Enabled high refresh mode");
|
|
} catch (e) {
|
|
debugPrint("Error setting high refresh rate: $e");
|
|
}
|
|
}
|
|
|
|
await fetchSystemPalette();
|
|
|
|
// Initialize Immich Logger Service
|
|
ImmichLogger();
|
|
|
|
var log = Logger("ImmichErrorLogger");
|
|
|
|
FlutterError.onError = (details) {
|
|
FlutterError.presentError(details);
|
|
log.severe(
|
|
'FlutterError - Catch all',
|
|
"${details.toString()}\nException: ${details.exception}\nLibrary: ${details.library}\nContext: ${details.context}",
|
|
details.stack,
|
|
);
|
|
};
|
|
|
|
PlatformDispatcher.instance.onError = (error, stack) {
|
|
debugPrint("FlutterError - Catch all: $error \n $stack");
|
|
log.severe('PlatformDispatcher - Catch all', error, stack);
|
|
return true;
|
|
};
|
|
|
|
initializeTimeZones();
|
|
|
|
FileDownloader().configureNotification(
|
|
running: TaskNotification(
|
|
'downloading_media'.tr(),
|
|
'file: {filename}',
|
|
),
|
|
complete: TaskNotification(
|
|
'download_finished'.tr(),
|
|
'file: {filename}',
|
|
),
|
|
progressBar: true,
|
|
);
|
|
|
|
FileDownloader().trackTasksInGroup(
|
|
downloadGroupLivePhoto,
|
|
markDownloadedComplete: false,
|
|
);
|
|
}
|
|
|
|
Future<Isar> loadDb() async {
|
|
final dir = await getApplicationDocumentsDirectory();
|
|
Isar db = await Isar.open(
|
|
[
|
|
StoreValueSchema,
|
|
ExifInfoSchema,
|
|
AssetSchema,
|
|
AlbumSchema,
|
|
UserSchema,
|
|
BackupAlbumSchema,
|
|
DuplicatedAssetSchema,
|
|
LoggerMessageSchema,
|
|
ETagSchema,
|
|
if (Platform.isAndroid) AndroidDeviceAssetSchema,
|
|
if (Platform.isIOS) IOSDeviceAssetSchema,
|
|
],
|
|
directory: dir.path,
|
|
maxSizeMiB: 1024,
|
|
);
|
|
Store.init(db);
|
|
return db;
|
|
}
|
|
|
|
class ImmichApp extends ConsumerStatefulWidget {
|
|
const ImmichApp({super.key});
|
|
|
|
@override
|
|
ImmichAppState createState() => ImmichAppState();
|
|
}
|
|
|
|
class ImmichAppState extends ConsumerState<ImmichApp>
|
|
with WidgetsBindingObserver {
|
|
@override
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
switch (state) {
|
|
case AppLifecycleState.resumed:
|
|
debugPrint("[APP STATE] resumed");
|
|
ref.read(appStateProvider.notifier).handleAppResume();
|
|
break;
|
|
case AppLifecycleState.inactive:
|
|
debugPrint("[APP STATE] inactive");
|
|
ref.read(appStateProvider.notifier).handleAppInactivity();
|
|
break;
|
|
case AppLifecycleState.paused:
|
|
debugPrint("[APP STATE] paused");
|
|
ref.read(appStateProvider.notifier).handleAppPause();
|
|
break;
|
|
case AppLifecycleState.detached:
|
|
debugPrint("[APP STATE] detached");
|
|
ref.read(appStateProvider.notifier).handleAppDetached();
|
|
break;
|
|
case AppLifecycleState.hidden:
|
|
debugPrint("[APP STATE] hidden");
|
|
ref.read(appStateProvider.notifier).handleAppHidden();
|
|
break;
|
|
}
|
|
}
|
|
|
|
Future<void> initApp() async {
|
|
WidgetsBinding.instance.addObserver(this);
|
|
|
|
// Draw the app from edge to edge
|
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
|
|
|
// Sets the navigation bar color
|
|
SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle(
|
|
systemNavigationBarColor: Colors.transparent,
|
|
);
|
|
if (Platform.isAndroid) {
|
|
// Android 8 does not support transparent app bars
|
|
final info = await DeviceInfoPlugin().androidInfo;
|
|
if (info.version.sdkInt <= 26) {
|
|
overlayStyle = context.isDarkTheme
|
|
? SystemUiOverlayStyle.dark
|
|
: SystemUiOverlayStyle.light;
|
|
}
|
|
}
|
|
SystemChrome.setSystemUIOverlayStyle(overlayStyle);
|
|
await ref.read(localNotificationService).setup();
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
Intl.defaultLocale = context.locale.toLanguageTag();
|
|
}
|
|
|
|
@override
|
|
initState() {
|
|
super.initState();
|
|
initApp().then((_) => debugPrint("App Init Completed"));
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
// needs to be delayed so that EasyLocalization is working
|
|
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final router = ref.watch(appRouterProvider);
|
|
final immichTheme = ref.watch(immichThemeProvider);
|
|
|
|
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,
|
|
locale: context.locale,
|
|
),
|
|
theme: getThemeData(
|
|
colorScheme: immichTheme.light,
|
|
locale: context.locale,
|
|
),
|
|
routeInformationParser: router.defaultRouteParser(),
|
|
routerDelegate: router.delegate(
|
|
navigatorObservers: () => [TabNavigationObserver(ref: ref)],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ignore: prefer-single-widget-per-file
|
|
class MainWidget extends StatelessWidget {
|
|
const MainWidget({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return EasyLocalization(
|
|
supportedLocales: locales.values.toList(),
|
|
path: translationsPath,
|
|
useFallbackTranslations: true,
|
|
fallbackLocale: locales.values.first,
|
|
child: const ImmichApp(),
|
|
);
|
|
}
|
|
}
|