funcs.dart 21 KB

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