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 6 : @override
193 6 : MyAppState createState() => MyAppState();
194 : }
195 :
196 : class MyAppState extends State<MyApp> {
197 4 : 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 8 : 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 4 : Widget router(String route) => switch (route) {
218 4 : AppRoute.aboutRoute => AboutPage(search: key),
219 4 : AppRoute.accountRoute => const AccountPage(),
220 3 : AppRoute.accountAddRoute => const AccountAddPage(),
221 3 : AppRoute.accountViewRoute => AccountViewPage(uuid: key),
222 3 : AppRoute.accountSearchRoute => AccountPage(search: key),
223 3 : AppRoute.accountEditRoute => AccountEditPage(uuid: key),
224 3 : AppRoute.automationRoute => const AutomationPage(),
225 3 : AppRoute.automationPaymentRoute => const PaymentAddPage(),
226 3 : AppRoute.automationPaymentViewRoute => PaymentViewPage(uuid: key),
227 3 : AppRoute.automationPaymentEditRoute => PaymentEditPage(uuid: key),
228 3 : AppRoute.billRoute => const BillPage(),
229 5 : AppRoute.billAddRoute => BillAddPage(focus: focus),
230 2 : AppRoute.billViewRoute => BillViewPage(uuid: key),
231 2 : AppRoute.billEditRoute => BillEditPage(uuid: key),
232 2 : AppRoute.billSearchRoute => BillSearchPage(),
233 2 : AppRoute.billScopeViewRoute => BillScopeViewPage(search: search, uuid: uuid),
234 2 : AppRoute.budgetRoute => const BudgetPage(),
235 2 : AppRoute.budgetAddRoute => const BudgetAddPage(),
236 2 : AppRoute.budgetViewRoute => BudgetViewPage(uuid: key),
237 2 : AppRoute.budgetSearchRoute => BudgetPage(search: key),
238 2 : AppRoute.budgetEditRoute => BudgetEditPage(uuid: key),
239 2 : AppRoute.currencyRoute => const CurrencyPage(),
240 2 : AppRoute.currencyAddRoute => const CurrencyAddPage(),
241 2 : 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 0 : 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 8 : return (_) => Directionality(
264 : textDirection: TextDirection.ltr,
265 4 : child: router(route),
266 : );
267 : }
268 :
269 6 : @override
270 : Widget build(BuildContext context) {
271 12 : final palette = context.watch<AppPalette>().value;
272 18 : final text = Theme.of(context).textTheme.withCustom(palette, Brightness.light);
273 18 : final textDark = Theme.of(context).textTheme.withCustom(palette, Brightness.dark);
274 18 : final sheet = Theme.of(context).bottomSheetTheme.copyWith(
275 : constraints: const BoxConstraints(maxWidth: double.infinity),
276 : );
277 6 : return MaterialApp(
278 : debugShowCheckedModeBanner: false,
279 6 : localizationsDelegates: [
280 : ...appLocalizationsDelegates,
281 6 : FallbackLocalizationDelegate(),
282 : ],
283 : supportedLocales: AppLocalizations.supportedLocales,
284 12 : locale: context.watch<AppLocale>().value,
285 6 : theme: ThemeData(
286 6 : colorScheme: const ColorScheme.light().withCustom(palette),
287 6 : floatingActionButtonTheme: const FloatingActionButtonThemeData().withCustom(palette, Brightness.light),
288 : brightness: Brightness.light,
289 : textTheme: text,
290 12 : datePickerTheme: DatePickerTheme.of(context).withCustom(palette, text, Brightness.light),
291 12 : timePickerTheme: TimePickerTheme.of(context).withCustom(palette, text, Brightness.light),
292 6 : dividerTheme: CustomDividerTheme(palette, Brightness.light),
293 : bottomSheetTheme: sheet,
294 : useMaterial3: true,
295 : ),
296 6 : darkTheme: ThemeData(
297 6 : colorScheme: const ColorScheme.dark().withCustom(palette),
298 6 : floatingActionButtonTheme: const FloatingActionButtonThemeData().withCustom(palette, Brightness.dark),
299 : brightness: Brightness.dark,
300 : textTheme: textDark,
301 12 : datePickerTheme: DatePickerTheme.of(context).withCustom(palette, textDark, Brightness.dark),
302 12 : timePickerTheme: TimePickerTheme.of(context).withCustom(palette, textDark, Brightness.dark),
303 6 : dividerTheme: CustomDividerTheme(palette, Brightness.dark),
304 : bottomSheetTheme: sheet,
305 : useMaterial3: true,
306 : ),
307 12 : themeMode: context.watch<AppTheme>().value,
308 : home: const Directionality(
309 : textDirection: TextDirection.ltr,
310 : child: HomePage(),
311 : ),
312 20 : onGenerateRoute: (settings) => AppPageRoute(builder: getPage(settings.name ?? '', settings.arguments)!),
313 : );
314 : }
315 : }
|