Files
sharedinbox/lib/main.dart
T
Thomas SharedInboxandClaude Sonnet 4.6 e73e4230fa fix(tests): suppress ink_sparkle shader crash in integration test
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>
2026-06-07 01:44:50 +02:00

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,
);
}
}