main
dart 114 lines 3.73 KB
Raw
1 import 'dart:async';
2 import 'dart:io' show Platform;
3
4 import 'package:flutter/foundation.dart';
5 import 'package:flutter/material.dart';
6 import 'package:just_audio_background/just_audio_background.dart';
7 import 'package:marionette_flutter/marionette_flutter.dart';
8 import 'package:marionette_logger/marionette_logger.dart';
9 import 'package:pdfrx/pdfrx.dart';
10
11 import 'bootstrap/bootstrap_host.dart';
12 import 'core/logger/app_logger.dart';
13 import 'core/services/crash_report_service.dart';
14 import 'core/widgets/crash_error_widget.dart';
15
16 Future<void> main() async {
17 await runZonedGuarded<Future<void>>(
18 () async {
19 final isFlutterTest =
20 !kIsWeb && Platform.environment.containsKey('FLUTTER_TEST');
21 if (!kReleaseMode && !isFlutterTest) {
22 final logCollector = LoggerLogCollector();
23 Log.attachOutput(logCollector);
24 MarionetteBinding.ensureInitialized(
25 MarionetteConfiguration(logCollector: logCollector),
26 );
27 } else {
28 WidgetsFlutterBinding.ensureInitialized();
29 }
30 await pdfrxFlutterInitialize();
31 final crashReports = CrashReportService.instance;
32 await crashReports.initialize();
33
34 // Framework errors are reported both here and via ErrorWidget.builder
35 // (because FlutterError.presentError eventually builds an ErrorWidget).
36 // CrashReportService deduplicates identical errors within a 2-second
37 // window, so the user sees only one crash screen per incident.
38 FlutterError.onError = (details) {
39 FlutterError.presentError(details);
40 if (CrashReportService.isBenignFrameworkError(
41 details.exception,
42 details.stack,
43 )) {
44 return;
45 }
46 crashReports.capture(
47 details.exception,
48 details.stack ?? StackTrace.current,
49 errorWidgetPayload: details.toString(),
50 );
51 };
52
53 PlatformDispatcher.instance.onError = (error, stack) {
54 if (CrashReportService.isBenignFrameworkError(error, stack)) {
55 return true;
56 }
57 crashReports.capture(error, stack);
58 return true;
59 };
60
61 ErrorWidget.builder = (details) {
62 if (CrashReportService.isBenignFrameworkError(
63 details.exception,
64 details.stack,
65 )) {
66 return const SizedBox.shrink();
67 }
68 final captured = crashReports.capture(
69 details.exception,
70 details.stack ?? StackTrace.current,
71 errorWidgetPayload: details.toString(),
72 );
73 return CrashErrorWidget(crash: captured);
74 };
75
76 await JustAudioBackground.init(
77 androidNotificationChannelId:
78 'com.abdulmominsakib.localmind.channel.audio',
79 androidNotificationChannelName: 'LocalMind Audio TTS Playback',
80 androidNotificationOngoing: true,
81 );
82 runApp(const CrashFallbackApp());
83 },
84 (error, stack) {
85 if (CrashReportService.isBenignFrameworkError(error, stack)) {
86 return;
87 }
88 CrashReportService.instance.capture(error, stack);
89 },
90 );
91 }
92
93 /// Root widget that swaps the entire app body for `CrashErrorWidget`
94 /// when an async/unhandled crash is captured outside `ErrorWidget.builder`'s
95 /// reach. `ErrorWidget.builder` still handles in-frame errors.
96 class CrashFallbackApp extends StatelessWidget {
97 const CrashFallbackApp({super.key});
98
99 @override
100 Widget build(BuildContext context) {
101 return ValueListenableBuilder<CrashReport?>(
102 valueListenable: CrashReportService.instance.currentCrash,
103 builder: (context, crash, _) {
104 if (crash != null) {
105 return MaterialApp(
106 debugShowCheckedModeBanner: false,
107 home: CrashErrorWidget(crash: crash),
108 );
109 }
110 return const BootstrapHost();
111 },
112 );
113 }
114 }