funcs.dart 22 KB

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