import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'config.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); // 全面屏:内容延伸到状态栏/导航栏之下,刘海与手势区交给 SafeArea 处理 await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); runApp(const DshApp()); } class DshApp extends StatelessWidget { const DshApp({super.key}); @override Widget build(BuildContext context) { return AnnotatedRegion( // 站点为深色主题:状态栏透明、图标反白 value: const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.light, systemNavigationBarColor: Colors.transparent, systemNavigationBarIconBrightness: Brightness.light, ), child: MaterialApp( title: 'DSH 工作台', debugShowCheckedModeBanner: false, theme: ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( seedColor: const Color(kThemeColorValue), brightness: Brightness.dark, ), scaffoldBackgroundColor: const Color(kThemeColorValue), ), home: const HomePage(), ), ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { MethodChannel? _channel; bool _loading = true; String? _loadError; Future _onViewCreated(int id) async { final channel = MethodChannel('dsh.workbench/gecko/$id'); _channel = channel; channel.setMethodCallHandler((call) async { if (!mounted) return; switch (call.method) { case 'onPageStarted': setState(() { _loading = true; _loadError = null; }); case 'onPageFinished': setState(() => _loading = false); case 'onLoadError': setState(() { _loading = false; _loadError = call.arguments as String? ?? '页面加载失败'; }); case 'onDownloadError': ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('下载失败:${call.arguments}'))); } }); await _invoke('load'); } Future _invoke(String method) async { try { return await _channel?.invokeMethod(method); } on PlatformException catch (error) { if (mounted) { setState(() { _loading = false; _loadError = error.message ?? '浏览器启动失败'; }); } return null; } } Future _onPopInvoked(bool didPop, Object? result) async { if (didPop) return; if (await _invoke('goBack') == false && mounted) { await SystemNavigator.pop(); } } void _retry() { setState(() { _loadError = null; _loading = true; }); unawaited(_invoke('reload')); } @override void dispose() { _channel?.setMethodCallHandler(null); super.dispose(); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: _onPopInvoked, child: Scaffold( backgroundColor: const Color(kThemeColorValue), body: SafeArea( child: Stack( children: [ AndroidView( viewType: 'dsh.workbench/gecko', creationParams: const {'url': kSiteUrl}, creationParamsCodec: const StandardMessageCodec(), onPlatformViewCreated: _onViewCreated, ), if (_loadError != null) _ErrorView(message: _loadError!, onRetry: _retry) else if (_loading) const _LoadingView(), ], ), ), ), ); } } class _LoadingView extends StatelessWidget { const _LoadingView(); @override Widget build(BuildContext context) { return const ColoredBox( color: Color(kThemeColorValue), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(color: Colors.white70), SizedBox(height: 16), Text('正在加载…', style: TextStyle(color: Colors.white70)), ], ), ), ); } } class _ErrorView extends StatelessWidget { const _ErrorView({required this.message, required this.onRetry}); final String message; final VoidCallback onRetry; @override Widget build(BuildContext context) { return ColoredBox( color: const Color(kThemeColorValue), child: Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.cloud_off, size: 56, color: Colors.white54), const SizedBox(height: 16), const Text( '无法连接到服务器', style: TextStyle(color: Colors.white, fontSize: 16), ), const SizedBox(height: 8), Text( message, textAlign: TextAlign.center, maxLines: 3, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Colors.white38, fontSize: 12), ), const SizedBox(height: 20), FilledButton.icon( onPressed: onRetry, icon: const Icon(Icons.refresh), label: const Text('重试'), ), ], ), ), ), ); } }