LCOV - code coverage report
Current view: top level - lib - main.dart Coverage Total Hit
Test: lcov.info Lines: 48.2 % 137 66
Test Date: 2026-11-14 15:08:11 Functions: - 0 0

            Line data    Source code
       1              : // Copyright 2023 The terCAD team. All rights reserved.
       2              : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
       3              : 
       4              : import 'dart:async';
       5              : 
       6              : import 'package:app_finance/_classes/controller/deep_link/deep_link_handler.dart';
       7              : import 'package:app_finance/_classes/controller/fallback_localization_delegate.dart';
       8              : import 'package:app_finance/_classes/herald/app_budget_positive.dart';
       9              : import 'package:app_finance/_classes/herald/app_design.dart';
      10              : import 'package:app_finance/_classes/herald/app_locale.dart';
      11              : import 'package:app_finance/_classes/herald/app_palette.dart';
      12              : import 'package:app_finance/_classes/herald/app_purchase.dart';
      13              : import 'package:app_finance/_classes/herald/app_sorting.dart';
      14              : import 'package:app_finance/_classes/herald/app_start_of_month.dart';
      15              : import 'package:app_finance/_classes/herald/app_sync.dart';
      16              : import 'package:app_finance/_classes/herald/app_theme.dart';
      17              : import 'package:app_finance/_classes/herald/app_start_of_week.dart';
      18              : import 'package:app_finance/_classes/herald/app_zoom.dart';
      19              : import 'package:app_finance/_classes/storage/app_preferences.dart';
      20              : import 'package:app_finance/_classes/structure/navigation/app_page_route.dart';
      21              : import 'package:app_finance/_configs/custom_color_scheme.dart';
      22              : import 'package:app_finance/_configs/custom_text_theme.dart';
      23              : import 'package:app_finance/_configs/localization_delegates.dart';
      24              : import 'package:app_finance/_classes/storage/app_data.dart';
      25              : import 'package:app_finance/_classes/structure/navigation/app_route.dart';
      26              : import 'package:app_finance/_configs/firebase_options.dart';
      27              : import 'package:app_finance/l10n/app_localization.dart';
      28              : import 'package:app_finance/pages/about/about_page.dart';
      29              : import 'package:app_finance/pages/account/account_add_page.dart';
      30              : import 'package:app_finance/pages/account/account_edit_page.dart';
      31              : import 'package:app_finance/pages/account/account_view_page.dart';
      32              : import 'package:app_finance/pages/account/account_page.dart';
      33              : import 'package:app_finance/pages/automation/automation_page.dart';
      34              : import 'package:app_finance/pages/automation/payment_add_page.dart';
      35              : import 'package:app_finance/pages/automation/payment_edit_page.dart';
      36              : import 'package:app_finance/pages/automation/payment_view_page.dart';
      37              : import 'package:app_finance/pages/bill/bill_add_page.dart';
      38              : import 'package:app_finance/pages/bill/bill_edit_page.dart';
      39              : import 'package:app_finance/pages/bill/bill_page.dart';
      40              : import 'package:app_finance/pages/bill/bill_scope_view_page.dart';
      41              : import 'package:app_finance/pages/bill/bill_search_page.dart';
      42              : import 'package:app_finance/pages/bill/bill_view_page.dart';
      43              : import 'package:app_finance/pages/budget/budget_page.dart';
      44              : import 'package:app_finance/pages/budget/budget_add_page.dart';
      45              : import 'package:app_finance/pages/budget/budget_edit_page.dart';
      46              : import 'package:app_finance/pages/budget/budget_view_page.dart';
      47              : import 'package:app_finance/pages/currency/currency_add_page.dart';
      48              : import 'package:app_finance/pages/currency/currency_page.dart';
      49              : import 'package:app_finance/pages/goal/goal_add_page.dart';
      50              : import 'package:app_finance/pages/goal/goal_edit_page.dart';
      51              : import 'package:app_finance/pages/goal/goal_page.dart';
      52              : import 'package:app_finance/pages/goal/goal_view_page.dart';
      53              : import 'package:app_finance/pages/integration/integration_page.dart';
      54              : import 'package:app_finance/pages/home/home_page.dart';
      55              : import 'package:app_finance/pages/invoice/invoice_edit_page.dart';
      56              : import 'package:app_finance/pages/invoice/invoice_page.dart';
      57              : import 'package:app_finance/pages/invoice/invoice_search_page.dart';
      58              : import 'package:app_finance/pages/invoice/invoice_transfer_page.dart';
      59              : import 'package:app_finance/pages/invoice/invoice_transfer_search_page.dart';
      60              : import 'package:app_finance/pages/invoice/invoice_view_page.dart';
      61              : import 'package:app_finance/pages/manager/manager_page.dart';
      62              : import 'package:app_finance/pages/metrics/metrics_page.dart';
      63              : import 'package:app_finance/pages/security/security_page.dart';
      64              : import 'package:app_finance/pages/settings/settings_page.dart';
      65              : import 'package:app_finance/pages/sorting_page.dart';
      66              : import 'package:app_finance/pages/start/start_page.dart';
      67              : import 'package:app_finance/pages/subscription/subscription_page.dart';
      68              : import 'package:firebase_analytics/firebase_analytics.dart';
      69              : import 'package:firebase_core/firebase_core.dart';
      70              : import 'package:firebase_crashlytics/firebase_crashlytics.dart';
      71              : import 'package:flutter/foundation.dart';
      72              : import 'package:material_ui/material_ui.dart';
      73              : import 'package:flutter_currency_picker/flutter_currency_picker.dart';
      74              : import 'package:provider/provider.dart';
      75              : import 'package:shared_preferences/shared_preferences.dart';
      76              : 
      77            0 : void main() async {
      78              :   final FirebaseOptions? platform;
      79            0 :   if (kIsWeb || defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.android) {
      80            0 :     platform = DefaultFirebaseOptions.currentPlatform;
      81              :   } else {
      82              :     platform = null;
      83              :   }
      84            0 :   runZonedGuarded<Future<void>>(() async {
      85            0 :     WidgetsFlutterBinding.ensureInitialized();
      86              : 
      87              :     if (platform != null) {
      88            0 :       await _initializeFirebase(platform);
      89              :     }
      90              : 
      91            0 :     AppPreferences.pref = await SharedPreferences.getInstance();
      92            0 :     CurrencyDefaults.cache = AppPreferences.pref;
      93            0 :     await DeepLinkHandler.init();
      94            0 :     final appSync = AppSync();
      95            0 :     runApp(
      96            0 :       MultiProvider(
      97            0 :         providers: [
      98            0 :           ChangeNotifierProvider<AppSync>(
      99            0 :             create: (_) => appSync,
     100              :           ),
     101            0 :           ChangeNotifierProvider<AppData>(
     102            0 :             create: (_) => AppData(appSync),
     103              :           ),
     104            0 :           ChangeNotifierProvider<AppTheme>(
     105            0 :             create: (_) => AppTheme(ThemeMode.system),
     106              :           ),
     107            0 :           ChangeNotifierProvider<AppLocale>(
     108            0 :             create: (_) => AppLocale(),
     109              :           ),
     110            0 :           ChangeNotifierProvider<AppDesign>(
     111            0 :             create: (_) => AppDesign(),
     112              :           ),
     113            0 :           ChangeNotifierProvider<AppZoom>(
     114            0 :             create: (_) => AppZoom(),
     115              :           ),
     116            0 :           ChangeNotifierProvider<AppPalette>(
     117            0 :             create: (_) => AppPalette(),
     118              :           ),
     119            0 :           ChangeNotifierProvider<AppPurchase>(
     120            0 :             create: (_) => AppPurchase(),
     121              :           ),
     122            0 :           ChangeNotifierProvider<AppStartOfWeek>(
     123            0 :             create: (_) => AppStartOfWeek(),
     124              :           ),
     125            0 :           ChangeNotifierProvider<AppStartOfMonth>(
     126            0 :             create: (_) => AppStartOfMonth(),
     127              :           ),
     128            0 :           ChangeNotifierProvider<AppSorting>(
     129            0 :             create: (_) => AppSorting(),
     130              :           ),
     131            0 :           ChangeNotifierProvider<AppBudgetPositive>(
     132            0 :             create: (_) => AppBudgetPositive(),
     133              :           ),
     134              :         ],
     135            0 :         child: MyApp(platform: platform),
     136              :       ),
     137              :     );
     138            0 :   }, (error, stack) {
     139              :     try {
     140            0 :       if (platform != null && Firebase.apps.isNotEmpty) {
     141              :         if (kIsWeb) {
     142            0 :           FirebaseAnalytics.instance.logEvent(name: 'flutter-error', parameters: {'error': error.toString()});
     143              :         } else {
     144            0 :           FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
     145              :         }
     146              :       }
     147              :     } catch (e) {
     148              :       // Skip Firebase logging
     149              :     }
     150            0 :     FlutterError.presentError(FlutterErrorDetails(exception: error, stack: stack));
     151              :     // SystemNavigator.pop();
     152              :   });
     153              : }
     154              : 
     155            0 : Future<void> _initializeFirebase(FirebaseOptions options) async {
     156              :   try {
     157            0 :     await Firebase.initializeApp(options: options).timeout(const Duration(seconds: 10));
     158              :     // Enable Crashlytics only after the app is ready, otherwise any
     159              :     // FirebaseCrashlytics call throws [core/no-app].
     160              :     if (!kIsWeb) {
     161            0 :       await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);
     162              :     }
     163            0 :     FirebaseAnalytics.instance.logAppOpen();
     164            0 :     PlatformDispatcher.instance.onError = (error, stack) {
     165              :       if (kIsWeb) {
     166            0 :         FirebaseAnalytics.instance.logEvent(
     167              :           name: 'platform-error',
     168            0 :           parameters: {'error': error.toString(), 'trace': stack.toString()},
     169              :         );
     170              :       } else {
     171            0 :         FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
     172              :       }
     173              :       return true;
     174              :     };
     175              :     if (kIsWeb) {
     176            0 :       FlutterError.onError = (details) {
     177            0 :         FlutterError.presentError(details);
     178            0 :         FirebaseAnalytics.instance.logEvent(name: 'flutter-error', parameters: {'error': details.toString()});
     179              :       };
     180              :     } else {
     181            0 :       FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
     182              :     }
     183              :   } catch (error, stack) {
     184            0 :     FlutterError.presentError(FlutterErrorDetails(exception: error, stack: stack));
     185              :   }
     186              : }
     187              : 
     188              : class MyApp extends StatefulWidget {
     189              :   final FirebaseOptions? platform;
     190            1 :   const MyApp({super.key, this.platform});
     191              : 
     192            1 :   @override
     193            1 :   MyAppState createState() => MyAppState();
     194              : }
     195              : 
     196              : class MyAppState extends State<MyApp> {
     197            1 :   WidgetBuilder? getPage(String route, Object? arguments) {
     198              :     AppRoute.current = route;
     199              :     final args = arguments as Map<String, dynamic>?;
     200            0 :     final String key = args?['uuid'] ?? args?['search'] ?? ''; // TBD: deprecated
     201            0 :     final String uuid = args?['uuid'] ?? '';
     202            0 :     final String search = args?['search'] ?? '';
     203            0 :     final int focus = args?['focus'] ?? 1;
     204            2 :     if (widget.platform != null) {
     205              :       try {
     206            0 :         if (Firebase.apps.isNotEmpty) {
     207            0 :           FirebaseAnalytics.instance.logScreenView(
     208              :             screenName: route,
     209            0 :             parameters: args?.cast(),
     210              :           );
     211              :         }
     212              :       } catch (e) {
     213              :         // Skip Firebase logging
     214              :       }
     215              :     }
     216              : 
     217            1 :     Widget router(String route) => switch (route) {
     218            1 :           AppRoute.aboutRoute => AboutPage(search: key),
     219            1 :           AppRoute.accountRoute => const AccountPage(),
     220            1 :           AppRoute.accountAddRoute => const AccountAddPage(),
     221            1 :           AppRoute.accountViewRoute => AccountViewPage(uuid: key),
     222            1 :           AppRoute.accountSearchRoute => AccountPage(search: key),
     223            1 :           AppRoute.accountEditRoute => AccountEditPage(uuid: key),
     224            1 :           AppRoute.automationRoute => const AutomationPage(),
     225            1 :           AppRoute.automationPaymentRoute => const PaymentAddPage(),
     226            1 :           AppRoute.automationPaymentViewRoute => PaymentViewPage(uuid: key),
     227            1 :           AppRoute.automationPaymentEditRoute => PaymentEditPage(uuid: key),
     228            1 :           AppRoute.billRoute => const BillPage(),
     229            2 :           AppRoute.billAddRoute => BillAddPage(focus: focus),
     230            1 :           AppRoute.billViewRoute => BillViewPage(uuid: key),
     231            1 :           AppRoute.billEditRoute => BillEditPage(uuid: key),
     232            2 :           AppRoute.billSearchRoute => BillSearchPage(),
     233            1 :           AppRoute.billScopeViewRoute => BillScopeViewPage(search: search, uuid: uuid),
     234            1 :           AppRoute.budgetRoute => const BudgetPage(),
     235            1 :           AppRoute.budgetAddRoute => const BudgetAddPage(),
     236            1 :           AppRoute.budgetViewRoute => BudgetViewPage(uuid: key),
     237            1 :           AppRoute.budgetSearchRoute => BudgetPage(search: key),
     238            1 :           AppRoute.budgetEditRoute => BudgetEditPage(uuid: key),
     239            1 :           AppRoute.currencyRoute => const CurrencyPage(),
     240            1 :           AppRoute.currencyAddRoute => const CurrencyAddPage(),
     241            1 :           AppRoute.goalRoute => const GoalPage(),
     242            1 :           AppRoute.goalAddRoute => const GoalAddPage(),
     243            1 :           AppRoute.goalViewRoute => GoalViewPage(uuid: key),
     244            1 :           AppRoute.goalEditRoute => GoalEditPage(uuid: key),
     245            1 :           AppRoute.invoiceRoute => InvoicePage(),
     246            1 :           AppRoute.invoiceViewRoute => InvoiceViewPage(uuid: key),
     247            1 :           AppRoute.invoiceEditRoute => InvoiceEditPage(uuid: key),
     248            1 :           AppRoute.invoiceSearchRoute => InvoiceSearchPage(),
     249            1 :           AppRoute.invoiceTransferRoute => InvoiceTransferPage(),
     250            1 :           AppRoute.invoiceTransferSearchRoute => InvoiceTransferSearchPage(),
     251            1 :           AppRoute.integrationRoute => const IntegrationPage(),
     252            1 :           AppRoute.homeRoute => const HomePage(),
     253            0 :           AppRoute.managerRoute => const ManagerPage(),
     254            0 :           AppRoute.metricsRoute => const MetricsPage(),
     255            0 :           AppRoute.metricsSearchRoute => MetricsPage(search: key),
     256            0 :           AppRoute.securityRoute => const SecurityPage(),
     257            0 :           AppRoute.settingsRoute => const SettingsPage(),
     258            0 :           AppRoute.startRoute => const StartPage(),
     259            0 :           AppRoute.subscriptionRoute => const SubscriptionPage(),
     260            0 :           AppRoute.sortingRoute => const SortingPage(),
     261              :           _ => const HomePage(),
     262              :         };
     263            2 :     return (_) => Directionality(
     264              :           textDirection: TextDirection.ltr,
     265            1 :           child: router(route),
     266              :         );
     267              :   }
     268              : 
     269            1 :   @override
     270              :   Widget build(BuildContext context) {
     271            2 :     final palette = context.watch<AppPalette>().value;
     272            3 :     final text = Theme.of(context).textTheme.withCustom(palette, Brightness.light);
     273            3 :     final textDark = Theme.of(context).textTheme.withCustom(palette, Brightness.dark);
     274            3 :     final sheet = Theme.of(context).bottomSheetTheme.copyWith(
     275              :           constraints: const BoxConstraints(maxWidth: double.infinity),
     276              :         );
     277            1 :     return MaterialApp(
     278              :       debugShowCheckedModeBanner: false,
     279            1 :       localizationsDelegates: [
     280              :         ...appLocalizationsDelegates,
     281            1 :         FallbackLocalizationDelegate(),
     282              :       ],
     283              :       supportedLocales: AppLocalizations.supportedLocales,
     284            2 :       locale: context.watch<AppLocale>().value,
     285            1 :       theme: ThemeData(
     286            1 :         colorScheme: const ColorScheme.light().withCustom(palette),
     287            1 :         floatingActionButtonTheme: const FloatingActionButtonThemeData().withCustom(palette, Brightness.light),
     288              :         brightness: Brightness.light,
     289              :         textTheme: text,
     290            2 :         datePickerTheme: DatePickerTheme.of(context).withCustom(palette, text, Brightness.light),
     291            2 :         timePickerTheme: TimePickerTheme.of(context).withCustom(palette, text, Brightness.light),
     292            1 :         dividerTheme: CustomDividerTheme(palette, Brightness.light),
     293              :         bottomSheetTheme: sheet,
     294              :         useMaterial3: true,
     295              :       ),
     296            1 :       darkTheme: ThemeData(
     297            1 :         colorScheme: const ColorScheme.dark().withCustom(palette),
     298            1 :         floatingActionButtonTheme: const FloatingActionButtonThemeData().withCustom(palette, Brightness.dark),
     299              :         brightness: Brightness.dark,
     300              :         textTheme: textDark,
     301            2 :         datePickerTheme: DatePickerTheme.of(context).withCustom(palette, textDark, Brightness.dark),
     302            2 :         timePickerTheme: TimePickerTheme.of(context).withCustom(palette, textDark, Brightness.dark),
     303            1 :         dividerTheme: CustomDividerTheme(palette, Brightness.dark),
     304              :         bottomSheetTheme: sheet,
     305              :         useMaterial3: true,
     306              :       ),
     307            2 :       themeMode: context.watch<AppTheme>().value,
     308              :       home: const Directionality(
     309              :         textDirection: TextDirection.ltr,
     310              :         child: HomePage(),
     311              :       ),
     312            5 :       onGenerateRoute: (settings) => AppPageRoute(builder: getPage(settings.name ?? '', settings.arguments)!),
     313              :     );
     314              :   }
     315              : }
        

Generated by: LCOV version 2.0-1