// 入口:安装全局错误钩子后用标准 runApp 启动。 // 关键点:release 包里框架/build 抛出的异常不会被自动展示,只会留黑屏; // 这里统一接管 FlutterError.onError、Zone 未捕获异常与 ErrorWidget.builder。 // ensureInitialized 与 runApp 都放进同一个 zone,避免触发 debugCheckZone 的 // "Zone mismatch"。runApp 内部会做 wrapWithDefaultView,注入 View 祖先。 import 'dart:async' show runZonedGuarded; import 'package:flutter/foundation.dart' show FlutterErrorDetails, defaultTargetPlatform, TargetPlatform; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show DeviceOrientation, SystemChrome, SystemUiMode; import 'package:iwbtc/src/core/app_error.dart'; import 'package:iwbtc/src/app_root.dart' show AppRoot; Future main() async { runZonedGuarded( () async { WidgetsFlutterBinding.ensureInitialized(); // 框架级错误(build / layout / paint / 手势回调等)。 FlutterError.onError = (FlutterErrorDetails d) { FlutterError.presentError(d); // 保持控制台/日志里的完整输出 AppError.reportFlutter(d); }; // Widget 抛错时的占位界面 —— 换成可读文案,而不是默认的红屏/黑屏。 ErrorWidget.builder = (FlutterErrorDetails d) => AppError.buildScreen(d.exceptionAsString()); await _configureMobile(); runApp(const AppRoot()); }, (Object e, StackTrace st) { AppError.reportZone(e, st); }, ); } /// 移动端专属的显示适配:横屏锁定 + 沉浸式全屏。 /// /// 画面是 800×608 的横向虚拟舞台,横屏下信箱缩放才能拿到可用的可视比例; /// 竖屏时上下留白巨大、画面极小,虚拟手柄也会挤成一团。桌面端保持原样不干预。 /// (安卓另有 AndroidManifest 的 `screenOrientation="sensorLandscape"`, /// 保证冷启动第一帧就是横屏,避免先竖后横的闪烁。) /// /// 沉浸式:用 `SystemUiMode.immersiveSticky` 隐藏状态栏与导航栏,让游戏 /// 铺满整个屏幕,与常规手机游戏一致。相比普通 `immersive`, /// `immersiveSticky` 在用户从屏幕边缘滑出系统栏后数秒会**自动重新隐藏**, /// 不会弹出"退出沉浸模式"的提示条,体验更顺。 Future _configureMobile() async { final t = defaultTargetPlatform; if (t != TargetPlatform.android && t != TargetPlatform.iOS) return; try { await SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); // 沉浸式全屏:隐藏状态栏与导航栏,游戏铺满整个屏幕。 await SystemChrome.setEnabledSystemUIMode( SystemUiMode.immersiveSticky, ); } catch (e) { // 某些设备/系统策略不允许锁定方向或沉浸,属可容忍情况,不影响游戏运行。 AppError.warn('移动端显示适配失败(横屏/沉浸)', '$e'); } }