Files
tuiche/lib/common/util/MyUtils.dart
2026-02-03 11:59:21 +08:00

588 lines
18 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:async';
import 'dart:math';
import 'package:ef/ef.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:intl/intl.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:vbvs_app/common/color/app_uri_status.dart';
import 'package:vbvs_app/common/pojo/city.dart';
import 'package:vbvs_app/common/util/CommonVariables.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
import 'package:vbvs_app/controller/mh_controller/mh_language_controller.dart';
import 'package:vbvs_app/controller/setting/language/language_controller.dart';
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
import 'package:vbvs_app/model/api_response.dart';
ThemeController themeController = Get.find();
LanguageController languageController = Get.find();
MHLanguageController mhLanguageController = Get.find();
class MyUtils {
// 获取城市显示文本
// 更详细的显示逻辑(可选)
static String getDetailedCityDisplayText(CityModel? cityModel) {
if (cityModel == null) {
return '选择城市'.tr;
}
// 根据数据层级显示不同的格式
if (cityModel.city != null && cityModel.city!.isNotEmpty) {
// 三级数据:国家-省份-城市
return '${cityModel.country ?? ''}-${cityModel.province ?? ''}-${cityModel.city ?? cityModel.value ?? ''}';
} else if (cityModel.province != null && cityModel.province!.isNotEmpty) {
// 二级数据:国家-省份
return '${cityModel.country ?? ''}-${cityModel.province ?? cityModel.value ?? ''}';
} else if (cityModel.country != null && cityModel.country!.isNotEmpty) {
// 一级数据:国家
return cityModel.country!;
} else if (cityModel.value != null && cityModel.value!.isNotEmpty) {
// 只有 value 字段
return cityModel.value!;
} else {
return '选择城市'.tr;
}
}
static String formatTimestampToSleep(int milliseconds) {
final date = DateTime.fromMillisecondsSinceEpoch(milliseconds);
return "${date.year}-${date.month}-${date.day}";
}
static String formatDate(DateTime dateTime) {
return "${dateTime.year}-${dateTime.month}-${dateTime.day.toString().padLeft(2, '0')}";
}
static String formatToDate(int timestamp) {
final dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
return "${dateTime.year}-${dateTime.month}-${dateTime.day.toString().padLeft(2, '0')}";
}
static Map<String, int> diffHoursMinutesMap(int startMillis, int endMillis) {
final duration = Duration(milliseconds: endMillis - startMillis);
final hours = duration.inHours;
final minutes = duration.inMinutes % 60;
return {
"hours": hours,
"minutes": minutes,
};
}
static String formatToHHmm(int timestampMillis) {
final dateTime = DateTime.fromMillisecondsSinceEpoch(timestampMillis);
final twoDigits = (int n) => n.toString().padLeft(2, '0');
return '${twoDigits(dateTime.hour)}:${twoDigits(dateTime.minute)}';
}
static ApiResponse formatResponse(
ApiResponse res,
String successMsg,
String errorMsg,
) {
if (res.code == HttpStatusCodes.ok) {
// 成功但 msg 为空时填默认成功提示
if (res.msg == null || res.msg!.isEmpty) {
res.msg = successMsg;
}
} else {
// 失败且 msg 为空时填默认失败提示
if (res.msg == null || res.msg!.isEmpty) {
res.msg = errorMsg;
}
}
return res;
}
static String timestampToDateString(int timestamp) {
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
String formattedDate =
'${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} '
'${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}:${dateTime.second.toString().padLeft(2, '0')}';
return formattedDate;
}
// static String hidePhoneNumber(String phoneNumber) {
// if (phoneNumber.length != 11) {
// // 检查手机号是否为11位
// throw Exception("手机号格式不正确");
// }
// // 将中间四位替换为星号
// return phoneNumber.replaceRange(3, 7, '****');
// }
static String hidePhoneNumber(String phoneNumber) {
if (phoneNumber.isEmpty) {
return phoneNumber;
}
// 提取所有数字
final digits = phoneNumber.replaceAll(RegExp(r'[^\d]'), '');
if (digits.isEmpty) {
return phoneNumber;
}
// 显示最后4位如果长度足够
final visibleDigits = min(4, digits.length);
final hiddenCount = digits.length - visibleDigits;
// 构建结果:隐藏部分 + 可见部分
final result =
'*' * hiddenCount + digits.substring(digits.length - visibleDigits);
return result;
}
static double initialScrollOffset = 0.0;
// 判断手机号格式是否正确的方法
// static bool isValidPhoneNumber(String phoneNumber) {
// final RegExp phoneRegExp = RegExp(r'^1[3-9]\d{9}$');
// return phoneRegExp.hasMatch(phoneNumber);
// }
static bool isValidPhoneNumber(String phoneNumber) {
// 匹配大多数国家的手机号(去掉空格和特殊字符后)
final RegExp phoneRegExp = RegExp(r'^\+?[1-9]\d{1,14}$');
return phoneRegExp.hasMatch(phoneNumber.replaceAll(RegExp(r'\s+'), ''));
}
static bool isValidEmail(String email) {
final RegExp emailRegExp = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
);
return emailRegExp.hasMatch(email);
}
static Future<void> makePhoneCall(String phoneNumber) async {
final Uri launchUri = Uri(
scheme: 'tel',
path: phoneNumber,
);
if (await canLaunchUrl(launchUri)) {
await launchUrl(launchUri);
} else {
throw '无法拨打电话';
}
}
static String formatDateTime(DateTime dateTime) {
// 定义日期格式
// dateTime.toLocal();
final DateFormat formatter = DateFormat('yyyy-MM-dd HH:mm');
// 返回格式化后的字符串
return formatter.format(dateTime);
}
static String formatTimestamp(int timestamp) {
try {
// 将毫秒时间戳转换为 DateTime
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
// 格式化为你想要的样式,比如 'yyyy-MM-dd HH:mm'
final DateFormat formatter = DateFormat('yyyy-MM-dd HH:mm');
return formatter.format(dateTime);
} catch (e) {
return '';
}
}
static void scrollToFocusedInput(FocusNode focusNode, _scrollController) {
// 获取输入框相对于整个页面的偏移量
RenderObject? object = focusNode.context?.findRenderObject();
if (object != null) {
final RenderBox box = object as RenderBox;
final Offset position = box.localToGlobal(Offset.zero);
// 将页面滚动到使输入框显示在屏幕上的位置
_scrollController.animateTo(
position.dy - MediaQuery.of(focusNode.context!).size.height / 4,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
}
static void resetScrollPosition(_scrollController) {
_scrollController.animateTo(
initialScrollOffset,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
static String formatBindTime(DateTime d) {
final DateFormat formatter = DateFormat('yyyy/MM/dd');
return formatter.format(d);
}
static DateTime? formatBirthdayTime(String? device) {
if (device == null || device.isEmpty) return null;
try {
return DateTime.parse(device.replaceAll('/', '-')); // 替换为标准格式
} catch (e) {
return null; // 解析失败时返回 null
}
}
static int getAgeByDate(DateTime? formatBirthdayTime) {
if (formatBirthdayTime == null) return 0;
final now = DateTime.now();
int age = now.year - formatBirthdayTime.year;
// 如果还没到今年生日,减一岁
if (now.month < formatBirthdayTime.month ||
(now.month == formatBirthdayTime.month &&
now.day < formatBirthdayTime.day)) {
age--;
}
return age;
}
static String formatDateTimeWeek(DateTime date) {
DateTime now = DateTime.now();
// 去除时间部分,仅比较年月日
DateTime today = DateTime(now.year, now.month, now.day);
DateTime target = DateTime(date.year, date.month, date.day);
if (target == today) {
return '今日'.tr;
}
List<String> weekdays = [
'周日'.tr,
'周一'.tr,
'周二'.tr,
'周三'.tr,
'周四'.tr,
'周五'.tr,
'周六'.tr,
];
// String currentLanguageCode = AppLanguage().getCurrentLanguageCode();
// if (currentLanguageCode != null) {
// if (currentLanguageCode != "zh_CN") {
// weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
// }
// }
return weekdays[date.weekday % 7]; // Dart中星期日是7要映射到索引0
}
/// 返回 MM/dd 格式
static String formatDateTimeDay(DateTime date) {
String twoDigits(int n) => n.toString().padLeft(2, '0');
return '${twoDigits(date.month)}/${twoDigits(date.day)}';
}
static String getFormatChineseTime(int date, {bool showWeekday = true}) {
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(date);
// 格式化年月日
String dateStr = DateFormat('yyyy年MM月dd日').format(dateTime);
if (!showWeekday) return dateStr;
// 获取星期
const weekDays = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'];
String weekStr = weekDays[dateTime.weekday - 1];
return '$dateStr $weekStr';
}
static String getFormatEnglishDate(int millis, {bool showWeekday = true}) {
final date = DateTime.fromMillisecondsSinceEpoch(millis);
// 英文星期简写
const weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
final weekday = weekdays[date.weekday - 1];
// 格式化年月日2025/07/21
final formattedDate =
'${date.year}/${date.month.toString().padLeft(2, '0')}/${date.day.toString().padLeft(2, '0')}';
if (!showWeekday) return formattedDate;
return '$weekday, $formattedDate';
}
}
Color stringToColor(String hexColor) {
String formattedColor = hexColor.replaceAll("#", "");
int colorValue = int.parse("0xFF$formattedColor");
return Color(colorValue);
}
//字节转16进制
String ab2str(List<int> buffer) {
return buffer.map((x) => x.toRadixString(16).padLeft(2, '0')).join('');
}
enum ToastColor { success, error, warn }
ToastColor color_success = ToastColor.success;
ToastColor color_warning = ToastColor.warn;
ToastColor color_error = ToastColor.error;
showToast(String msg,
{ToastColor color = ToastColor.error, int closeTime = 3}) {
// Fluttertoast.showToast(
// msg: msg,
// toastLength: Toast.LENGTH_LONG,
// gravity: ToastGravity.CENTER,
// timeInSecForIosWeb: 1,
// backgroundColor: color == null ? color_error : color,
// fontSize: 14.0,
// );
final context = Get.overlayContext; // 获取 Overlay 的上下文
Color background = Colors.red;
Color color_text = stringToColor("#FA5A4C");
String icon = "";
if (color == color_success) {
background = stringToColor("#DBF1E1");
icon = "success";
color_text = stringToColor("#31CA79");
} else if (color == color_warning) {
background = stringToColor("#FDF6EC");
icon = "warn";
color_text = stringToColor("#F8AE00");
} else if (color == color_error) {
background = stringToColor("#FEF0F0");
icon = "error";
color_text = stringToColor("#FA5A4C");
}
if (context == null) return;
OverlayEntry overlayEntry = OverlayEntry(
builder: (context) => Positioned(
top: MediaQuery.of(context).size.height * 0.10,
left: MediaQuery.of(context).size.width * 0.5 - 225.rpx,
child: Material(
color: Colors.transparent,
child: Container(
width: 450.rpx,
padding: EdgeInsets.only(top: 20.rpx, bottom: 20.rpx),
decoration: BoxDecoration(
color: background,
borderRadius: BorderRadius.circular(10.rpx),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
"assets/images/toast/${icon}_.png",
width: 30.rpx,
height: 30.rpx,
),
SizedBox(
width: 18.rpx,
),
Container(
constraints: BoxConstraints(maxWidth: 380.rpx),
child: Text(
msg,
maxLines: 9,
style: TextStyle(
color: color_text,
fontSize: 28.rpx,
fontWeight: FontWeight.w400),
),
)
],
),
),
),
),
);
Overlay.of(context)?.insert(overlayEntry);
Future.delayed(Duration(seconds: closeTime), () {
overlayEntry.remove();
});
}
var closeIcon = GestureDetector(
onTapDown: (f) {
Get.back();
},
child: Container(
padding: EdgeInsets.fromLTRB(10.rpx, 18.rpx, 10.rpx, 10.rpx),
child: Container(
height: 42.rpx,
width: 42.rpx,
alignment: Alignment.center,
child: Icon(
Icons.close,
),
),
),
);
var closeIconWhite = GestureDetector(
onTapDown: (f) {
Get.back();
},
child: Container(
padding: EdgeInsets.all(10.rpx),
child: Container(
height: 42.rpx,
width: 42.rpx,
alignment: Alignment.center,
child: Icon(
Icons.close,
color: themeController.currentColor.sc3,
),
),
),
);
var returnIconButtom = returnIconButtomNew(
onBack: () {
// ⚡这里写你需要在返回前执行的逻辑
// Get.back();
},
);
Widget returnIconButtomNew({VoidCallback? onBack}) {
return ClickableContainer(
backgroundColor: Colors.transparent,
highlightColor: Colors.transparent,
padding: EdgeInsets.fromLTRB(20.rpx, 20.rpx, 20.rpx, 20.rpx),
onTap: () {
if (onBack != null) onBack(); // 执行回调
Get.back(); // 返回
},
child: SvgPicture.asset(
'assets/img/icon/return_buttom.svg',
width: 42.rpx,
height: 42.rpx,
),
);
}
var returnIconButtomAddCallback = (
VoidCallback? returnCallBack, {
bool enableBack = true,
}) {
return ClickableContainer(
backgroundColor: Colors.transparent,
highlightColor: Colors.transparent,
padding: EdgeInsets.fromLTRB(20.rpx, 20.rpx, 20.rpx, 20.rpx),
onTap: () {
returnCallBack?.call();
if (enableBack) {
Get.back();
}
},
child: SvgPicture.asset(
'assets/img/icon/return_buttom.svg',
width: 42.rpx,
height: 42.rpx,
),
);
};
String time_08_Formatter(String time) {
if (time == null || time == "") {
return "";
}
return DateFormat("yyyy-MM-dd HH:mm:ss")
.format(DateTime.parse(time).toLocal());
}
// String time_08_Formatter_pattern(String time, String pattern) {
// if (time == null || time == "") {
// return "";
// }
// return DateFormat(pattern).format(DateTime.parse(time).toLocal());
// }
String time_08_Formatter_pattern(dynamic date, String pattern) {
if (date == null || date.toString().isEmpty) return "-";
try {
String normalized = date.toString().replaceAll("/", "-");
DateTime dt = DateTime.parse(normalized);
return DateFormat(pattern).format(dt);
} catch (e) {
return "-";
}
}
String storagePubSrc =
"${CommonVariables.supabaseUrl}/storage/v1/object/public/";
getStorageResourceUrl(String v) {
if (v.contains('http')) {
return v;
}
return storagePubSrc + v;
}
enum LoadingDialogIcon { ble, wifi, none }
class LoadingDialog {
static show(String name, {LoadingDialogIcon icon = LoadingDialogIcon.none}) {
ThemeController themeController = Get.find();
String iconUrl = "";
if (icon == LoadingDialogIcon.wifi) {
iconUrl = "wifi";
} else if (icon == LoadingDialogIcon.ble) {
iconUrl = "ble";
}
Get.dialog(
PopScope(
canPop: false,
child: Container(
// color: Colors.transparent,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
constraints:
BoxConstraints(minWidth: 300.rpx, maxWidth: 500.rpx),
decoration:
BoxDecoration(borderRadius: BorderRadius.circular(8)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (iconUrl.isNotEmpty)
Container(
width: 120.rpx,
height: 120.rpx,
margin: EdgeInsets.only(bottom: 60.rpx),
child:
Image.asset("assets/images/toast/${iconUrl}.png"),
),
Text(
textAlign: TextAlign.center,
name,
style: TextStyle(
fontSize: 16,
color: themeController.currentColor.sc3,
decoration: TextDecoration.none),
),
SizedBox(
height: 30.rpx,
),
CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
themeController.currentColor.sc1,
),
),
],
),
)
],
),
),
),
barrierDismissible: false,
barrierColor: Color.fromRGBO(0, 0, 0, 0.8));
}
static hide() {
Get.back();
}
}