| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | import 'dart:math'; | 
					
						
							| 
									
										
										
										
											2025-08-27 16:14:50 +08:00
										 |  |  |  | import 'dart:async'; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | import 'package:flutter/material.dart'; | 
					
						
							|  |  |  |  | import 'package:package_info_plus/package_info_plus.dart'; | 
					
						
							| 
									
										
										
										
											2025-08-08 11:05:06 +08:00
										 |  |  |  | import 'package:flutter_easyloading/flutter_easyloading.dart'; | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |  | import 'package:flutter/services.dart'; | 
					
						
							| 
									
										
										
										
											2025-09-15 15:54:03 +08:00
										 |  |  |  | import 'dart:io'; | 
					
						
							|  |  |  |  | import 'package:image_picker/image_picker.dart'; | 
					
						
							|  |  |  |  | import 'package:permission_handler/permission_handler.dart'; | 
					
						
							| 
									
										
										
										
											2025-09-18 15:40:01 +08:00
										 |  |  |  | import 'package:connectivity_plus/connectivity_plus.dart'; | 
					
						
							| 
									
										
										
										
											2025-09-19 09:36:32 +08:00
										 |  |  |  | import 'package:url_launcher/url_launcher.dart'; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | int getRandomWithNum(int min, int max) { | 
					
						
							| 
									
										
										
										
											2025-08-27 16:14:50 +08:00
										 |  |  |  |   if (max < min) { | 
					
						
							|  |  |  |  |     // 保护性处理:交换或抛错,这里交换
 | 
					
						
							|  |  |  |  |     final tmp = min; | 
					
						
							|  |  |  |  |     min = max; | 
					
						
							|  |  |  |  |     max = tmp; | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |   final random = Random(); | 
					
						
							| 
									
										
										
										
											2025-08-27 16:14:50 +08:00
										 |  |  |  |   return random.nextInt(max - min + 1) + min; // 生成 [min, max] 的随机数
 | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  | double screenHeight(BuildContext context) { | 
					
						
							|  |  |  |  |   double screenHeight = MediaQuery.of(context).size.height; | 
					
						
							|  |  |  |  |   return screenHeight; | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | double screenWidth(BuildContext context) { | 
					
						
							|  |  |  |  |   double screenWidth = MediaQuery.of(context).size.width; | 
					
						
							|  |  |  |  |   return screenWidth; | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  | Future<T?> pushPage<T>(Widget page, BuildContext context) { | 
					
						
							|  |  |  |  |   return Navigator.push<T>( | 
					
						
							|  |  |  |  |     context, | 
					
						
							|  |  |  |  |     MaterialPageRoute(builder: (_) => page), | 
					
						
							|  |  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | void present(Widget page, BuildContext context) { | 
					
						
							| 
									
										
										
										
											2025-07-28 16:50:40 +08:00
										 |  |  |  |   Navigator.of(context).push( | 
					
						
							|  |  |  |  |     PageRouteBuilder( | 
					
						
							|  |  |  |  |       pageBuilder: (context, animation, secondaryAnimation) => page, | 
					
						
							|  |  |  |  |       transitionDuration: Duration.zero, | 
					
						
							|  |  |  |  |       reverseTransitionDuration: Duration.zero, | 
					
						
							|  |  |  |  |     ), | 
					
						
							|  |  |  |  |   ); | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | void presentOpaque(Widget page, BuildContext context) { | 
					
						
							|  |  |  |  |   Navigator.of(context).push( | 
					
						
							|  |  |  |  |     PageRouteBuilder( | 
					
						
							|  |  |  |  |       opaque: false, // 允许下层透出
 | 
					
						
							|  |  |  |  |       barrierColor: Colors.black.withOpacity(0.5), //路由遮罩色
 | 
					
						
							|  |  |  |  |       pageBuilder: (context, animation, secondaryAnimation) => page, | 
					
						
							|  |  |  |  |       transitionDuration: Duration.zero, | 
					
						
							|  |  |  |  |       reverseTransitionDuration: Duration.zero, | 
					
						
							|  |  |  |  |     ), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |   ); | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-07-28 16:50:40 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:22:07 +08:00
										 |  |  |  | class FocusHelper { | 
					
						
							|  |  |  |  |   static final FocusNode _emptyNode = FocusNode(); | 
					
						
							|  |  |  |  |   /// 延迟一帧后再移交焦点,避免不生效的问题
 | 
					
						
							|  |  |  |  |   static void clearFocus(BuildContext context) { | 
					
						
							| 
									
										
										
										
											2025-09-05 09:16:54 +08:00
										 |  |  |  |     try{ | 
					
						
							|  |  |  |  |       WidgetsBinding.instance.addPostFrameCallback((_) async { | 
					
						
							|  |  |  |  |         FocusScope.of(context).requestFocus(_emptyNode); | 
					
						
							|  |  |  |  |         await SystemChannels.textInput.invokeMethod('TextInput.hide'); | 
					
						
							|  |  |  |  |       }); | 
					
						
							|  |  |  |  |     }catch(w) { | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:22:07 +08:00
										 |  |  |  |   } | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | /// 文本样式工具类,返回 Text Widget
 | 
					
						
							|  |  |  |  | class HhTextStyleUtils { | 
					
						
							|  |  |  |  |   /// 主要标题,返回 Text
 | 
					
						
							|  |  |  |  |   /// [text]: 文本内容
 | 
					
						
							|  |  |  |  |   /// [color]: 文本颜色,默认黑色
 | 
					
						
							|  |  |  |  |   /// [fontSize]: 字体大小,默认16.0
 | 
					
						
							|  |  |  |  |   /// [bold]: 是否加粗,默认true
 | 
					
						
							|  |  |  |  |   static Text mainTitle( | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |     String text, { | 
					
						
							|  |  |  |  |     Color color = Colors.black, | 
					
						
							| 
									
										
										
										
											2025-09-01 17:25:55 +08:00
										 |  |  |  |     double fontSize = 14.0, | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |     bool bold = true, | 
					
						
							|  |  |  |  |   }) { | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |     return Text( | 
					
						
							|  |  |  |  |       text, | 
					
						
							|  |  |  |  |       style: TextStyle( | 
					
						
							|  |  |  |  |         color: color, | 
					
						
							|  |  |  |  |         fontSize: fontSize, | 
					
						
							|  |  |  |  |         fontWeight: bold ? FontWeight.bold : FontWeight.normal, | 
					
						
							|  |  |  |  |       ), | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   static TextStyle secondaryTitleStyle = TextStyle( | 
					
						
							|  |  |  |  |     color: Colors.black54, | 
					
						
							| 
									
										
										
										
											2025-09-01 17:25:55 +08:00
										 |  |  |  |     fontSize: 13.0, | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   /// 次要标题,返回 Text
 | 
					
						
							|  |  |  |  |   /// [text]: 文本内容
 | 
					
						
							|  |  |  |  |   /// [color]: 文本颜色,默认深灰
 | 
					
						
							|  |  |  |  |   /// [fontSize]: 字体大小,默认14.0
 | 
					
						
							|  |  |  |  |   /// [bold]: 是否加粗,默认false
 | 
					
						
							|  |  |  |  |   static Text secondaryTitle( | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |     String text, { | 
					
						
							|  |  |  |  |     Color color = Colors.black54, | 
					
						
							| 
									
										
										
										
											2025-09-01 17:25:55 +08:00
										 |  |  |  |     double fontSize = 12.0, | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |     bool bold = false, | 
					
						
							|  |  |  |  |   }) { | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |     return Text( | 
					
						
							|  |  |  |  |       text, | 
					
						
							|  |  |  |  |       style: TextStyle( | 
					
						
							|  |  |  |  |         color: color, | 
					
						
							|  |  |  |  |         fontSize: fontSize, | 
					
						
							|  |  |  |  |         fontWeight: bold ? FontWeight.bold : FontWeight.normal, | 
					
						
							|  |  |  |  |       ), | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   /// 小文字,返回 Text
 | 
					
						
							|  |  |  |  |   /// [text]: 文本内容
 | 
					
						
							|  |  |  |  |   /// [color]: 文本颜色,默认灰色
 | 
					
						
							|  |  |  |  |   /// [fontSize]: 字体大小,默认12.0
 | 
					
						
							|  |  |  |  |   /// [bold]: 是否加粗,默认false
 | 
					
						
							|  |  |  |  |   static Text smallText( | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |     String text, { | 
					
						
							|  |  |  |  |     Color color = Colors.black54, | 
					
						
							| 
									
										
										
										
											2025-09-01 17:25:55 +08:00
										 |  |  |  |     double fontSize = 11.0, | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |     bool bold = false, | 
					
						
							|  |  |  |  |   }) { | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |     return Text( | 
					
						
							|  |  |  |  |       text, | 
					
						
							|  |  |  |  |       style: TextStyle( | 
					
						
							|  |  |  |  |         color: color, | 
					
						
							|  |  |  |  |         fontSize: fontSize, | 
					
						
							|  |  |  |  |         fontWeight: bold ? FontWeight.bold : FontWeight.normal, | 
					
						
							|  |  |  |  |       ), | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | /// 版本信息模型类
 | 
					
						
							|  |  |  |  | class AppVersionInfo { | 
					
						
							|  |  |  |  |   final String versionName; // 版本名称(如 1.0.0)
 | 
					
						
							|  |  |  |  |   final String buildNumber; // 构建号(如 1)
 | 
					
						
							|  |  |  |  |   final String fullVersion; // 完整版本(如 1.0.0+1)
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   AppVersionInfo({ | 
					
						
							|  |  |  |  |     required this.versionName, | 
					
						
							|  |  |  |  |     required this.buildNumber, | 
					
						
							|  |  |  |  |     required this.fullVersion, | 
					
						
							|  |  |  |  |   }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   @override | 
					
						
							|  |  |  |  |   String toString() { | 
					
						
							|  |  |  |  |     return fullVersion; | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // 获取应用版本信息的方法
 | 
					
						
							|  |  |  |  | Future<AppVersionInfo> getAppVersion() async { | 
					
						
							|  |  |  |  |   try { | 
					
						
							|  |  |  |  |     final packageInfo = await PackageInfo.fromPlatform(); | 
					
						
							|  |  |  |  |     return AppVersionInfo( | 
					
						
							|  |  |  |  |       versionName: packageInfo.version, | 
					
						
							|  |  |  |  |       buildNumber: packageInfo.buildNumber, | 
					
						
							|  |  |  |  |       fullVersion: '${packageInfo.version}+${packageInfo.buildNumber}', | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |   } catch (e) { | 
					
						
							|  |  |  |  |     // 获取失败时返回默认值
 | 
					
						
							|  |  |  |  |     return AppVersionInfo( | 
					
						
							|  |  |  |  |       versionName: '1.0.0', | 
					
						
							|  |  |  |  |       buildNumber: '1', | 
					
						
							|  |  |  |  |       fullVersion: '1.0.0+0', | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | /// ------------------------------------------------------
 | 
					
						
							|  |  |  |  | /// 全局会话管理
 | 
					
						
							|  |  |  |  | /// ------------------------------------------------------
 | 
					
						
							|  |  |  |  | class SessionService { | 
					
						
							|  |  |  |  |   SessionService._(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   static final SessionService instance = SessionService._(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   String? corpinfoId; | 
					
						
							|  |  |  |  |   String? loginUserId; | 
					
						
							|  |  |  |  |   Map<String, dynamic>? loginUser; | 
					
						
							|  |  |  |  |   String? deptId; | 
					
						
							|  |  |  |  |   String? deptLevel; | 
					
						
							|  |  |  |  |   String? postId; | 
					
						
							|  |  |  |  |   String? username; | 
					
						
							|  |  |  |  |   String? version; | 
					
						
							|  |  |  |  |   String? basePath; | 
					
						
							|  |  |  |  |   String? isRest; | 
					
						
							|  |  |  |  |   List<dynamic>? permission; | 
					
						
							|  |  |  |  |   bool updateInfo = false; | 
					
						
							| 
									
										
										
										
											2025-07-23 20:14:38 +08:00
										 |  |  |  |   String? dangerJson; | 
					
						
							|  |  |  |  |   String? riskJson; | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |  |   String? departmentJsonStr; | 
					
						
							| 
									
										
										
										
											2025-07-25 18:06:37 +08:00
										 |  |  |  |   String? departmentHiddenTypeJsonStr; | 
					
						
							| 
									
										
										
										
											2025-07-30 10:47:52 +08:00
										 |  |  |  |   String? customRecordDangerJson; | 
					
						
							| 
									
										
										
										
											2025-08-01 16:40:59 +08:00
										 |  |  |  |   String? unqualifiedInspectionItemID; | 
					
						
							| 
									
										
										
										
											2025-08-06 09:19:51 +08:00
										 |  |  |  |   String? listItemNameJson; | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  |   String? studyToken; | 
					
						
							| 
									
										
										
										
											2025-09-12 21:04:05 +08:00
										 |  |  |  |   String? loginPhone; | 
					
						
							|  |  |  |  |   String? loginPass; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-06 09:19:51 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   /// 如果以下任何一项为空,则跳转到登录页
 | 
					
						
							|  |  |  |  |   void loginSession(BuildContext context) { | 
					
						
							|  |  |  |  |     if (corpinfoId == null || loginUserId == null || loginUser == null) { | 
					
						
							|  |  |  |  |       Navigator.pushReplacementNamed(context, '/login'); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   // setters
 | 
					
						
							|  |  |  |  |   void setLoginUser(Map<String, dynamic> user) => loginUser = user; | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  |   void setStudyToken(String token) => studyToken = token; | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setLoginUserId(String id) => loginUserId = id; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setCorpinfoId(String id) => corpinfoId = id; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setDeptId(String id) => deptId = id; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setDeptLevel(String level) => deptLevel = level; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setPostId(String id) => postId = id; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setUsername(String name) => username = name; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setVersion(String ver) => version = ver; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setBasePath(String url) => basePath = url; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setIsRest(String rest) => isRest = rest; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setPermission(List<dynamic> list) => permission = list; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setUpdateInfo(bool flag) => updateInfo = flag; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-23 20:14:38 +08:00
										 |  |  |  |   void setDangerWaitInfo(String json) => dangerJson = json; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setRiskWaitInfo(String json) => riskJson = json; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-24 11:18:47 +08:00
										 |  |  |  |   void setDepartmentJsonStr(String json) => departmentJsonStr = json; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 10:47:52 +08:00
										 |  |  |  |   void setCustomRecordDangerJson(String json) => customRecordDangerJson = json; | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-01 16:40:59 +08:00
										 |  |  |  |   void setUnqualifiedInspectionItemIDJson(String json) => unqualifiedInspectionItemID = json; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-06 09:19:51 +08:00
										 |  |  |  |   void setListItemNameJson(String json) => listItemNameJson = json; | 
					
						
							| 
									
										
										
										
											2025-08-01 16:40:59 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 21:04:05 +08:00
										 |  |  |  |   void setSavePhone(String phone) => loginPhone = phone; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void setSavePass(String pass) => loginPass = pass; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | /// ------------------------------------------------------
 | 
					
						
							|  |  |  |  | /// 日期格式化
 | 
					
						
							|  |  |  |  | /// ------------------------------------------------------
 | 
					
						
							|  |  |  |  | String formatDate(DateTime? date, String fmt) { | 
					
						
							|  |  |  |  |   if (date == null) return ''; | 
					
						
							|  |  |  |  |   String twoDigits(int n) => n.toString().padLeft(2, '0'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   final replacements = <String, String>{ | 
					
						
							|  |  |  |  |     'yyyy': date.year.toString(), | 
					
						
							|  |  |  |  |     'yy': date.year.toString().substring(2), | 
					
						
							|  |  |  |  |     'MM': twoDigits(date.month), | 
					
						
							|  |  |  |  |     'M': date.month.toString(), | 
					
						
							|  |  |  |  |     'dd': twoDigits(date.day), | 
					
						
							|  |  |  |  |     'd': date.day.toString(), | 
					
						
							|  |  |  |  |     'hh': twoDigits(date.hour), | 
					
						
							|  |  |  |  |     'h': date.hour.toString(), | 
					
						
							|  |  |  |  |     'mm': twoDigits(date.minute), | 
					
						
							|  |  |  |  |     'm': date.minute.toString(), | 
					
						
							|  |  |  |  |     'ss': twoDigits(date.second), | 
					
						
							|  |  |  |  |     's': date.second.toString(), | 
					
						
							|  |  |  |  |   }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   String result = fmt; | 
					
						
							|  |  |  |  |   replacements.forEach((key, value) { | 
					
						
							|  |  |  |  |     result = result.replaceAllMapped(RegExp(key), (_) => value); | 
					
						
							|  |  |  |  |   }); | 
					
						
							|  |  |  |  |   return result; | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |  | /// 把 'yyyy-MM-dd HH:mm'(或 'yyyy-MM-ddTHH:mm')解析为 DateTime,失败返回 null
 | 
					
						
							|  |  |  |  | DateTime? _parseYMdHm(String s) { | 
					
						
							|  |  |  |  |   if (s.isEmpty) return null; | 
					
						
							|  |  |  |  |   String t = s.trim(); | 
					
						
							| 
									
										
										
										
											2025-09-05 11:27:45 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   // 只包含日期的情况:yyyy-MM-dd
 | 
					
						
							|  |  |  |  |   if (RegExp(r'^\d{4}-\d{2}-\d{2}$').hasMatch(t)) { | 
					
						
							|  |  |  |  |     return DateTime.tryParse(t); // 默认 00:00:00
 | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   // yyyy-MM-dd HH:mm 或 yyyy-MM-ddTHH:mm
 | 
					
						
							|  |  |  |  |   if (RegExp(r'^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}$').hasMatch(t)) { | 
					
						
							|  |  |  |  |     final iso = t.replaceFirst(' ', 'T') + ':00'; | 
					
						
							|  |  |  |  |     return DateTime.tryParse(iso); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   // yyyy-MM-dd HH:mm:ss 或 yyyy-MM-ddTHH:mm:ss
 | 
					
						
							|  |  |  |  |   if (RegExp(r'^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}$').hasMatch(t)) { | 
					
						
							|  |  |  |  |     return DateTime.tryParse(t.replaceFirst(' ', 'T')); | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-09-05 11:27:45 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   // 都不匹配,返回 null
 | 
					
						
							|  |  |  |  |   return null; | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-05 11:27:45 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |  | /// 比较两个 'yyyy-MM-dd HH:mm' 格式字符串
 | 
					
						
							|  |  |  |  | /// 返回 1 (a>b), 0 (a==b), -1 (a<b)
 | 
					
						
							|  |  |  |  | int compareYMdHmStrings(String a, String b) { | 
					
						
							|  |  |  |  |   final da = _parseYMdHm(a); | 
					
						
							|  |  |  |  |   final db = _parseYMdHm(b); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   if (da == null || db == null) { | 
					
						
							|  |  |  |  |     throw FormatException("时间格式错误,期望 'yyyy-MM-dd HH:mm' 或 'yyyy-MM-dd HH:mm:ss'"); | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |  |   if (da.isAtSameMomentAs(db)) return 0; | 
					
						
							|  |  |  |  |   return da.isAfter(db) ? 1 : -1; | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | /// 便捷:a 是否 晚于 b
 | 
					
						
							|  |  |  |  | bool isAfterStr(String a, String b) => compareYMdHmStrings(a, b) == 1; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | /// 便捷:a 是否 早于 b
 | 
					
						
							|  |  |  |  | bool isBeforeStr(String a, String b) => compareYMdHmStrings(a, b) == -1; | 
					
						
							| 
									
										
										
										
											2025-09-05 11:27:45 +08:00
										 |  |  |  | /// 判断传入时间字符串 (yyyy-MM-dd HH:mm) 是否早于当前时间
 | 
					
						
							|  |  |  |  | bool isBeforeNow(String timeStr) { | 
					
						
							|  |  |  |  |   final dt = _parseYMdHm(timeStr); | 
					
						
							|  |  |  |  |   if (dt == null) { | 
					
						
							|  |  |  |  |     throw FormatException("时间格式错误,期望 'yyyy-MM-dd HH:mm' 或 'yyyy-MM-dd HH:mm:ss'"); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |   return dt.isBefore(DateTime.now()); | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | /// ------------------------------------------------------
 | 
					
						
							|  |  |  |  | /// 防多次点击
 | 
					
						
							|  |  |  |  | /// ------------------------------------------------------
 | 
					
						
							|  |  |  |  | class ClickUtil { | 
					
						
							|  |  |  |  |   ClickUtil._(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   static bool _canClick = true; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   /// 调用示例:
 | 
					
						
							|  |  |  |  |   /// ClickUtil.noMultipleClicks(() { /* your code */ });
 | 
					
						
							|  |  |  |  |   static void noMultipleClicks(VoidCallback fn, {int delayMs = 2000}) { | 
					
						
							|  |  |  |  |     if (_canClick) { | 
					
						
							|  |  |  |  |       _canClick = false; | 
					
						
							|  |  |  |  |       fn(); | 
					
						
							|  |  |  |  |       Future.delayed(Duration(milliseconds: delayMs), () { | 
					
						
							|  |  |  |  |         _canClick = true; | 
					
						
							|  |  |  |  |       }); | 
					
						
							|  |  |  |  |     } else { | 
					
						
							|  |  |  |  |       debugPrint('请稍后点击'); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | void presentPage(BuildContext context, Widget page) { | 
					
						
							|  |  |  |  |   Navigator.push( | 
					
						
							|  |  |  |  |     context, | 
					
						
							|  |  |  |  |     MaterialPageRoute(fullscreenDialog: true, builder: (_) => page), | 
					
						
							|  |  |  |  |   ); | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-07-16 08:37:08 +08:00
										 |  |  |  | class LoadingDialogHelper { | 
					
						
							| 
									
										
										
										
											2025-08-27 16:14:50 +08:00
										 |  |  |  |   static Timer? _timer; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-15 15:54:03 +08:00
										 |  |  |  |   /// 显示加载框(带超时,默认 60 秒)
 | 
					
						
							|  |  |  |  |   static void show({String? message, Duration timeout = const Duration(seconds: 60)}) { | 
					
						
							| 
									
										
										
										
											2025-08-27 16:14:50 +08:00
										 |  |  |  |     // 先清理上一个计时器,避免重复
 | 
					
						
							|  |  |  |  |     _timer?.cancel(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-08 11:05:06 +08:00
										 |  |  |  |     if (message != null) { | 
					
						
							|  |  |  |  |       EasyLoading.show(status: message); | 
					
						
							|  |  |  |  |     } else { | 
					
						
							|  |  |  |  |       EasyLoading.show(); | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-08-27 16:14:50 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     // 设置超时自动隐藏
 | 
					
						
							|  |  |  |  |     _timer = Timer(timeout, () { | 
					
						
							|  |  |  |  |       // 保护性调用 dismiss(避免访问不存在的 isShow)
 | 
					
						
							|  |  |  |  |       try { | 
					
						
							|  |  |  |  |         EasyLoading.dismiss(); | 
					
						
							|  |  |  |  |       } catch (e) { | 
					
						
							|  |  |  |  |         debugPrint('EasyLoading.dismiss error: $e'); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |       _timer?.cancel(); | 
					
						
							|  |  |  |  |       _timer = null; | 
					
						
							|  |  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-07-16 08:37:08 +08:00
										 |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-27 16:14:50 +08:00
										 |  |  |  |   /// 隐藏加载框(手动触发)
 | 
					
						
							| 
									
										
										
										
											2025-08-08 11:05:06 +08:00
										 |  |  |  |   static void hide() { | 
					
						
							| 
									
										
										
										
											2025-08-27 16:14:50 +08:00
										 |  |  |  |     // 清理计时器
 | 
					
						
							|  |  |  |  |     _timer?.cancel(); | 
					
						
							|  |  |  |  |     _timer = null; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     try { | 
					
						
							| 
									
										
										
										
											2025-08-08 11:05:06 +08:00
										 |  |  |  |       EasyLoading.dismiss(); | 
					
						
							| 
									
										
										
										
											2025-08-27 16:14:50 +08:00
										 |  |  |  |     } catch (e) { | 
					
						
							|  |  |  |  |       debugPrint('EasyLoading.dismiss error: $e'); | 
					
						
							| 
									
										
										
										
											2025-07-16 08:37:08 +08:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-07-29 08:50:41 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-01 16:41:44 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  | /// 将秒数转换为 “HH:MM:SS” 格式
 | 
					
						
							|  |  |  |  | String secondsCount(dynamic seconds) { | 
					
						
							|  |  |  |  |   // 先尝试解析出一个 double 值
 | 
					
						
							|  |  |  |  |   double totalSeconds; | 
					
						
							|  |  |  |  |   if (seconds == null) { | 
					
						
							|  |  |  |  |     totalSeconds = 0; | 
					
						
							|  |  |  |  |   } else if (seconds is num) { | 
					
						
							|  |  |  |  |     totalSeconds = seconds.toDouble(); | 
					
						
							|  |  |  |  |   } else { | 
					
						
							|  |  |  |  |     // seconds 是字符串或其他,尝试 parse
 | 
					
						
							|  |  |  |  |     totalSeconds = double.tryParse(seconds.toString()) ?? 0.0; | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   // 取整秒,向下取整
 | 
					
						
							|  |  |  |  |   final int secs = totalSeconds.floor(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   final int h = (secs ~/ 3600) % 24; | 
					
						
							|  |  |  |  |   final int m = (secs ~/ 60) % 60; | 
					
						
							|  |  |  |  |   final int s = secs % 60; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   // padLeft 保证两位数
 | 
					
						
							|  |  |  |  |   final String hh = h.toString().padLeft(2, '0'); | 
					
						
							|  |  |  |  |   final String mm = m.toString().padLeft(2, '0'); | 
					
						
							|  |  |  |  |   final String ss = s.toString().padLeft(2, '0'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   return '$hh:$mm:$ss'; | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-08-06 13:43:22 +08:00
										 |  |  |  | void printLongString(String text, {int chunkSize = 800}) { | 
					
						
							|  |  |  |  |   final pattern = RegExp('.{1,$chunkSize}'); // 每 chunkSize 个字符一组
 | 
					
						
							|  |  |  |  |   for (final match in pattern.allMatches(text)) { | 
					
						
							|  |  |  |  |     print(match.group(0)); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-07-28 14:22:07 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | /// 表单处理
 | 
					
						
							|  |  |  |  | class FormUtils { | 
					
						
							|  |  |  |  |   /// 判断 [data] 中的 [key] 是否存在“有效值”:
 | 
					
						
							|  |  |  |  |   /// - key 不存在或值为 null -> false
 | 
					
						
							|  |  |  |  |   /// - String:去掉首尾空白后非空 -> true
 | 
					
						
							|  |  |  |  |   /// - Iterable / Map:非空 -> true
 | 
					
						
							|  |  |  |  |   /// - 其它类型(int、double、bool 等)只要不为 null 就算有值 -> true
 | 
					
						
							| 
									
										
										
										
											2025-09-10 13:48:03 +08:00
										 |  |  |  |   static bool hasValue(Map<dynamic, dynamic> data, String key) { | 
					
						
							| 
									
										
										
										
											2025-07-28 14:22:07 +08:00
										 |  |  |  |     if (!data.containsKey(key)) return false; | 
					
						
							|  |  |  |  |     final val = data[key]; | 
					
						
							|  |  |  |  |     if (val == null) return false; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     if (val is String) { | 
					
						
							|  |  |  |  |       return val.trim().isNotEmpty; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |     if (val is Iterable || val is Map) { | 
					
						
							|  |  |  |  |       return val.isNotEmpty; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |     // 数字、布尔等其它非空即可
 | 
					
						
							|  |  |  |  |     return true; | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-08-14 15:05:48 +08:00
										 |  |  |  |   /// 在list中根据一个 key,value,找到对应的map
 | 
					
						
							|  |  |  |  |   static Map findMapForKeyValue(List list,String key, String value) { | 
					
						
							|  |  |  |  |     Map target = list.firstWhere( | 
					
						
							|  |  |  |  |           (item) => item[key] == value, | 
					
						
							|  |  |  |  |       orElse: () => {}, | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |     return target ?? {}; | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-28 14:22:07 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | class NoDataWidget { | 
					
						
							|  |  |  |  |   static Widget show() { | 
					
						
							|  |  |  |  |     return Center( | 
					
						
							|  |  |  |  |       child: Column( | 
					
						
							|  |  |  |  |         mainAxisAlignment: MainAxisAlignment.center, | 
					
						
							|  |  |  |  |         children: [ | 
					
						
							|  |  |  |  |           Image.asset('assets/images/null.png', width: 200,), | 
					
						
							|  |  |  |  |           Text('暂无数据', style: TextStyle(color: Colors.grey)), | 
					
						
							|  |  |  |  |         ], | 
					
						
							|  |  |  |  |       ), | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | class NativeOrientation { | 
					
						
							|  |  |  |  |   static const MethodChannel _channel = MethodChannel('app.orientation'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 11:09:23 +08:00
										 |  |  |  |   static Future<bool> setLandscape() async { | 
					
						
							|  |  |  |  |     try { | 
					
						
							|  |  |  |  |       final res = await _channel.invokeMethod('setOrientation', 'landscape'); | 
					
						
							|  |  |  |  |       return res == true; | 
					
						
							|  |  |  |  |     } on PlatformException catch (e) { | 
					
						
							|  |  |  |  |       debugPrint('PlatformException setLandscape: $e'); | 
					
						
							|  |  |  |  |       return false; | 
					
						
							|  |  |  |  |     } catch (e) { | 
					
						
							|  |  |  |  |       debugPrint('Unknown error setLandscape: $e'); | 
					
						
							|  |  |  |  |       return false; | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-08-25 11:09:23 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   static Future<bool> setPortrait() async { | 
					
						
							|  |  |  |  |     try { | 
					
						
							|  |  |  |  |       final res = await _channel.invokeMethod('setOrientation', 'portrait'); | 
					
						
							|  |  |  |  |       return res == true; | 
					
						
							|  |  |  |  |     } on PlatformException catch (e) { | 
					
						
							|  |  |  |  |       debugPrint('PlatformException setPortrait: $e'); | 
					
						
							|  |  |  |  |       return false; | 
					
						
							|  |  |  |  |     } catch (e) { | 
					
						
							|  |  |  |  |       debugPrint('Unknown error setPortrait: $e'); | 
					
						
							|  |  |  |  |       return false; | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-08-25 11:09:23 +08:00
										 |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-09-02 16:22:17 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-15 15:54:03 +08:00
										 |  |  |  | class CameraPermissionHelper { | 
					
						
							|  |  |  |  |   static final ImagePicker _picker = ImagePicker(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   // 检查并请求相机权限(使用 ImagePicker 触发权限请求)
 | 
					
						
							|  |  |  |  |   static Future<bool> checkAndRequestCameraPermission() async { | 
					
						
							|  |  |  |  |     if (Platform.isIOS) { | 
					
						
							|  |  |  |  |       // 对于 iOS,使用 ImagePicker 触发权限请求
 | 
					
						
							|  |  |  |  |       try { | 
					
						
							|  |  |  |  |         // 尝试获取图片(但立即取消)来触发权限请求
 | 
					
						
							|  |  |  |  |         final XFile? file = await _picker.pickImage( | 
					
						
							|  |  |  |  |           source: ImageSource.camera, | 
					
						
							|  |  |  |  |           maxWidth: 1, // 最小尺寸,减少处理时间
 | 
					
						
							|  |  |  |  |           maxHeight: 1, | 
					
						
							|  |  |  |  |           imageQuality: 1, | 
					
						
							|  |  |  |  |         ).timeout(const Duration(milliseconds: 100), onTimeout: () { | 
					
						
							|  |  |  |  |           return null; // 超时返回 null,避免等待用户操作
 | 
					
						
							|  |  |  |  |         }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         // 无论是否成功获取文件,权限请求已经被触发
 | 
					
						
							|  |  |  |  |         // 现在检查实际的权限状态
 | 
					
						
							|  |  |  |  |         var status = await Permission.camera.status; | 
					
						
							|  |  |  |  |         return status.isGranted; | 
					
						
							|  |  |  |  |       } catch (e) { | 
					
						
							|  |  |  |  |         // 如果出现错误,回退到直接检查权限状态
 | 
					
						
							|  |  |  |  |         var status = await Permission.camera.status; | 
					
						
							|  |  |  |  |         return status.isGranted; | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |     } else { | 
					
						
							|  |  |  |  |       // Android 使用标准的权限检查方式
 | 
					
						
							|  |  |  |  |       var status = await Permission.camera.status; | 
					
						
							|  |  |  |  |       if (status.isDenied) { | 
					
						
							|  |  |  |  |         status = await Permission.camera.request(); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |       return status.isGranted; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   // 检查并请求相册权限
 | 
					
						
							|  |  |  |  |   static Future<bool> checkAndRequestPhotoPermission() async { | 
					
						
							|  |  |  |  |     if (Platform.isIOS) { | 
					
						
							|  |  |  |  |       // 对于 iOS,使用 ImagePicker 触发权限请求
 | 
					
						
							|  |  |  |  |       try { | 
					
						
							|  |  |  |  |         // 尝试获取图片(但立即取消)来触发权限请求
 | 
					
						
							|  |  |  |  |         final XFile? file = await _picker.pickImage( | 
					
						
							|  |  |  |  |           source: ImageSource.gallery, | 
					
						
							|  |  |  |  |           maxWidth: 1, | 
					
						
							|  |  |  |  |           maxHeight: 1, | 
					
						
							|  |  |  |  |           imageQuality: 1, | 
					
						
							|  |  |  |  |         ).timeout(const Duration(milliseconds: 100), onTimeout: () { | 
					
						
							|  |  |  |  |           return null; | 
					
						
							|  |  |  |  |         }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         // 检查实际的权限状态
 | 
					
						
							|  |  |  |  |         var status = await Permission.photos.status; | 
					
						
							|  |  |  |  |         return status.isGranted; | 
					
						
							|  |  |  |  |       } catch (e) { | 
					
						
							|  |  |  |  |         var status = await Permission.photos.status; | 
					
						
							|  |  |  |  |         return status.isGranted; | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |     } else { | 
					
						
							|  |  |  |  |       // Android 使用标准的权限检查方式
 | 
					
						
							|  |  |  |  |       var status = await Permission.storage.status; | 
					
						
							|  |  |  |  |       if (status.isDenied) { | 
					
						
							|  |  |  |  |         status = await Permission.storage.request(); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |       return status.isGranted; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-18 15:40:01 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | Future<bool> checkNetworkWifi() async { | 
					
						
							|  |  |  |  |   final connectivityResult = await Connectivity().checkConnectivity(); | 
					
						
							|  |  |  |  |   if (connectivityResult == ConnectivityResult.mobile) { | 
					
						
							|  |  |  |  |     print("当前是移动网络(可能是 2G/3G/4G/5G)"); | 
					
						
							| 
									
										
										
										
											2025-09-18 21:45:41 +08:00
										 |  |  |  |     return false; | 
					
						
							| 
									
										
										
										
											2025-09-18 15:40:01 +08:00
										 |  |  |  |   } else if (connectivityResult == ConnectivityResult.wifi) { | 
					
						
							|  |  |  |  |     return true; | 
					
						
							|  |  |  |  |     print("当前是 WiFi"); | 
					
						
							|  |  |  |  |   } else if (connectivityResult == ConnectivityResult.ethernet) { | 
					
						
							|  |  |  |  |     print("当前是有线网络"); | 
					
						
							|  |  |  |  |   } else if (connectivityResult == ConnectivityResult.none) { | 
					
						
							|  |  |  |  |     print("当前无网络连接"); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |   return false; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-19 09:36:32 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | Future<void> openAppStore() async { | 
					
						
							|  |  |  |  |   String appId = '6739233192'; | 
					
						
							|  |  |  |  |   // 优先使用 itms-apps 直接打开 App Store 应用(iOS)
 | 
					
						
							|  |  |  |  |   final Uri uri = Uri.parse('itms-apps://itunes.apple.com/app/id$appId'); | 
					
						
							|  |  |  |  |   // 可选:直接打开写评论页:
 | 
					
						
							|  |  |  |  |   // final Uri uri = Uri.parse('itms-apps://itunes.apple.com/app/id$appId?action=write-review');
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   if (await canLaunchUrl(uri)) { | 
					
						
							|  |  |  |  |     await launchUrl(uri, mode: LaunchMode.externalApplication); | 
					
						
							| 
									
										
										
										
											2025-09-19 17:59:47 +08:00
										 |  |  |  |     exit(0); | 
					
						
							| 
									
										
										
										
											2025-09-19 09:36:32 +08:00
										 |  |  |  |   } else { | 
					
						
							|  |  |  |  |     // 回退到 https 链接(在浏览器中打开 App Store 页面)
 | 
					
						
							|  |  |  |  |     final Uri webUri = Uri.parse('https://itunes.apple.com/app/id$appId'); | 
					
						
							|  |  |  |  |     if (await canLaunchUrl(webUri)) { | 
					
						
							|  |  |  |  |       await launchUrl(webUri, mode: LaunchMode.externalApplication); | 
					
						
							|  |  |  |  |     } else { | 
					
						
							|  |  |  |  |       throw 'Could not launch App Store for app id $appId'; | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-09-19 17:59:47 +08:00
										 |  |  |  |     exit(0); | 
					
						
							| 
									
										
										
										
											2025-09-19 09:36:32 +08:00
										 |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-09-18 15:40:01 +08:00
										 |  |  |  | } |