shouldInitializeCrashReporting() and scrubExtra() are pure, fully unit-tested decision/scrubbing functions wired into sentry_flutter via beforeSend/beforeBreadcrumb. Config.crashReportingEnabled defaults off; the DSN is a compile-time --dart-define, not a preference. RecordingEngine gained an injected onUnexpectedStop callback, firing once when restoreAfterProcessDeath finds a trip still 'recording' at launch -- the process died without a user stop. Marked partially done: the ticket's real acceptance criteria (a forced crash landing in a real Sentry project from a release build, inspecting a real payload) need a Sentry account and a release build this environment can't produce.
59 lines
2.1 KiB
Dart
59 lines
2.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import 'src/app/providers.dart';
|
|
import 'src/config/config.dart';
|
|
import 'src/crash/crash_reporter.dart';
|
|
import 'src/ui/router.dart';
|
|
import 'src/ui/theme.dart';
|
|
|
|
Future<void> main() async {
|
|
// Loaded early, once, purely to decide whether to talk to Sentry at all -- the
|
|
// decision has to be made before `runApp`, since `SentryFlutter.init` wraps it. The
|
|
// widget tree loads its own `Config` again in `RipprApp.initState` for everything
|
|
// else; SharedPreferences is memory-cached after the first read, so this costs nothing
|
|
// beyond a single extra map lookup.
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
final config = await Config.load();
|
|
await maybeInitCrashReporting(
|
|
config: config,
|
|
appRunner: () async => runApp(const ProviderScope(child: RipprApp())),
|
|
);
|
|
}
|
|
|
|
class RipprApp extends ConsumerStatefulWidget {
|
|
const RipprApp({super.key});
|
|
|
|
@override
|
|
ConsumerState<RipprApp> createState() => _RipprAppState();
|
|
}
|
|
|
|
class _RipprAppState extends ConsumerState<RipprApp> {
|
|
final _router = buildRouter();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Re-attach to a ride that was in progress when the process died. Must run before any
|
|
// user interaction, and the database is the only thing that knows a ride was open.
|
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
// Preferences first: the uploader and the map toggle both read from them.
|
|
ref.read(configProvider.notifier).state = await Config.load();
|
|
await ref.read(recordingEngineProvider).restoreAfterProcessDeath();
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// V3-06: forces creation so the live notification keeps updating and Pause/Resume
|
|
// keeps working regardless of which screen is on top -- see the provider's doc.
|
|
ref.watch(rideNotificationCoordinatorProvider);
|
|
return MaterialApp.router(
|
|
title: 'Rippr',
|
|
debugShowCheckedModeBanner: false,
|
|
theme: ripprTheme(),
|
|
routerConfig: _router,
|
|
);
|
|
}
|
|
}
|