// main.dart import 'dart:async'; import 'package:flutter/material.dart'; import 'package:qhd_prevention/pages/badge_manager.dart'; import 'package:qhd_prevention/services/auth_service.dart'; import 'package:qhd_prevention/tools/tools.dart'; import 'package:shared_preferences/shared_preferences.dart'; import './pages/login_page.dart'; import './pages/main_tab.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'http/HttpManager.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter/services.dart'; // for TextInput.hide // 全局导航键 final GlobalKey navigatorKey = GlobalKey(); // 全局消息控制器 class GlobalMessage { static void showError(String message) { final context = navigatorKey.currentContext; if (context != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: Colors.red, duration: const Duration(seconds: 3), ), ); } } } /// 全局 helper:在弹窗前取消焦点并尽量隐藏键盘,避免弹窗后键盘自动弹起 Future showDialogAfterUnfocus(BuildContext context, Widget dialog) async { // 取消焦点并尝试隐藏键盘 FocusScope.of(context).unfocus(); try { await SystemChannels.textInput.invokeMethod('TextInput.hide'); } catch (_) {} // 给系统一点时间,避免竞态(100-200ms 足够) await Future.delayed(const Duration(milliseconds: 150)); return showDialog(context: context, builder: (_) => dialog); } /// 同理:在展示底部模态前确保键盘隐藏 Future showModalBottomSheetAfterUnfocus({ required BuildContext context, required WidgetBuilder builder, bool isScrollControlled = false, }) async { FocusScope.of(context).unfocus(); try { await SystemChannels.textInput.invokeMethod('TextInput.hide'); } catch (_) {} await Future.delayed(const Duration(milliseconds: 150)); return showModalBottomSheet( context: context, isScrollControlled: isScrollControlled, builder: builder, ); } void main() async { WidgetsFlutterBinding.ensureInitialized(); // 初始化 EasyLoading EasyLoading.instance ..displayDuration = const Duration(seconds: 20) ..indicatorType = EasyLoadingIndicatorType.ring ..loadingStyle = EasyLoadingStyle.custom ..indicatorSize = 36.0 ..radius = 0 ..progressColor = Colors.blue ..backgroundColor = Colors.grey.shade300 ..indicatorColor = Colors.blue ..textColor = Colors.black ..userInteractions = false ..dismissOnTap = false; await initializeDateFormatting('zh_CN', null); // 初始化HTTP管理器未授权回调 HttpManager.onUnauthorized = () async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool('isLoggedIn', false); await prefs.remove('token'); navigatorKey.currentState?.pushNamedAndRemoveUntil( '/login', (route) => false, ); Future.delayed(const Duration(milliseconds: 100), () { GlobalMessage.showError('会话已过期,请重新登录'); }); }; // 自动登录逻辑 final prefs = await SharedPreferences.getInstance(); final savedLogin = prefs.getBool('isLoggedIn') ?? false; bool isLoggedIn = false; if (savedLogin) { // 如果本地标记已登录,进一步验证 token 是否有效 try { isLoggedIn = await AuthService.isLoggedIn(); } catch (e) { isLoggedIn = false; } } runApp(MyApp(isLoggedIn: isLoggedIn)); } /// MyApp:恢复为 Stateless(无需监听 viewInsets) class MyApp extends StatelessWidget { final bool isLoggedIn; const MyApp({super.key, required this.isLoggedIn}); @override Widget build(BuildContext context) { return MaterialApp( title: '', navigatorKey: navigatorKey, // 在路由变化时统一取消焦点(防止 push/pop 时焦点回到 TextField) navigatorObservers: [KeyboardUnfocusNavigatorObserver()], builder: (context, child) { return EasyLoading.init( builder: (context, widget) { return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { // 全局点击空白处取消焦点(隐藏键盘) FocusHelper.clearFocus(context); }, child: widget, ); }, )(context, child); }, theme: ThemeData( dividerTheme: const DividerThemeData( color: Colors.black12, thickness: .5, indent: 0, endIndent: 0, ), primarySwatch: Colors.blue, scaffoldBackgroundColor: const Color(0xFFF1F1F1), colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), inputDecorationTheme: const InputDecorationTheme( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 8), ), snackBarTheme: const SnackBarThemeData( behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(8)), ), ), progressIndicatorTheme: const ProgressIndicatorThemeData( color: Colors.blue, ), ), home: isLoggedIn ? const MainPage() : const LoginPage(), debugShowCheckedModeBanner: false, routes: { '/login': (_) => const LoginPage(), }, ); } } /// NavigatorObserver:在 push/pop/remove/replace 等路由变化时统一取消焦点 class KeyboardUnfocusNavigatorObserver extends NavigatorObserver { void _unfocus() { try { FocusManager.instance.primaryFocus?.unfocus(); } catch (e) { debugPrint('NavigatorObserver unfocus error: $e'); } } @override void didPush(Route route, Route? previousRoute) { _unfocus(); super.didPush(route, previousRoute); } @override void didPop(Route route, Route? previousRoute) { _unfocus(); super.didPop(route, previousRoute); } @override void didRemove(Route route, Route? previousRoute) { _unfocus(); super.didRemove(route, previousRoute); } @override void didReplace({Route? newRoute, Route? oldRoute}) { _unfocus(); super.didReplace(newRoute: newRoute, oldRoute: oldRoute); } }