flutter_integrated_whb/lib/main.dart

208 lines
6.3 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 '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);
}
}