123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755 |
- import 'dart:convert';
- import 'dart:io';
- import 'package:auto_route/auto_route.dart';
- import 'package:bot_toast/bot_toast.dart';
- import 'package:device_info_plus/device_info_plus.dart';
- import 'package:eitc_erm_dental_flutter/app_router.gr.dart';
- import 'package:eitc_erm_dental_flutter/dialog/app_update_dialog.dart';
- import 'package:eitc_erm_dental_flutter/entity/file_prefix_info.dart';
- import 'package:eitc_erm_dental_flutter/entity/version_info.dart';
- import 'package:eitc_erm_dental_flutter/exts.dart';
- import 'package:eitc_erm_dental_flutter/generated/l10n.dart';
- import 'package:eitc_erm_dental_flutter/http/api_service.dart';
- import 'package:eitc_erm_dental_flutter/http/http.dart';
- import 'package:eitc_erm_dental_flutter/main.dart';
- import 'package:eitc_erm_dental_flutter/sp_util.dart';
- import 'package:eitc_erm_dental_flutter/vm/global_view_model.dart';
- import 'package:encrypt/encrypt.dart' as encrpyt;
- import 'package:flutter/foundation.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:flutter_screenutil/flutter_screenutil.dart';
- import 'package:logger/logger.dart';
- import 'package:network_info_plus/network_info_plus.dart';
- import 'package:package_info_plus/package_info_plus.dart';
- import 'package:path_provider/path_provider.dart';
- import 'package:permission_handler/permission_handler.dart';
- import 'package:pointycastle/asymmetric/pkcs1.dart';
- import 'package:pointycastle/asymmetric/rsa.dart';
- import 'package:pointycastle/pointycastle.dart';
- import 'global.dart';
- ///国际化
- S getS() => S.of(navigatorKey.currentState!.context);
- late Logger logger;
- ///初始化日志
- Future<dynamic> initLog() async {
- LogOutput? output;
- //android版有文件日志
- if (Platform.isAndroid) {
- Directory? dir = await getDownloadsDirectory();
- if (dir != null) {
- dir = Directory("${dir.path}/logs");
- if (!await dir.exists()) {
- await dir.create(recursive: true);
- }
- output = kDebugMode
- ? MultiOutput([ConsoleOutput(), AdvancedFileOutput(path: dir.path)])
- : ConsoleOutput();
- }
- } else {
- output = ConsoleOutput();
- }
- logger = Logger(
- printer: PrettyPrinter(
- methodCount: 5, dateTimeFormat: DateTimeFormat.dateAndTime),
- output: output);
- }
- void logd(
- dynamic message, {
- DateTime? time,
- Object? error,
- StackTrace? stackTrace,
- }) {
- logger.d(message,
- time: time ?? DateTime.now(), error: error, stackTrace: stackTrace);
- }
- void logi(
- dynamic message, {
- DateTime? time,
- Object? error,
- StackTrace? stackTrace,
- }) {
- logger.i(message,
- time: time ?? DateTime.now(), error: error, stackTrace: stackTrace);
- }
- void loge(
- dynamic message, {
- DateTime? time,
- Object? error,
- StackTrace? stackTrace,
- }) {
- logger.e(message,
- time: time ?? DateTime.now(), error: error, stackTrace: stackTrace);
- }
- void logw(
- dynamic message, {
- DateTime? time,
- Object? error,
- StackTrace? stackTrace,
- }) {
- logger.w(message,
- time: time ?? DateTime.now(), error: error, stackTrace: stackTrace);
- }
- void logf(
- dynamic message, {
- DateTime? time,
- Object? error,
- StackTrace? stackTrace,
- }) {
- logger.f(message,
- time: time ?? DateTime.now(), error: error, stackTrace: stackTrace);
- }
- ///显示toast
- void showToast(
- {required String text,
- Duration duration = const Duration(seconds: 1, milliseconds: 500)}) {
- BotToast.showText(text: text, duration: duration);
- }
- ///显示删除提示框
- Future<T?> showDeleteAlertDialog<T>(BuildContext context, String hint) {
- return showDialog(
- context: context,
- builder: (ctx) => AlertDialog(
- title: Text(getS().hint),
- content: Text(hint),
- actions: [
- TextButton(
- onPressed: () => {Navigator.pop(context)},
- child: Text(getS().cancel)),
- TextButton(
- onPressed: () => {Navigator.pop(context, true)},
- child: Text(getS().confirm)),
- ],
- ));
- }
- ///是否有存储权限
- Future<bool> hasStoragePermission() async {
- if (Platform.isAndroid) {
- AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
- //小于32用READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限
- if (info.version.sdkInt <= 32) {
- return await Permission.storage.isGranted;
- }
- //33及以上使用READ_MEDIA_IMAGES和READ_MEDIA_VIDEO权限
- return await Permission.photos.isGranted &&
- await Permission.videos.isGranted;
- } else if (Platform.isIOS) {
- return await Permission.photos.isGranted;
- }
- return true;
- }
- ///请求存储权限
- Future<bool> requestStoreagePermission() async {
- if (await hasStoragePermission()) {
- return true;
- }
- if (await SpUtil.hasStoragePermissionRequested()) {
- return false;
- }
- bool bo = false;
- if (Platform.isAndroid) {
- AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
- if (info.version.sdkInt <= 32) {
- bo = await Permission.storage.request().isGranted;
- } else {
- Map<Permission, PermissionStatus> result = await [
- Permission.photos,
- Permission.videos,
- ].request();
- bo = result[Permission.photos]!.isGranted &&
- result[Permission.videos]!.isGranted;
- }
- } else if (Platform.isIOS) {
- bo = await Permission.photos.request().isGranted;
- }
- await SpUtil.setStoragePermissionRequested(true);
- return bo;
- }
- ///是否有定位权限
- Future<bool> hasLocationPremission() {
- return Permission.locationWhenInUse.isGranted;
- }
- ///开启屏幕旋转
- void screenEnableRotate() {
- SystemChrome.setPreferredOrientations([
- DeviceOrientation.landscapeLeft,
- DeviceOrientation.landscapeRight,
- DeviceOrientation.portraitUp,
- DeviceOrientation.portraitDown
- ]);
- }
- ///关闭屏幕旋转
- void screenDisableRotate() {
- SystemChrome.setPreferredOrientations(
- [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
- }
- ///牙齿区域左上
- const String toothAreaLeftTop = "lt";
- ///牙齿区域左下
- const String toothAreaLeftBottom = "lb";
- ///牙齿区域右上
- const String toothAreaRightTop = "rt";
- ///牙齿区域右下
- const String toothAreaRightBottom = "rb";
- ///牙齿区域翻译
- String toothAreaTranslate(String area) {
- switch (area) {
- case toothAreaLeftTop:
- return getS().leftTopArea;
- case toothAreaLeftBottom:
- return getS().leftBototmArea;
- case toothAreaRightTop:
- return getS().rightTopArea;
- case toothAreaRightBottom:
- return getS().rightBottomArea;
- default:
- return getS().unselected;
- }
- }
- String makeFilePrefix(
- {required String name,
- required String idCard,
- required String mobile,
- required String area,
- required String wifi,
- required int time,
- required String userId}) {
- FilePrefixInfo info = FilePrefixInfo(
- name: name,
- idCard: idCard,
- mobile: mobile,
- time: time,
- area: area,
- wifi: wifi,
- userId: userId);
- String encode = info.encode();
- if (encode.isEmpty) {
- return "";
- }
- return "${encode}_";
- }
- ///解码base64
- String decodeBase64(String encode) {
- Uint8List list = base64.decode(encode);
- return String.fromCharCodes(list);
- }
- ///编码base64
- String encodeBase64(String str) {
- return base64.encode(utf8.encode(str));
- }
- ///验证身份证
- bool validIdCard(String str) {
- //18位
- return RegExp(
- 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]$')
- .hasMatch(str) ||
- //15位
- RegExp(r'[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$')
- .hasMatch(str);
- }
- ///验证手机号
- bool validMobile(String str) {
- return RegExp(r'1[3-9]\d{9}$').hasMatch(str);
- }
- ///设置全屏
- void setFullScreen(bool isFullScreen) {
- SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
- overlays: isFullScreen ? [] : [SystemUiOverlay.top]);
- }
- ///读取WIFI名称
- Future<String?> getWifiName() async {
- //是否有权限
- bool hasLocationPermission = await hasLocationPremission();
- if (!hasLocationPermission) {
- if (await SpUtil.hasLocationPermissionRequested()) {
- logd("读取wifi名称没有位置权限且已经请求过位置权限");
- return null;
- }
- if (navigatorKey.currentState!.mounted) {
- logd("弹窗提示要请求位置权限");
- await showDialog(
- context: navigatorKey.currentState!.context,
- builder: (ctx) {
- return AlertDialog(
- title: Text(getS().hint),
- content: Text(getS().requestLocationHint),
- actions: [
- TextButton(
- onPressed: () =>
- Navigator.of(navigatorKey.currentState!.context).pop(),
- child: Text(getS().confirm))
- ],
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(15.r)),
- );
- });
- logd("请求位置权限");
- PermissionStatus status = await Permission.locationWhenInUse.request();
- await SpUtil.setLocationPermissionRequested(true);
- hasLocationPermission = status == PermissionStatus.granted;
- }
- }
- if (!hasLocationPermission) {
- logd("读取wifi名称没有位置权限");
- return null;
- }
- NetworkInfo networkInfo = NetworkInfo();
- String? name = await networkInfo.getWifiName();
- if (name == null) {
- logd("读取wifi名称返回null");
- return null;
- }
- logd("读取wifi名称=$name");
- //android返回的wifi名称里带双引号
- if (name.contains('"')) {
- name = name.replaceAll(RegExp(r'"'), "");
- }
- return name;
- }
- ///获取设备型号,根据wifi名字获取,如果wifi名字为空就返回null
- Future<String?> getDeviceModel() async {
- String? wifiName = await getWifiName();
- if (wifiName.isNullOrEmpty) {
- return null;
- }
- return wifiName!.replaceAll(deviceWifiPrefix, "");
- }
- ///是否是设备WIFI,用来判断是否连接了设备
- Future<bool> isDeviceWifi() async {
- String? name = await getWifiName();
- //没有读取到wifi名称就默认是外网
- if (name.isNullOrEmpty) {
- return false;
- }
- return SynchronousFuture(name!.startsWith(deviceWifiPrefix));
- }
- ///检查是否外网WIFI
- ///
- /// [isShowToast] 是否弹提示
- Future<bool> checkInternetWifi([bool isShowToast = true]) async {
- if (await isDeviceWifi()) {
- if (isShowToast) {
- showToast(text: getS().pleaseConnectInternetWifi);
- }
- return false;
- }
- return true;
- }
- ///检查是否已登录
- ///
- /// [toLogin] 是否跳转登录页面
- /// [cancelable] 登录页面是否可以取消,如果可以取消,则只push登录页面,否则将replace掉所有其他页面
- bool checkLogin(BuildContext context,
- {bool toLogin = true, bool cancelable = true}) {
- if (hasToken) {
- return true;
- }
- if (toLogin && context.mounted) {
- if (cancelable) {
- context.pushRoute(LoginRoute(cancelable: cancelable));
- } else {
- popAllRoutes(context);
- context.pushRoute(LoginRoute(cancelable: false));
- }
- }
- return false;
- }
- ///前往用户协议
- void gotoUserAgreement(BuildContext context, {bool checkWifi = true}) async {
- if (checkWifi) {
- if (!await checkInternetWifi()) {
- return;
- }
- }
- if (!context.mounted) {
- return;
- }
- context.pushRoute(WebviewRoute(
- url: "",
- title: getS().userAgreement,
- htmlFuture: ApiService(Http.instance.dio).getUserAgreement()));
- }
- ///前往隐私协议
- void gotoPrivacyPolicy(BuildContext context, {bool checkWifi = true}) async {
- if (checkWifi) {
- if (!await checkInternetWifi()) {
- return;
- }
- }
- if (!context.mounted) {
- return;
- }
- context.pushRoute(WebviewRoute(
- url: "",
- title: getS().privacyPolicy,
- htmlFuture: ApiService(Http.instance.dio).getPrivacyPolicy()));
- }
- ///前往权限说明
- void gotoPermissionDesc(BuildContext context, {bool checkWifi = true}) async {
- if (checkWifi) {
- if (!await checkInternetWifi()) {
- return;
- }
- }
- if (!context.mounted) {
- return;
- }
- context.pushRoute(WebviewRoute(
- url: "",
- title: getS().permissionDescription,
- htmlFuture: ApiService(Http.instance.dio)
- .getPermissionDescription(Platform.isIOS ? "ios" : "android")));
- }
- ///给身份证加星号
- String setIdCardStar(String idCard) {
- if (idCard.length <= 6) {
- return idCard;
- }
- return "${idCard.substring(0, 3).padRight(idCard.length - 3, "*")}${idCard.substring(idCard.length - 3)}";
- }
- ///设置已选择的咨询人ID
- Future<bool> setSelectedPatientId(int id) async {
- bool bo = await SpUtil.setSelectedPatientId(id);
- if (bo) {
- selectedPatientId = id;
- }
- return bo;
- }
- ///从身份证中获取性别,0男性,1女性,-1错误
- int getGenderFromIdCard(String idCard) {
- if (!(idCard.length == 15 || idCard.length == 18)) {
- return -1;
- }
- try {
- //15位身份证最后3位奇数男性,偶数女性
- //18位身份证第17位奇数男性,偶数女性
- int sex = int.parse(
- idCard.length == 15 ? idCard.substring(12) : idCard.substring(16, 17));
- return sex.isOdd ? 0 : 1;
- } catch (e) {
- loge("从身份证中解析性别异常", error: e);
- }
- return -1;
- }
- ///从身份证中获取年龄,-1错误
- int getAgeFromIdCard(String idCard) {
- if (!(idCard.length == 15 || idCard.length == 18)) {
- return -1;
- }
- try {
- int year, month, day;
- //18位身份证
- if (idCard.length == 18) {
- year = int.parse(idCard.substring(6, 10));
- month = int.parse(idCard.substring(10, 12));
- day = int.parse(idCard.substring(12, 14));
- }
- //15位身份证
- else {
- //6,7位表示出生年份后两位
- String str = idCard.substring(6, 8);
- int a = int.parse(str);
- int nowYear = DateTime.now().year;
- int thousandYear = int.parse("${"$nowYear".substring(0, 2)}00");
- //如果身份证年份后两位比现在大,就把现在的千年减去100再加上后两位
- if (a > nowYear - thousandYear) {
- year = thousandYear - 100 + a;
- }
- //否则就用现在的千年加上后两位
- else {
- year = thousandYear + a;
- }
- month = int.parse(idCard.substring(8, 10));
- day = int.parse(idCard.substring(10, 12));
- }
- //生日
- DateTime birth = DateTime(year, month, day);
- //年龄
- int age = (DateTime.now().difference(birth).inDays / 365.0).floor();
- //logd("从身份证中获取年龄,year=$year,month=$month,day=$day,age=$age");
- return age;
- } catch (e) {
- loge("从身份证中获取年龄异常", error: e);
- }
- return -1;
- }
- final _key = encrpyt.Key.fromUtf8("1234567890123456");
- final _iv = encrpyt.IV.fromUtf8("1234567890123456");
- final _encrypter = encrpyt.Encrypter(
- encrpyt.AES(_key, mode: encrpyt.AESMode.cbc, padding: "PKCS7"));
- ///AES加密,返回base64
- String aesEncrypt(String str) {
- if (str.isEmpty) {
- return "";
- }
- var encrypted = _encrypter.encrypt(str, iv: _iv);
- return encrypted.base64;
- }
- ///AES解密,使用base64
- String aesDecrypt(String str) {
- if (str.isEmpty) {
- return "";
- }
- var encrptyed = encrpyt.Encrypted.from64(str);
- return _encrypter.decrypt(encrptyed, iv: _iv);
- }
- ///合并字节数组
- Uint8List concatenateBytes(List<Uint8List> byteList) {
- int totalLength = byteList.fold(0, (sum, bytes) => sum + bytes.length);
- Uint8List result = Uint8List(totalLength);
- int offset = 0;
- for (var bytes in byteList) {
- result.setRange(offset, offset + bytes.length, bytes);
- offset += bytes.length;
- }
- return result;
- }
- ///RSA加密,返回BASE64字符串,仅支持PKCS1编码
- ///
- /// [keyBitLength] 密钥长度,应为512,1024,2048,4096中的一个,默认512
- String rsaEncrypt(String pubKey, String content, {int keyBitLength = 512}) {
- try {
- final cipher = PKCS1Encoding(RSAEngine())
- ..init(
- true,
- PublicKeyParameter<RSAPublicKey>(
- encrpyt.RSAKeyParser().parse(pubKey) as RSAPublicKey));
- final utf8Bytes = Uint8List.fromList(utf8.encode(content));
- final inputLen = utf8Bytes.length;
- int offLen = 0;
- int i = 0;
- List<Uint8List> bops = [];
- int size = (keyBitLength / 8 - 11).toInt();
- while (inputLen - offLen > 0) {
- final chunkSize = (inputLen - offLen > size) ? size : inputLen - offLen;
- final cache = cipher.process(
- Uint8List.sublistView(utf8Bytes, offLen, offLen + chunkSize));
- bops.add(cache);
- i++;
- offLen = size * i;
- }
- final encryptedData = concatenateBytes(bops);
- final base64Encoded = base64Encode(encryptedData);
- return base64Encoded;
- } catch (e) {
- loge("RSA加密异常", error: e);
- return "";
- }
- }
- ///RSA解密,输入BASE64,仅支持PKCS1编码
- ///
- /// [keyBitLength] 密钥长度,应为512,1024,2048,4096中的一个,默认512
- String rsaDecrypt(String priKey, String content, {int keyBitLength = 512}) {
- try {
- final cipher = PKCS1Encoding(RSAEngine());
- cipher.init(
- false,
- PrivateKeyParameter<RSAPrivateKey>(
- encrpyt.RSAKeyParser().parse(priKey) as RSAPrivateKey));
- final encryptedBytes = base64Decode(content);
- final inputLen = encryptedBytes.length;
- int offLen = 0;
- int i = 0;
- List<Uint8List> byteList = [];
- int size = keyBitLength ~/ 8;
- while (inputLen - offLen > 0) {
- final int chunkSize =
- (inputLen - offLen > size) ? size : inputLen - offLen;
- final cache = cipher.process(
- Uint8List.sublistView(encryptedBytes, offLen, offLen + chunkSize));
- byteList.add(cache);
- i++;
- offLen = size * i;
- }
- final byteArray = concatenateBytes(byteList);
- return utf8.decode(byteArray);
- } catch (e) {
- loge("RSA解密异常", error: e);
- return '';
- }
- }
- ///检查新版本
- ///
- /// 返回true表示有新版本,false没有新版本
- Future<bool> checkNewVersion(BuildContext context, WidgetRef ref) async {
- VersionInfo? info = await ref
- .read(checkNewVersionProvider.notifier)
- .checkNewVersion(Platform.isIOS ? "ios" : "android");
- if (info == null || info.code.isNullOrEmpty) {
- logd("检查更新,服务器返回信息为null,或code为空");
- return false;
- }
- PackageInfo packageInfo = await PackageInfo.fromPlatform();
- logd(
- "检查更新,当前version=${packageInfo.version},当前code=${packageInfo.buildNumber},服务器version=${info.version},服务器code=${info.code}");
- int versionCode = 0;
- int serverCode = 0;
- try {
- versionCode = int.parse(packageInfo.buildNumber);
- serverCode = int.parse(info.code ?? "0");
- } catch (e) {
- loge("检查更新转换版本号异常", error: e);
- }
- //ios的version和code都需要比较,因为对于同一版本,ios发布时会自动提升code
- //不同版本又会从pubspec.yaml里填写的code重新计算
- if (Platform.isIOS) {
- if (info.version.isNullOrEmpty) {
- logd("ios比较版本,服务器version为空");
- return false;
- }
- //把服务器的版本号转为数字数组
- List<int> serverVersionNums = info.version!.split(".").map((e) {
- try {
- return int.parse(e);
- } catch (e) {
- loge("转换服务器版本字符到异常", error: e);
- }
- return 0;
- }).toList();
- //把本地的版本号转为数字数组
- List<int> currentVersionNums = packageInfo.version.split(".").map((e) {
- try {
- return int.parse(e);
- } catch (e) {
- loge("转换本地版本字符到异常", error: e);
- }
- return 0;
- }).toList();
- //如果本地长度小于服务器长度,则补齐本地数组
- if (currentVersionNums.length < serverVersionNums.length) {
- currentVersionNums.addAll(List.filled(
- serverVersionNums.length - currentVersionNums.length, -1));
- }
- //从左到右比较数字大小,只要服务器有一个大于本地的就认为需要升级
- bool needUpdate = false;
- bool sameVersion = true;
- for (int i = 0; i < serverVersionNums.length; i++) {
- if (serverVersionNums[i] != currentVersionNums[i]) {
- sameVersion = false;
- }
- if (serverVersionNums[i] > currentVersionNums[i]) {
- needUpdate = true;
- break;
- }
- }
- //如果版本号都一致,就判断code的大小
- if (!needUpdate && sameVersion && serverCode > versionCode) {
- needUpdate = true;
- }
- return needUpdate;
- }
- //其他的只需要判断code
- else {
- if (versionCode >= serverCode) {
- return false;
- }
- }
- if (context.mounted) {
- showDialog(
- context: context,
- builder: (ctx) {
- return AppUpdateDialog(
- version: info.version!,
- content: info.content ?? "",
- url: info.downloadUrl ?? "",
- isForce: info.mandatoryUpdate == "1");
- });
- }
- return true;
- }
- ///登出清空数据
- Future<dynamic> logoutClearData() async {
- updateToken("");
- await SpUtil.setUserId("");
- await SpUtil.setUserName("");
- await SpUtil.setToken("");
- await setSelectedPatientId(-1);
- }
- ///弹出所有路由
- void popAllRoutes(BuildContext context) {
- AutoRouter.of(context).popUntil((_) => false);
- }
- ///app名字
- String get appName =>
- switch (appFlavor) { flavorHst => getS().appNameHst, _ => getS().appName };
- ///退出APP
- void exitApp() async {
- await SystemNavigator.pop();
- //exit(0);
- }
- ///微信是否安装
- ///
- /// [toast] 是否显示提示,默认true
- Future<bool> isWxInstalled({bool toast = true}) async {
- bool bo = await fluwx.isWeChatInstalled;
- if (!bo && toast) {
- showToast(text: getS().wxNotInstalledHint);
- }
- return bo;
- }
|