flutter_integrated_whb/lib/main.dart

304 lines
9.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// 全局路由
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
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<T?> showDialogAfterUnfocus<T>(
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<T>(context: context, builder: (_) => dialog);
}
/// 同理:在展示底部模态前确保键盘隐藏
Future<T?> showModalBottomSheetAfterUnfocus<T>({
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<T>(
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<String, dynamic> 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<String, dynamic> 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);
}
}