328 lines
9.4 KiB
Dart
328 lines
9.4 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:ef/ef.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:vbvs_app/common/util/CommonVariables.dart';
|
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
|
|
Future<void> initDataEf({String key = ""}) async {
|
|
await ef.init(
|
|
'${CommonVariables.supabaseUrl}/',
|
|
key,
|
|
nosqlEnableWebsocket: false,
|
|
);
|
|
}
|
|
|
|
class MyUtils {
|
|
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 double initialScrollOffset = 0.0;
|
|
// 判断手机号格式是否正确的方法
|
|
static bool isValidPhoneNumber(String phoneNumber) {
|
|
final RegExp phoneRegExp = RegExp(r'^1[3-9]\d{9}$');
|
|
return phoneRegExp.hasMatch(phoneNumber);
|
|
}
|
|
|
|
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 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,
|
|
);
|
|
}
|
|
}
|
|
|
|
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,
|
|
// textColor: Colors.white,
|
|
// 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: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
var returnIconButtom = IconButton(
|
|
// padding: EdgeInsets.zero, // 去除默认 padding
|
|
// constraints: BoxConstraints(), // 去除最小尺寸限制
|
|
onPressed: () => Get.back(),
|
|
icon: Icon(Icons.navigate_before, size: 60.rpx),
|
|
);
|
|
|
|
var returnIconButtomAddCallback = (returnCallBack) {
|
|
return IconButton(
|
|
onPressed: () {
|
|
returnCallBack?.call();
|
|
Get.back();
|
|
},
|
|
icon: Icon(Icons.navigate_before, size: 60.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 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}) {
|
|
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: const TextStyle(
|
|
fontSize: 16,
|
|
color: Colors.white,
|
|
decoration: TextDecoration.none),
|
|
),
|
|
SizedBox(
|
|
height: 30.rpx,
|
|
),
|
|
const CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
valueColor: AlwaysStoppedAnimation<Color>(
|
|
Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
barrierDismissible: false,
|
|
barrierColor: Color.fromRGBO(0, 0, 0, 0.8));
|
|
}
|
|
|
|
static hide() {
|
|
Get.back();
|
|
}
|
|
}
|