import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'app_theme.dart'; import 'tp_launch_page.dart'; /// Paint a light first frame before asking plugins to read saved data. Future launchApplication({ Future? ready, Widget? child, Future Function()? bootstrap, VoidCallback? onHomeReady, }) async { assert(bootstrap != null || (ready != null && child != null)); // Attach error handling immediately, even if a caller already started work. final legacy = bootstrap == null ? _legacyReady(ready!, child!) : null; runApp( TpStartup(bootstrap: bootstrap ?? () => legacy!, onHomeReady: onHomeReady), ); } Future _legacyReady(Future ready, Widget child) async { try { await ready; } catch (_) { // AppController exposes failed storage reads through the recovery screen. } return child; } class TpStartup extends StatefulWidget { const TpStartup({super.key, required this.bootstrap, this.onHomeReady}); final Future Function() bootstrap; final VoidCallback? onHomeReady; @override State createState() => _TpStartupState(); } class _TpStartupState extends State with SingleTickerProviderStateMixin { late final _fade = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), )..addStatusListener((status) { if (status == AnimationStatus.completed && mounted) { setState(() => _revealed = true); } }); Widget? _home; bool _failed = false, _revealed = false; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) _bootstrap(); }); } void _bootstrap() { setState(() => _failed = false); Future.sync(widget.bootstrap).then( (home) { if (!mounted) return; setState(() => _home = home); // Paint home behind an opaque brand frame before fading it out. WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; final reduced = MediaQuery.maybeOf(context)?.disableAnimations ?? WidgetsBinding .instance .platformDispatcher .accessibilityFeatures .disableAnimations; if (reduced) { _fade.value = 1; } else { _fade.forward(); } widget.onHomeReady?.call(); }); }, onError: (Object error, StackTrace stack) { if (mounted) setState(() => _failed = true); }, ); } @override void dispose() { _fade.dispose(); super.dispose(); } @override Widget build(BuildContext context) { Widget content = Directionality( textDirection: TextDirection.ltr, child: Theme( data: hepanTheme(), child: AnimatedBuilder( animation: _fade, builder: (context, _) { final progress = Curves.easeOutCubic.transform(_fade.value); return AnnotatedRegion( value: (progress < 0.5 ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark) .copyWith( statusBarColor: Colors.transparent, systemNavigationBarColor: Color.lerp( const Color(0xFF38B3FF), canvas, progress, ), ), child: ColoredBox( color: const Color(0xFF38B3FF), child: Stack( fit: StackFit.expand, children: [ if (_home != null) ExcludeSemantics( excluding: !_revealed, child: ExcludeFocus( excluding: !_revealed, child: IgnorePointer( ignoring: !_revealed, child: Transform.translate( offset: Offset(0, 6 * (1 - progress)), child: _home, ), ), ), ), if (!_revealed) IgnorePointer( ignoring: !_failed, child: Opacity( key: const Key('startup-brand-overlay'), opacity: 1 - progress, child: _failed ? _recovery() : const TpLaunchPage(nativeContinuity: true), ), ), ], ), ), ); }, ), ), ); if (MediaQuery.maybeOf(context) == null) { content = MediaQuery.fromView(view: View.of(context), child: content); } return content; } Widget _recovery() => Material( color: const Color(0xFF38B3FF), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( '启动未完成,请重试', style: TextStyle(color: Colors.white, fontSize: 18), ), const SizedBox(height: 20), FilledButton( style: FilledButton.styleFrom( backgroundColor: Colors.white, foregroundColor: river, ), onPressed: _bootstrap, child: const Text('重新打开'), ), ], ), ), ); }