// main.dart import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:qhd_prevention/services/auth_service.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 import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart' show BMFMapSDK, BMF_COORD_TYPE; import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart'; import 'pages/mine/mine_set_pwd_page.dart'; // 全局导航键 final GlobalKey navigatorKey = GlobalKey(); // 全局路由 final RouteObserver routeObserver = RouteObserver(); bool _isLoggingOut = false; // 全局消息控制器 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(); // 1) 同意 SDK 隐私(百度 SDK 要求) BMFMapSDK.setAgreePrivacy(true); // 2) 初始化鉴权 / 坐标类型 if (Platform.isIOS) { // iOS 可以通过接口直接设置 AK BMFMapSDK.setApiKeyAndCoordType('g3lZyqt0KkFnZGUsjIVO7U6lTCfpjSCt', BMF_COORD_TYPE.BD09LL); } else if (Platform.isAndroid) { // Android 插件示例:Android 的 AK 通过 Manifest 配置 BMFMapSDK.setApiKeyAndCoordType('43G1sKuHV6oRTrdR9VTIGPF9soej7V5a', BMF_COORD_TYPE.BD09LL); await BMFAndroidVersion.initAndroidVersion(); // 可选,插件示例中有 } await SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); // 初始化 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 navigatorState = navigatorKey.currentState; if (navigatorState == null) return; // 尝试获取当前路由名(注意:ModalRoute 可能为 null) final currentRouteName = ModalRoute.of(navigatorState.context)?.settings.name; if (currentRouteName == '/login') { // 已在登录页,直接返回,避免重复登出流程与提示 return; } if (_isLoggingOut) return; // 防止并发多次触发 _isLoggingOut = true; try { final prefs = await SharedPreferences.getInstance(); await prefs.setBool('isLoggedIn', false); await prefs.remove('token'); navigatorState.pushNamedAndRemoveUntil('/login', (route) => false); Future.delayed(const Duration(milliseconds: 100), () { GlobalMessage.showError('您的账号已在其他设备登录,已自动下线,请使用单一设备进行学习。'); }); } finally { _isLoggingOut = false; } }; // 自动登录逻辑 final prefs = await SharedPreferences.getInstance(); final savedLogin = prefs.getBool('isLoggedIn') ?? false; // bool isLoggedIn = false; Map data={}; if (savedLogin) { // 如果本地标记已登录,进一步验证 token 是否有效 try { // isLoggedIn = await AuthService.isLoggedIn(); data =await AuthService.isLoggedIn(); // if (data.isEmpty||data['result']!= 'success') { // isLoggedIn =false; // }else{ // isLoggedIn = true; // } } catch (e) { data={}; // isLoggedIn = false; } } // runApp(MyApp(isLoggedIn: isLoggedIn)); runApp(MyApp(data: data)); } /// MyApp:恢复为 Stateless(无需监听 viewInsets) class MyApp extends StatelessWidget { // final bool isLoggedIn; final Map data; const MyApp({super.key, required this.data}); Widget getHomePage() { // isLoggedIn ? const MainPage() : const LoginPage(), if (data.isNotEmpty&&data['result'] == 'success') { // data['LONG_TERM_PASSWORD_NOT_CHANGED']='1'; if(data['WEAK_PASSWORD']=='1'){ return const MineSetPwdPage("3"); }else if(data['LONG_TERM_PASSWORD_NOT_CHANGED']=='1'){ return const MineSetPwdPage("4"); }else{ return const MainPage(); } }else{ return const LoginPage(); } } @override Widget build(BuildContext context) { return MaterialApp( title: '', localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, // 如果使用了其他本地化包,请添加对应的 delegate ], supportedLocales: const [ Locale('zh', 'CN'), // 中文 Locale('en', 'US'), // 英文(备用) ], locale: const Locale('zh', 'CN'), // 强制使用中文 navigatorKey: navigatorKey, // 在路由变化时统一取消焦点(防止 push/pop 时焦点回到 TextField) navigatorObservers: [KeyboardUnfocusNavigatorObserver(), routeObserver], builder: (context, child) { // 使用 EasyLoading.init,同时在其内部把 textScaleFactor 固定为 1.0 return EasyLoading.init( builder: (context, widget) { // 拿到当前 MediaQuery 并创建一个 textScaleFactor = 1.0 的副本 final mq = MediaQuery.maybeOf(context) ?? MediaQueryData.fromWindow(WidgetsBinding.instance.window); final fixed = mq.copyWith(textScaleFactor: 1.0); return MediaQuery( data: fixed, child: GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { // 全局点击空白处取消焦点(隐藏键盘) try { FocusManager.instance.primaryFocus?.unfocus(); } catch (e) { debugPrint('NavigatorObserver unfocus error: $e'); } }, child: widget, ), ); }, )(context, child); }, theme: ThemeData( textTheme: const TextTheme( bodyMedium: TextStyle(fontSize: 13), // 默认字体大小 ), 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:getHomePage(), // 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); } }