208 lines
6.3 KiB
Dart
208 lines
6.3 KiB
Dart
// 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<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||
|
||
// 全局消息控制器
|
||
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();
|
||
|
||
// 初始化 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);
|
||
}
|
||
}
|