flutter_integrated_whb/lib/tools/tools.dart

614 lines
17 KiB
Dart
Raw Normal View History

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';
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-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) {
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 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;
String? dangerJson;
String? riskJson;
2025-07-24 11:18:47 +08:00
String? departmentJsonStr;
String? departmentHiddenTypeJsonStr;
2025-07-30 10:47:52 +08:00
String? customRecordDangerJson;
String? unqualifiedInspectionItemID;
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-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;
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
void setUnqualifiedInspectionItemIDJson(String json) => unqualifiedInspectionItemID = json;
void setListItemNameJson(String json) => listItemNameJson = json;
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;
}
/// 把 '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-09-05 11:27:45 +08:00
// 都不匹配,返回 null
return null;
}
2025-09-05 11:27:45 +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
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-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中根据一个 keyvalue找到对应的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)),
],
),
);
}
}
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-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-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;
}