funcs.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:auto_route/auto_route.dart';
  4. import 'package:bot_toast/bot_toast.dart';
  5. import 'package:device_info_plus/device_info_plus.dart';
  6. import 'package:eitc_erm_dental_flutter/app_router.gr.dart';
  7. import 'package:eitc_erm_dental_flutter/dialog/app_update_dialog.dart';
  8. import 'package:eitc_erm_dental_flutter/entity/file_prefix_info.dart';
  9. import 'package:eitc_erm_dental_flutter/entity/version_info.dart';
  10. import 'package:eitc_erm_dental_flutter/exts.dart';
  11. import 'package:eitc_erm_dental_flutter/generated/l10n.dart';
  12. import 'package:eitc_erm_dental_flutter/http/api_service.dart';
  13. import 'package:eitc_erm_dental_flutter/http/http.dart';
  14. import 'package:eitc_erm_dental_flutter/main.dart';
  15. import 'package:eitc_erm_dental_flutter/sp_util.dart';
  16. import 'package:eitc_erm_dental_flutter/vm/global_view_model.dart';
  17. import 'package:encrypt/encrypt.dart' as encrpyt;
  18. import 'package:flutter/foundation.dart';
  19. import 'package:flutter/material.dart';
  20. import 'package:flutter/services.dart';
  21. import 'package:flutter_riverpod/flutter_riverpod.dart';
  22. import 'package:flutter_screenutil/flutter_screenutil.dart';
  23. import 'package:logger/logger.dart';
  24. import 'package:network_info_plus/network_info_plus.dart';
  25. import 'package:package_info_plus/package_info_plus.dart';
  26. import 'package:path_provider/path_provider.dart';
  27. import 'package:permission_handler/permission_handler.dart';
  28. import 'global.dart';
  29. ///国际化
  30. S getS() => S.of(navigatorKey.currentState!.context);
  31. late Logger logger;
  32. ///初始化日志
  33. Future<dynamic> initLog() async {
  34. LogOutput? output;
  35. Directory? dir = await getDownloadsDirectory();
  36. if (dir != null) {
  37. dir = Directory("${dir.path}/logs");
  38. if (!await dir.exists()) {
  39. await dir.create(recursive: true);
  40. }
  41. output = kDebugMode
  42. ? MultiOutput([ConsoleOutput(), AdvancedFileOutput(path: dir.path)])
  43. : ConsoleOutput();
  44. }
  45. logger = Logger(
  46. printer: PrettyPrinter(
  47. methodCount: 5, dateTimeFormat: DateTimeFormat.dateAndTime),
  48. output: output);
  49. }
  50. void logd(
  51. dynamic message, {
  52. DateTime? time,
  53. Object? error,
  54. StackTrace? stackTrace,
  55. }) {
  56. logger.d(message,
  57. time: time ?? DateTime.now(), error: error, stackTrace: stackTrace);
  58. }
  59. void logi(
  60. dynamic message, {
  61. DateTime? time,
  62. Object? error,
  63. StackTrace? stackTrace,
  64. }) {
  65. logger.i(message,
  66. time: time ?? DateTime.now(), error: error, stackTrace: stackTrace);
  67. }
  68. void loge(
  69. dynamic message, {
  70. DateTime? time,
  71. Object? error,
  72. StackTrace? stackTrace,
  73. }) {
  74. logger.e(message,
  75. time: time ?? DateTime.now(), error: error, stackTrace: stackTrace);
  76. }
  77. void logw(
  78. dynamic message, {
  79. DateTime? time,
  80. Object? error,
  81. StackTrace? stackTrace,
  82. }) {
  83. logger.w(message,
  84. time: time ?? DateTime.now(), error: error, stackTrace: stackTrace);
  85. }
  86. void logf(
  87. dynamic message, {
  88. DateTime? time,
  89. Object? error,
  90. StackTrace? stackTrace,
  91. }) {
  92. logger.f(message,
  93. time: time ?? DateTime.now(), error: error, stackTrace: stackTrace);
  94. }
  95. ///显示toast
  96. void showToast(
  97. {required String text,
  98. Duration duration = const Duration(seconds: 1, milliseconds: 500)}) {
  99. BotToast.showText(text: text, duration: duration);
  100. }
  101. ///显示删除提示框
  102. Future<T?> showDeleteAlertDialog<T>(BuildContext context, String hint) {
  103. return showDialog(
  104. context: context,
  105. builder: (ctx) => AlertDialog(
  106. title: Text(getS().hint),
  107. content: Text(hint),
  108. actions: [
  109. TextButton(
  110. onPressed: () => {Navigator.pop(context)},
  111. child: Text(getS().cancel)),
  112. TextButton(
  113. onPressed: () => {Navigator.pop(context, true)},
  114. child: Text(getS().confirm)),
  115. ],
  116. ));
  117. }
  118. ///是否有存储权限
  119. Future<bool> hasStoragePermission() async {
  120. if (Platform.isAndroid) {
  121. AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
  122. //小于32用READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限
  123. if (info.version.sdkInt <= 32) {
  124. return await Permission.storage.isGranted;
  125. }
  126. //33及以上使用READ_MEDIA_IMAGES和READ_MEDIA_VIDEO权限
  127. return await Permission.photos.isGranted &&
  128. await Permission.videos.isGranted;
  129. } else if (Platform.isIOS) {
  130. return await Permission.photos.isGranted;
  131. }
  132. return true;
  133. }
  134. ///请求存储权限
  135. Future<bool> requestStoreagePermission() async {
  136. if (await hasStoragePermission()) {
  137. return true;
  138. }
  139. if (await SpUtil.hasStoragePermissionRequested()) {
  140. return false;
  141. }
  142. bool bo = false;
  143. if (Platform.isAndroid) {
  144. AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
  145. if (info.version.sdkInt <= 32) {
  146. bo = await Permission.storage.request().isGranted;
  147. } else {
  148. Map<Permission, PermissionStatus> result = await [
  149. Permission.photos,
  150. Permission.videos,
  151. ].request();
  152. bo = result[Permission.photos]!.isGranted &&
  153. result[Permission.videos]!.isGranted;
  154. }
  155. } else if (Platform.isIOS) {
  156. bo = await Permission.photos.request().isGranted;
  157. }
  158. await SpUtil.setStoragePermissionRequested(true);
  159. return bo;
  160. }
  161. ///是否有定位权限
  162. Future<bool> hasLocationPremission() {
  163. return Permission.locationWhenInUse.isGranted;
  164. }
  165. ///开启屏幕旋转
  166. void screenEnableRotate() {
  167. SystemChrome.setPreferredOrientations([
  168. DeviceOrientation.landscapeLeft,
  169. DeviceOrientation.landscapeRight,
  170. DeviceOrientation.portraitUp,
  171. DeviceOrientation.portraitDown
  172. ]);
  173. }
  174. ///关闭屏幕旋转
  175. void screenDisableRotate() {
  176. SystemChrome.setPreferredOrientations(
  177. [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
  178. }
  179. ///牙齿区域左上
  180. const String toothAreaLeftTop = "lt";
  181. ///牙齿区域左下
  182. const String toothAreaLeftBottom = "lb";
  183. ///牙齿区域右上
  184. const String toothAreaRightTop = "rt";
  185. ///牙齿区域右下
  186. const String toothAreaRightBottom = "rb";
  187. ///牙齿区域翻译
  188. String toothAreaTranslate(String area) {
  189. switch (area) {
  190. case toothAreaLeftTop:
  191. return getS().leftTopArea;
  192. case toothAreaLeftBottom:
  193. return getS().leftBototmArea;
  194. case toothAreaRightTop:
  195. return getS().rightTopArea;
  196. case toothAreaRightBottom:
  197. return getS().rightBottomArea;
  198. default:
  199. return getS().unselected;
  200. }
  201. }
  202. String makeFilePrefix(
  203. {required String name,
  204. required String idCard,
  205. required String mobile,
  206. required String area,
  207. required String wifi,
  208. required int time,
  209. required String userId}) {
  210. FilePrefixInfo info = FilePrefixInfo(
  211. name: name,
  212. idCard: idCard,
  213. mobile: mobile,
  214. time: time,
  215. area: area,
  216. wifi: wifi,
  217. userId: userId);
  218. String encode = info.encode();
  219. if (encode.isEmpty) {
  220. return "";
  221. }
  222. return "${encode}_";
  223. }
  224. ///解码base64
  225. String decodeBase64(String encode) {
  226. Uint8List list = base64.decode(encode);
  227. return String.fromCharCodes(list);
  228. }
  229. ///编码base64
  230. String encodeBase64(String str) {
  231. return base64.encode(utf8.encode(str));
  232. }
  233. ///验证身份证
  234. bool validIdCard(String str) {
  235. //18位
  236. return RegExp(
  237. r'^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$')
  238. .hasMatch(str) ||
  239. //15位
  240. RegExp(r'[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$')
  241. .hasMatch(str);
  242. }
  243. ///验证手机号
  244. bool validMobile(String str) {
  245. return RegExp(r'1[3-9]\d{9}$').hasMatch(str);
  246. }
  247. ///设置全屏
  248. void setFullScreen(bool isFullScreen) {
  249. SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
  250. overlays: isFullScreen ? [] : [SystemUiOverlay.top]);
  251. }
  252. ///读取WIFI名称
  253. Future<String?> getWifiName() async {
  254. //是否有权限
  255. bool hasLocationPermission = await hasLocationPremission();
  256. if (!hasLocationPermission) {
  257. if (await SpUtil.hasLocationPermissionRequested()) {
  258. logd("读取wifi名称没有位置权限且已经请求过位置权限");
  259. return null;
  260. }
  261. if (navigatorKey.currentState!.mounted) {
  262. logd("弹窗提示要请求位置权限");
  263. await showDialog(
  264. context: navigatorKey.currentState!.context,
  265. builder: (ctx) {
  266. return AlertDialog(
  267. title: Text(getS().hint),
  268. content: Text(getS().requestLocationHint),
  269. actions: [
  270. TextButton(
  271. onPressed: () =>
  272. Navigator.of(navigatorKey.currentState!.context).pop(),
  273. child: Text(getS().confirm))
  274. ],
  275. shape: RoundedRectangleBorder(
  276. borderRadius: BorderRadius.circular(15.r)),
  277. );
  278. });
  279. logd("请求位置权限");
  280. PermissionStatus status = await Permission.locationWhenInUse.request();
  281. await SpUtil.setLocationPermissionRequested(true);
  282. hasLocationPermission = status == PermissionStatus.granted;
  283. }
  284. }
  285. if (!hasLocationPermission) {
  286. logd("读取wifi名称没有位置权限");
  287. return null;
  288. }
  289. NetworkInfo networkInfo = NetworkInfo();
  290. String? name = await networkInfo.getWifiName();
  291. if (name == null) {
  292. logd("读取wifi名称返回null");
  293. return null;
  294. }
  295. logd("读取wifi名称=$name");
  296. //android返回的wifi名称里带双引号
  297. if (name.contains('"')) {
  298. name = name.replaceAll(RegExp(r'"'), "");
  299. }
  300. return name;
  301. }
  302. ///获取设备型号,根据wifi名字获取,如果wifi名字为空就返回null
  303. Future<String?> getDeviceModel() async {
  304. String? wifiName = await getWifiName();
  305. if (wifiName.isNullOrEmpty) {
  306. return null;
  307. }
  308. return wifiName!.replaceAll(deviceWifiPrefix, "");
  309. }
  310. ///是否是设备WIFI,用来判断是否连接了设备
  311. Future<bool> isDeviceWifi() async {
  312. String? name = await getWifiName();
  313. //没有读取到wifi名称就默认是外网
  314. if (name.isNullOrEmpty) {
  315. return false;
  316. }
  317. return SynchronousFuture(name!.startsWith(deviceWifiPrefix));
  318. }
  319. ///检查是否外网WIFI
  320. ///
  321. /// [isShowToast] 是否弹提示
  322. Future<bool> checkInternetWifi([bool isShowToast = true]) async {
  323. if (await isDeviceWifi()) {
  324. if (isShowToast) {
  325. showToast(text: getS().pleaseConnectInternetWifi);
  326. }
  327. return false;
  328. }
  329. return true;
  330. }
  331. ///检查是否已登录
  332. ///
  333. /// [toLogin] 是否跳转登录页面
  334. /// [cancelable] 登录页面是否可以取消,如果可以取消,则只push登录页面,否则将replace掉所有其他页面
  335. bool checkLogin(BuildContext context,
  336. {bool toLogin = true, bool cancelable = true}) {
  337. if (hasToken) {
  338. return true;
  339. }
  340. if (toLogin && context.mounted) {
  341. if (cancelable) {
  342. context.pushRoute(LoginRoute(cancelable: cancelable));
  343. } else {
  344. popAllRoutes(context);
  345. context.pushRoute(LoginRoute(cancelable: false));
  346. }
  347. }
  348. return false;
  349. }
  350. ///前往用户协议
  351. void gotoUserAgreement(BuildContext context, {bool checkWifi = true}) async {
  352. if (checkWifi) {
  353. if (!await checkInternetWifi()) {
  354. return;
  355. }
  356. }
  357. if (!context.mounted) {
  358. return;
  359. }
  360. context.pushRoute(WebviewRoute(
  361. url: "",
  362. title: getS().userAgreement,
  363. htmlFuture: ApiService(Http.instance.dio).getUserAgreement()));
  364. }
  365. ///前往隐私协议
  366. void gotoPrivacyPolicy(BuildContext context, {bool checkWifi = true}) async {
  367. if (checkWifi) {
  368. if (!await checkInternetWifi()) {
  369. return;
  370. }
  371. }
  372. if (!context.mounted) {
  373. return;
  374. }
  375. context.pushRoute(WebviewRoute(
  376. url: "",
  377. title: getS().privacyPolicy,
  378. htmlFuture: ApiService(Http.instance.dio).getPrivacyPolicy()));
  379. }
  380. ///前往权限说明
  381. void gotoPermissionDesc(BuildContext context, {bool checkWifi = true}) async {
  382. if (checkWifi) {
  383. if (!await checkInternetWifi()) {
  384. return;
  385. }
  386. }
  387. if (!context.mounted) {
  388. return;
  389. }
  390. context.pushRoute(WebviewRoute(
  391. url: "",
  392. title: getS().permissionDescription,
  393. htmlFuture: ApiService(Http.instance.dio)
  394. .getPermissionDescription(Platform.isIOS ? "ios" : "android")));
  395. }
  396. ///给身份证加星号
  397. String setIdCardStar(String idCard) {
  398. if (idCard.length <= 6) {
  399. return idCard;
  400. }
  401. return "${idCard.substring(0, 3).padRight(idCard.length - 3, "*")}${idCard.substring(idCard.length - 3)}";
  402. }
  403. ///设置已选择的咨询人ID
  404. Future<bool> setSelectedPatientId(int id) async {
  405. bool bo = await SpUtil.setSelectedPatientId(id);
  406. if (bo) {
  407. selectedPatientId = id;
  408. }
  409. return bo;
  410. }
  411. ///从身份证中获取性别,0男性,1女性,-1错误
  412. int getGenderFromIdCard(String idCard) {
  413. if (!(idCard.length == 15 || idCard.length == 18)) {
  414. return -1;
  415. }
  416. try {
  417. //15位身份证最后3位奇数男性,偶数女性
  418. //18位身份证第17位奇数男性,偶数女性
  419. int sex = int.parse(
  420. idCard.length == 15 ? idCard.substring(12) : idCard.substring(16, 17));
  421. return sex.isOdd ? 0 : 1;
  422. } catch (e) {
  423. loge("从身份证中解析性别异常", error: e);
  424. }
  425. return -1;
  426. }
  427. ///从身份证中获取年龄,-1错误
  428. int getAgeFromIdCard(String idCard) {
  429. if (!(idCard.length == 15 || idCard.length == 18)) {
  430. return -1;
  431. }
  432. try {
  433. int year, month, day;
  434. //18位身份证
  435. if (idCard.length == 18) {
  436. year = int.parse(idCard.substring(6, 10));
  437. month = int.parse(idCard.substring(10, 12));
  438. day = int.parse(idCard.substring(12, 14));
  439. }
  440. //15位身份证
  441. else {
  442. //6,7位表示出生年份后两位
  443. String str = idCard.substring(6, 8);
  444. int a = int.parse(str);
  445. int nowYear = DateTime.now().year;
  446. int thousandYear = int.parse("${"$nowYear".substring(0, 2)}00");
  447. //如果身份证年份后两位比现在大,就把现在的千年减去100再加上后两位
  448. if (a > nowYear - thousandYear) {
  449. year = thousandYear - 100 + a;
  450. }
  451. //否则就用现在的千年加上后两位
  452. else {
  453. year = thousandYear + a;
  454. }
  455. month = int.parse(idCard.substring(8, 10));
  456. day = int.parse(idCard.substring(10, 12));
  457. }
  458. //生日
  459. DateTime birth = DateTime(year, month, day);
  460. //年龄
  461. int age = (DateTime.now().difference(birth).inDays / 365.0).floor();
  462. //logd("从身份证中获取年龄,year=$year,month=$month,day=$day,age=$age");
  463. return age;
  464. } catch (e) {
  465. loge("从身份证中获取年龄异常", error: e);
  466. }
  467. return -1;
  468. }
  469. final _key = encrpyt.Key.fromUtf8("1234567890123456");
  470. final _iv = encrpyt.IV.fromUtf8("1234567890123456");
  471. final _encrypter = encrpyt.Encrypter(
  472. encrpyt.AES(_key, mode: encrpyt.AESMode.cbc, padding: "PKCS7"));
  473. ///AES加密,返回base64
  474. String aesEncrypt(String str) {
  475. if (str.isEmpty) {
  476. return "";
  477. }
  478. var encrypted = _encrypter.encrypt(str, iv: _iv);
  479. return encrypted.base64;
  480. }
  481. ///AES解密,使用base64
  482. String aesDecrypt(String str) {
  483. if (str.isEmpty) {
  484. return "";
  485. }
  486. var encrptyed = encrpyt.Encrypted.from64(str);
  487. return _encrypter.decrypt(encrptyed, iv: _iv);
  488. }
  489. ///检查新版本
  490. ///
  491. /// 返回true表示有新版本,false没有新版本
  492. Future<bool> checkNewVersion(BuildContext context, WidgetRef ref) async {
  493. VersionInfo? info = await ref
  494. .read(checkNewVersionProvider.notifier)
  495. .checkNewVersion(Platform.isIOS ? "ios" : "android");
  496. if (info == null || info.code.isNullOrEmpty) {
  497. logd("检查更新,服务器返回信息为null,或code为空");
  498. return false;
  499. }
  500. PackageInfo packageInfo = await PackageInfo.fromPlatform();
  501. logd(
  502. "检查更新,当前version=${packageInfo.version},当前code=${packageInfo.buildNumber},服务器version=${info.version},服务器code=${info.code}");
  503. int versionCode = 0;
  504. int serverCode = 0;
  505. try {
  506. versionCode = int.parse(packageInfo.buildNumber);
  507. serverCode = int.parse(info.code ?? "0");
  508. } catch (e) {
  509. loge("检查更新转换版本号异常", error: e);
  510. }
  511. //ios的version和code都需要比较,因为对于同一版本,ios发布时会自动提升code
  512. //不同版本又会从pubspec.yaml里填写的code重新计算
  513. if (Platform.isIOS) {
  514. if (info.version.isNullOrEmpty) {
  515. logd("ios比较版本,服务器version为空");
  516. return false;
  517. }
  518. //把服务器的版本号转为数字数组
  519. List<int> serverVersionNums = info.version!.split(".").map((e) {
  520. try {
  521. return int.parse(e);
  522. } catch (e) {
  523. loge("转换服务器版本字符到异常", error: e);
  524. }
  525. return 0;
  526. }).toList();
  527. //把本地的版本号转为数字数组
  528. List<int> currentVersionNums = packageInfo.version.split(".").map((e) {
  529. try {
  530. return int.parse(e);
  531. } catch (e) {
  532. loge("转换本地版本字符到异常", error: e);
  533. }
  534. return 0;
  535. }).toList();
  536. //如果本地长度小于服务器长度,则补齐本地数组
  537. if (currentVersionNums.length < serverVersionNums.length) {
  538. currentVersionNums.addAll(List.filled(
  539. serverVersionNums.length - currentVersionNums.length, -1));
  540. }
  541. //从左到右比较数字大小,只要服务器有一个大于本地的就认为需要升级
  542. bool needUpdate = false;
  543. bool sameVersion = true;
  544. for (int i = 0; i < serverVersionNums.length; i++) {
  545. if (serverVersionNums[i] != currentVersionNums[i]) {
  546. sameVersion = false;
  547. }
  548. if (serverVersionNums[i] > currentVersionNums[i]) {
  549. needUpdate = true;
  550. break;
  551. }
  552. }
  553. //如果版本号都一致,就判断code的大小
  554. if (!needUpdate && sameVersion && serverCode > versionCode) {
  555. needUpdate = true;
  556. }
  557. return needUpdate;
  558. }
  559. //其他的只需要判断code
  560. else {
  561. if (versionCode >= serverCode) {
  562. return false;
  563. }
  564. }
  565. if (context.mounted) {
  566. showDialog(
  567. context: context,
  568. builder: (ctx) {
  569. return AppUpdateDialog(
  570. version: info.version!,
  571. content: info.content ?? "",
  572. url: info.downloadUrl ?? "",
  573. isForce: info.mandatoryUpdate == "1");
  574. });
  575. }
  576. return true;
  577. }
  578. ///登出清空数据
  579. Future<dynamic> logoutClearData() async {
  580. updateToken("");
  581. await SpUtil.setUserId("");
  582. await SpUtil.setUserName("");
  583. await SpUtil.setToken("");
  584. await setSelectedPatientId(-1);
  585. }
  586. ///弹出所有路由
  587. void popAllRoutes(BuildContext context) {
  588. AutoRouter.of(context).popUntil((_) => false);
  589. }
  590. ///是否是慧视健康
  591. bool get isHsjk => appFlavor == flavorHsjk;
  592. ///是否是慧视通
  593. bool get isHst => appFlavor == flavorHst;
  594. ///app名字
  595. String get appName =>
  596. switch (appFlavor) { flavorHst => getS().appNameHst, _ => getS().appName };
  597. ///退出APP
  598. void exitApp() async {
  599. await SystemNavigator.pop();
  600. //exit(0);
  601. }