Files
dsh-web/lib/main.dart

210 lines
5.7 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'config.dart';
Future<void> 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<SystemUiOverlayStyle>(
// 站点为深色主题:状态栏透明、图标反白
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<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
MethodChannel? _channel;
bool _loading = true;
String? _loadError;
Future<void> _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<T?> _invoke<T>(String method) async {
try {
return await _channel?.invokeMethod<T>(method);
} on PlatformException catch (error) {
if (mounted) {
setState(() {
_loading = false;
_loadError = error.message ?? '浏览器启动失败';
});
}
return null;
}
}
Future<void> _onPopInvoked(bool didPop, Object? result) async {
if (didPop) return;
if (await _invoke<bool>('goBack') == false && mounted) {
await SystemNavigator.pop();
}
}
void _retry() {
setState(() {
_loadError = null;
_loading = true;
});
unawaited(_invoke<void>('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('重试'),
),
],
),
),
),
);
}
}