The pre-compiled ink_sparkle.frag shader built for an earlier SDK version causes an INVALID_ARGUMENT crash when rendered via software (LIBGL_ALWAYS_ SOFTWARE=1) in the CI Dagger container. Any button tap in the integration test triggers an ink effect, which loads the shader and crashes. Adding NoSplash.splashFactory to both app themes prevents the shader from being loaded. This matches what helpers.dart already does for widget tests and what CrashScreen does for its own MaterialApp. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
126 lines
4.1 KiB
Dart
126 lines
4.1 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_riverpod/misc.dart' show Override;
|
|
|
|
import 'package:sharedinbox/core/models/user_preferences.dart';
|
|
import 'package:sharedinbox/core/services/notification_service.dart';
|
|
import 'package:sharedinbox/core/sync/background_sync.dart';
|
|
import 'package:sharedinbox/data/db/database.dart';
|
|
import 'package:sharedinbox/di.dart';
|
|
import 'package:sharedinbox/ui/router.dart';
|
|
import 'package:sharedinbox/ui/screens/crash_screen.dart';
|
|
import 'package:stack_trace/stack_trace.dart' as stack_trace;
|
|
|
|
void main({List<Override> overrides = const []}) {
|
|
unawaited(
|
|
runZonedGuarded(
|
|
() async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
|
|
// Dart's async machinery propagates stack traces in chain format
|
|
// (with '===== asynchronous gap =====' separators). Flutter's
|
|
// StackFrame parser asserts on those lines, so strip them first.
|
|
FlutterError.demangleStackTrace = (StackTrace s) {
|
|
if (s is stack_trace.Chain) return s.toTrace().vmTrace;
|
|
if (s is stack_trace.Trace) return s.vmTrace;
|
|
return s;
|
|
};
|
|
|
|
// Catch errors during build (e.g. layout exceptions) and show CrashScreen.
|
|
ErrorWidget.builder = (details) => CrashScreen(
|
|
exception: details.exception,
|
|
stackTrace: details.stack,
|
|
);
|
|
|
|
// Catch framework-level errors (e.g. from gestures, timers).
|
|
FlutterError.onError = (details) {
|
|
FlutterError.presentError(details);
|
|
runApp(
|
|
CrashScreen(
|
|
exception: details.exception,
|
|
stackTrace: details.stack,
|
|
),
|
|
);
|
|
};
|
|
|
|
await initDatabasePath();
|
|
if (Platform.isAndroid) {
|
|
await initNotifications();
|
|
await registerBackgroundSync();
|
|
await _registerPrefetchTaskFromStoredPrefs();
|
|
}
|
|
runApp(
|
|
ProviderScope(overrides: overrides, child: const SharedInboxApp()),
|
|
);
|
|
},
|
|
// This handler runs in the parent zone — runApp cannot be called here.
|
|
// Framework errors are already handled by FlutterError.onError above.
|
|
(error, stack) => FlutterError.reportError(
|
|
FlutterErrorDetails(exception: error, stack: stack),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Reads the stored prefetch preference and registers the WorkManager task
|
|
/// with the correct network constraint for it. Opens and immediately closes
|
|
/// a temporary DB connection; safe because initDatabasePath() has already run.
|
|
Future<void> _registerPrefetchTaskFromStoredPrefs() async {
|
|
final db = AppDatabase();
|
|
try {
|
|
final row = await db.select(db.userPreferences).getSingleOrNull();
|
|
final mode = PrefetchMode.fromString(row?.prefetchMode);
|
|
await registerBodyPrefetchTask(mode);
|
|
} finally {
|
|
await db.close();
|
|
}
|
|
}
|
|
|
|
class SharedInboxApp extends ConsumerStatefulWidget {
|
|
const SharedInboxApp({super.key});
|
|
|
|
@override
|
|
ConsumerState<SharedInboxApp> createState() => _SharedInboxAppState();
|
|
}
|
|
|
|
const _kGitHash = String.fromEnvironment('GIT_HASH');
|
|
|
|
class _SharedInboxAppState extends ConsumerState<SharedInboxApp> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Start background IMAP sync once — runs for the lifetime of the app.
|
|
ref.read(syncManagerProvider).start();
|
|
ref.read(reliabilityRunnerProvider).start();
|
|
if (_kGitHash.isNotEmpty) {
|
|
unawaited(
|
|
ref.read(dbProvider).recordInstalledVersionIfNew(_kGitHash),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp.router(
|
|
title: 'sharedinbox.de',
|
|
theme: ThemeData(
|
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
|
|
useMaterial3: true,
|
|
splashFactory: NoSplash.splashFactory,
|
|
),
|
|
darkTheme: ThemeData(
|
|
colorScheme: ColorScheme.fromSeed(
|
|
seedColor: Colors.indigo,
|
|
brightness: Brightness.dark,
|
|
),
|
|
useMaterial3: true,
|
|
splashFactory: NoSplash.splashFactory,
|
|
),
|
|
routerConfig: router,
|
|
);
|
|
}
|
|
}
|