|
|
|
// ignore_for_file: unnecessary_null_comparison
|
|
|
|
|
|
|
|
import 'dart:io';
|
|
|
|
import 'dart:math';
|
|
|
|
|
|
|
|
/// 使用 Uint8List 数据类型
|
|
|
|
import 'dart:typed_data';
|
|
|
|
|
|
|
|
import 'package:bytedesk_kefu/mqtt/bytedesk_mqtt.dart';
|
|
|
|
import 'package:bytedesk_kefu/util/bytedesk_constants.dart';
|
|
|
|
import 'package:sp_util/sp_util.dart';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:fluttertoast/fluttertoast.dart';
|
|
|
|
import 'package:intl/intl.dart';
|
|
|
|
// import 'package:flutter_image_compress/flutter_image_compress.dart';
|
|
|
|
// import 'package:permission_handler/permission_handler.dart';
|
|
|
|
/// 使用 DefaultCacheManager 类(可能无法自动引入,需要手动引入)
|
|
|
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|
|
|
|
|
|
|
/// 授权管理
|
|
|
|
// import 'package:permission_handler/permission_handler.dart';
|
|
|
|
/// 图片缓存管理
|
|
|
|
import 'package:cached_network_image/cached_network_image.dart';
|
|
|
|
|
|
|
|
/// 保存文件或图片到本地
|
|
|
|
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
|
|
|
import 'package:flutter/services.dart' show rootBundle;
|
|
|
|
|
|
|
|
class BytedeskUtils {
|
|
|
|
//
|
|
|
|
static String goodsInfo = '';
|
|
|
|
static bool get isDesktop => !isWeb && (isWindows || isLinux || isMacOS);
|
|
|
|
static bool get isMobile => isAndroid || isIOS;
|
|
|
|
static bool get isWeb => kIsWeb;
|
|
|
|
static bool get isWindows => Platform.isWindows;
|
|
|
|
static bool get isLinux => Platform.isLinux;
|
|
|
|
static bool get isMacOS => Platform.isMacOS;
|
|
|
|
static bool get isAndroid => Platform.isAndroid;
|
|
|
|
static bool get isFuchsia => Platform.isFuchsia;
|
|
|
|
static bool get isIOS => Platform.isIOS;
|
|
|
|
|
|
|
|
static void printLog(content) {
|
|
|
|
if (BytedeskConstants.isDebug) {
|
|
|
|
print(content);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static String client() {
|
|
|
|
if (isWeb) {
|
|
|
|
return 'flutter_web';
|
|
|
|
} else if (isAndroid) {
|
|
|
|
return 'flutter_android';
|
|
|
|
} else if (isIOS) {
|
|
|
|
return 'flutter_ios';
|
|
|
|
} else {
|
|
|
|
return 'flutter_web';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static String clientSchool() {
|
|
|
|
if (isWeb) {
|
|
|
|
return 'flutter_web_school';
|
|
|
|
} else if (isAndroid) {
|
|
|
|
return 'flutter_android_school';
|
|
|
|
} else if (isIOS) {
|
|
|
|
return 'flutter_ios_school';
|
|
|
|
} else {
|
|
|
|
return 'flutter_web_school';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static String getClient() {
|
|
|
|
String? role = SpUtil.getString(BytedeskConstants.role,
|
|
|
|
defValue: BytedeskConstants.ROLE_VISITOR);
|
|
|
|
// 区分用户端,还是学校端
|
|
|
|
return (role == BytedeskConstants.ROLE_VISITOR)
|
|
|
|
? BytedeskUtils.client()
|
|
|
|
: BytedeskUtils.clientSchool();
|
|
|
|
}
|
|
|
|
|
|
|
|
static String getBaseUrl() {
|
|
|
|
if (isWeb) {
|
|
|
|
return BytedeskConstants.httpBaseUrl;
|
|
|
|
} else if (isAndroid) {
|
|
|
|
return BytedeskConstants.httpBaseUrlAndroid;
|
|
|
|
} else if (isIOS) {
|
|
|
|
return BytedeskConstants.httpBaseUrliOS;
|
|
|
|
} else {
|
|
|
|
return BytedeskConstants.httpBaseUrl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 含访客登录
|
|
|
|
static bool? isLogin() {
|
|
|
|
return SpUtil.getBool(BytedeskConstants.isLogin);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 验证手机号登录
|
|
|
|
static bool? isAuthenticated() {
|
|
|
|
return SpUtil.getBool(BytedeskConstants.isAuthenticated);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 当前是否客服页面
|
|
|
|
static bool? isCurrentChatKfPage() {
|
|
|
|
return SpUtil.getBool(BytedeskConstants.isCurrentChatKfPage);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
static String? getAccessToken() {
|
|
|
|
return SpUtil.getString(BytedeskConstants.accessToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
static String? getUid() {
|
|
|
|
return SpUtil.getString(BytedeskConstants.uid);
|
|
|
|
}
|
|
|
|
|
|
|
|
static String? getNickname() {
|
|
|
|
return SpUtil.getString(BytedeskConstants.nickname);
|
|
|
|
}
|
|
|
|
|
|
|
|
static String? getAvatar() {
|
|
|
|
return SpUtil.getString(BytedeskConstants.avatar);
|
|
|
|
}
|
|
|
|
|
|
|
|
static String? getDescription() {
|
|
|
|
return SpUtil.getString(BytedeskConstants.description);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool mqttConnect() {
|
|
|
|
var isLogin = SpUtil.getBool(BytedeskConstants.isLogin);
|
|
|
|
if (isLogin!) {
|
|
|
|
new BytedeskMqtt().connect();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool mqttReConnect() {
|
|
|
|
bool isConnected = new BytedeskMqtt().isConnected();
|
|
|
|
if (!isConnected) {
|
|
|
|
new BytedeskMqtt().connect();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isMqttConnected() {
|
|
|
|
return new BytedeskMqtt().isConnected();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mqttDisconnect() {
|
|
|
|
new BytedeskMqtt().disconnect();
|
|
|
|
}
|
|
|
|
|
|
|
|
static double? getLatitude() {
|
|
|
|
if (isWeb) {
|
|
|
|
return SpUtil.getDouble(BytedeskConstants.latitude);
|
|
|
|
} else if (isAndroid) {
|
|
|
|
return SpUtil.getDouble(BytedeskConstants.latitude);
|
|
|
|
} else {
|
|
|
|
return double.parse(SpUtil.getString(BytedeskConstants.latitude)!);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static double? getLongtitude() {
|
|
|
|
if (isWeb) {
|
|
|
|
return SpUtil.getDouble(BytedeskConstants.longtitude);
|
|
|
|
} else if (isAndroid) {
|
|
|
|
return SpUtil.getDouble(BytedeskConstants.longtitude);
|
|
|
|
} else {
|
|
|
|
return double.parse(SpUtil.getString(BytedeskConstants.longtitude)!);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// static Color string2Color(String colorString) {
|
|
|
|
// int value = 0x00000000;
|
|
|
|
|
|
|
|
// if (isNotEmpty(colorString)) {
|
|
|
|
// if (colorString[0] == '#') {
|
|
|
|
// colorString = colorString.substring(1);
|
|
|
|
// }
|
|
|
|
// value = int.tryParse(colorString, radix: 16);
|
|
|
|
// if (value != null) {
|
|
|
|
// if (value < 0xFF000000) {
|
|
|
|
// value += 0xFF000000;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// return Color(value);
|
|
|
|
// }
|
|
|
|
|
|
|
|
static String formatedDateNow() {
|
|
|
|
var format = new DateFormat('yyyy-MM-dd HH:mm:ss');
|
|
|
|
return format.format(new DateTime.now());
|
|
|
|
}
|
|
|
|
|
|
|
|
static String formatedTimestampNow() {
|
|
|
|
var format = new DateFormat('yyyyMMddHHmmss');
|
|
|
|
return format.format(new DateTime.now());
|
|
|
|
}
|
|
|
|
|
|
|
|
static Map url2query(String url) {
|
|
|
|
var search = new RegExp('([^&=]+)=?([^&]*)');
|
|
|
|
var result = new Map();
|
|
|
|
|
|
|
|
// Get rid off the beginning ? in query strings.
|
|
|
|
if (url.startsWith('?')) url = url.substring(1);
|
|
|
|
|
|
|
|
// A custom decoder.
|
|
|
|
decode(String s) => Uri.decodeComponent(s.replaceAll('+', ' '));
|
|
|
|
|
|
|
|
// Go through all the matches and build the result map.
|
|
|
|
for (Match match in search.allMatches(url)) {
|
|
|
|
result[decode(match.group(1)!)] = decode(match.group(2)!);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Color randomColor() {
|
|
|
|
return Color((Random().nextDouble() * 0xFFFFFF).toInt() << 0)
|
|
|
|
.withOpacity(1.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static String removeDecimalZeroFormat(double n) {
|
|
|
|
return n.toStringAsFixed(n.truncateToDouble() == n ? 0 : 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static String getDateStr(DateTime date) {
|
|
|
|
if (date.toString() == null) {
|
|
|
|
return "";
|
|
|
|
} else if (date.toString().length < 10) {
|
|
|
|
return date.toString();
|
|
|
|
}
|
|
|
|
return date.toString().substring(0, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int currentTimeMillis() {
|
|
|
|
return new DateTime.now().millisecondsSinceEpoch;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 图片压缩 https://pub.dev/packages/flutter_image_compress
|
|
|
|
// static Future<File> compressImage(File file, String targetPath) async {
|
|
|
|
// // BytedeskUtils.printLog("compressImage");
|
|
|
|
// final result = await FlutterImageCompress.compressAndGetFile(
|
|
|
|
// file.absolute.path,
|
|
|
|
// targetPath,
|
|
|
|
// quality: 60,
|
|
|
|
// minWidth: 256,
|
|
|
|
// minHeight: 256,
|
|
|
|
// rotate: 0,
|
|
|
|
// );
|
|
|
|
// return result;
|
|
|
|
// }
|
|
|
|
|
|
|
|
//
|
|
|
|
static const double MILLIS_LIMIT = 1000.0;
|
|
|
|
|
|
|
|
static const double SECONDS_LIMIT = 60 * MILLIS_LIMIT;
|
|
|
|
|
|
|
|
static const double MINUTES_LIMIT = 60 * SECONDS_LIMIT;
|
|
|
|
|
|
|
|
static const double HOURS_LIMIT = 24 * MINUTES_LIMIT;
|
|
|
|
|
|
|
|
static const double DAYS_LIMIT = 30 * HOURS_LIMIT;
|
|
|
|
|
|
|
|
static String getNewsTimeStr(DateTime date) {
|
|
|
|
int subTime =
|
|
|
|
DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch;
|
|
|
|
|
|
|
|
if (subTime < MILLIS_LIMIT) {
|
|
|
|
return "刚刚";
|
|
|
|
} else if (subTime < SECONDS_LIMIT) {
|
|
|
|
return (subTime / MILLIS_LIMIT).round().toString() + " 秒前";
|
|
|
|
} else if (subTime < MINUTES_LIMIT) {
|
|
|
|
return (subTime / SECONDS_LIMIT).round().toString() + " 分钟前";
|
|
|
|
} else if (subTime < HOURS_LIMIT) {
|
|
|
|
return (subTime / MINUTES_LIMIT).round().toString() + " 小时前";
|
|
|
|
} else if (subTime < DAYS_LIMIT) {
|
|
|
|
return (subTime / HOURS_LIMIT).round().toString() + " 天前";
|
|
|
|
} else {
|
|
|
|
return getDateStr(date);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static String getTimeDuration(String comTime) {
|
|
|
|
var nowTime = DateTime.now();
|
|
|
|
var compareTime = DateTime.parse(comTime);
|
|
|
|
if (nowTime.isAfter(compareTime)) {
|
|
|
|
if (nowTime.year == compareTime.year) {
|
|
|
|
if (nowTime.month == compareTime.month) {
|
|
|
|
if (nowTime.day == compareTime.day) {
|
|
|
|
// if (nowTime.hour == compareTime.hour) {
|
|
|
|
// if (nowTime.minute == compareTime.minute) {
|
|
|
|
//
|
|
|
|
return compareTime.hour.toString() +
|
|
|
|
':' +
|
|
|
|
((compareTime.minute < 10)
|
|
|
|
? '0' + compareTime.minute.toString()
|
|
|
|
: compareTime.minute.toString());
|
|
|
|
// }
|
|
|
|
// return (nowTime.minute - compareTime.minute).toString() + '分钟前';
|
|
|
|
// }
|
|
|
|
// return (nowTime.hour - compareTime.hour).toString() + '小时前';
|
|
|
|
}
|
|
|
|
return (nowTime.day - compareTime.day).toString() + '天前';
|
|
|
|
}
|
|
|
|
return (nowTime.month - compareTime.month).toString() + '月前';
|
|
|
|
}
|
|
|
|
return (nowTime.year - compareTime.year).toString() + '年前';
|
|
|
|
}
|
|
|
|
return 'time error';
|
|
|
|
}
|
|
|
|
|
|
|
|
static Future<bool> requestPermission() async {
|
|
|
|
BytedeskUtils.printLog('请求定位');
|
|
|
|
return true;
|
|
|
|
// FIXME:
|
|
|
|
// if (await Permission.location.request().isGranted) {
|
|
|
|
// return true;
|
|
|
|
// } else {
|
|
|
|
// // decorated.toast('需要定位权限!');
|
|
|
|
// return false;
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 根据两点经纬度 使用math 算出之间距离
|
|
|
|
/// https://blog.csdn.net/weixin_38025168/article/details/107491452
|
|
|
|
static double getDistance(
|
|
|
|
double lat1, double lng1, double lat2, double lng2) {
|
|
|
|
/// 单位:米
|
|
|
|
double def = 6378137.0;
|
|
|
|
double radLat1 = _rad(lat1);
|
|
|
|
double radLat2 = _rad(lat2);
|
|
|
|
double a = radLat1 - radLat2;
|
|
|
|
double b = _rad(lng1) - _rad(lng2);
|
|
|
|
double s = 2 *
|
|
|
|
asin(sqrt(pow(sin(a / 2), 2) +
|
|
|
|
cos(radLat1) * cos(radLat2) * pow(sin(b / 2), 2)));
|
|
|
|
return (s * def).roundToDouble() / 1000; // 返回千米
|
|
|
|
}
|
|
|
|
|
|
|
|
static double _rad(double d) {
|
|
|
|
return d * pi / 180.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// static Color textColor(BuildContext context) {
|
|
|
|
// return ThemeUtils.isDark(context) ? Colours.dark_text : Colours.text;
|
|
|
|
// }
|
|
|
|
|
|
|
|
//销量转换
|
|
|
|
static String calculateSales(int sales) {
|
|
|
|
String prefix = '已拼';
|
|
|
|
if (sales >= 10000 && sales < 100000) {
|
|
|
|
return '$prefix${(sales / 10000).toStringAsFixed(1)}万件';
|
|
|
|
} else if (sales >= 100000) {
|
|
|
|
return '${prefix}10万+件';
|
|
|
|
}
|
|
|
|
return '$prefix$sales件';
|
|
|
|
}
|
|
|
|
|
|
|
|
//价格转换
|
|
|
|
static String convertPrice(int price) {
|
|
|
|
String result = (price / 100).toString();
|
|
|
|
//如果小数点后只有1位,且为 0
|
|
|
|
if (result.endsWith('.0')) {
|
|
|
|
return result.substring(0, result.lastIndexOf('.'));
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 保存图片到相册
|
|
|
|
/// 默认为下载网络图片,如需下载资源图片,需要指定 [isAsset] 为 `true`。
|
|
|
|
static Future<void> saveImage(String imageUrl, {bool isAsset: false}) async {
|
|
|
|
try {
|
|
|
|
// if (imageUrl == null) throw '保存失败,图片不存在!';
|
|
|
|
|
|
|
|
/// 权限检测
|
|
|
|
// PermissionStatus storageStatus = await Permission.storage.status;
|
|
|
|
// if (storageStatus != PermissionStatus.granted) {
|
|
|
|
// storageStatus = await Permission.storage.request();
|
|
|
|
// if (storageStatus != PermissionStatus.granted) {
|
|
|
|
// throw '无法存储图片,请先授权!';
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
/// 保存的图片数据
|
|
|
|
Uint8List imageBytes;
|
|
|
|
|
|
|
|
if (isAsset == true) {
|
|
|
|
/// 保存资源图片
|
|
|
|
ByteData bytes = await rootBundle.load(imageUrl);
|
|
|
|
imageBytes = bytes.buffer.asUint8List();
|
|
|
|
} else {
|
|
|
|
/// 保存网络图片
|
|
|
|
CachedNetworkImage image = CachedNetworkImage(imageUrl: imageUrl);
|
|
|
|
DefaultCacheManager manager = DefaultCacheManager();
|
|
|
|
Map<String, String>? headers = image.httpHeaders;
|
|
|
|
File file = await manager.getSingleFile(
|
|
|
|
image.imageUrl,
|
|
|
|
headers: headers,
|
|
|
|
);
|
|
|
|
imageBytes = await file.readAsBytes();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 保存图片
|
|
|
|
final result = await ImageGallerySaver.saveImage(imageBytes);
|
|
|
|
if (result == null || result == '') throw '图片保存失败';
|
|
|
|
// 提示
|
|
|
|
Fluttertoast.showToast(msg: "保存成功");
|
|
|
|
BytedeskUtils.printLog("保存成功");
|
|
|
|
} catch (e) {
|
|
|
|
BytedeskUtils.printLog(e.toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 退出登录
|
|
|
|
static void exitLogin(BuildContext context) {
|
|
|
|
// 友盟统计:取消用户账号
|
|
|
|
// UmengCommonSdk.onProfileSignOff();
|
|
|
|
// 清空本地
|
|
|
|
// SpUtil.putBool(BytedeskConstants.isLogin, false);
|
|
|
|
// SpUtil.clear();
|
|
|
|
// NavigatorUtils.push(context, LoginRouter.loginPage, clearStack: true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 清空本地缓存
|
|
|
|
static void clearUserCache() {
|
|
|
|
//
|
|
|
|
SpUtil.putString(BytedeskConstants.uid, '');
|
|
|
|
SpUtil.putString(BytedeskConstants.username, '');
|
|
|
|
SpUtil.putString(BytedeskConstants.nickname, '');
|
|
|
|
SpUtil.putString(BytedeskConstants.avatar, '');
|
|
|
|
SpUtil.putString(BytedeskConstants.description, '');
|
|
|
|
SpUtil.putString(BytedeskConstants.subDomain, '');
|
|
|
|
SpUtil.putString(BytedeskConstants.role, '');
|
|
|
|
//
|
|
|
|
SpUtil.putString(BytedeskConstants.unionid, '');
|
|
|
|
SpUtil.putString(BytedeskConstants.openid, '');
|
|
|
|
//
|
|
|
|
SpUtil.putBool(BytedeskConstants.isLogin, false);
|
|
|
|
SpUtil.putBool(BytedeskConstants.isAuthenticated, false);
|
|
|
|
SpUtil.putString(BytedeskConstants.mobile, '');
|
|
|
|
SpUtil.putString(BytedeskConstants.accessToken, '');
|
|
|
|
}
|
|
|
|
}
|