import 'dart:io'; import 'package:app_installer/app_installer.dart'; import 'package:dio/dio.dart'; import 'package:eitc_erm_dental_flutter/exts.dart'; import 'package:eitc_erm_dental_flutter/funcs.dart'; import 'package:eitc_erm_dental_flutter/widget/custom_divider.dart'; import 'package:eitc_erm_dental_flutter/widget/main_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:percent_indicator/percent_indicator.dart' as pi; import 'package:permission_handler/permission_handler.dart'; import '../global.dart'; import '../http/http.dart'; ///app更新弹窗 class AppUpdateDialog extends StatefulWidget { final String version; final String content; final String url; final bool isForce; const AppUpdateDialog( {super.key, required this.version, required this.content, required this.url, required this.isForce}); @override State createState() => _AppUpdateDialogState(); } class _AppUpdateDialogState extends State { _State state = _State.waiting; double _downloadProgress = 0.0; CancelToken? _downloadCancelToken; String _filePath = ""; @override Widget build(BuildContext context) { return PopScope( //强制更新或下载时不允许返回关闭 canPop: !widget.isForce && state != _State.downloading, child: Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.r)), child: Padding( padding: EdgeInsets.all(20.r), child: state == _State.waiting ? _getUpdateContent() : _getDownload(), ), )); } ///获取更新内容 Widget _getUpdateContent() { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( getS().hasNewVersion, style: context.titleMedium, ), Text("V${widget.version}"), SizedBox( height: 10.h, ), Text(widget.content), SizedBox( height: 10.h, ), Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ Visibility( visible: !widget.isForce, child: Row( children: [ MainButton( text: getS().nextTime, onPressed: _cancelUpdate, isOutlined: true, ), SizedBox( width: 15.w, ) ], ), ), MainButton(text: getS().updateNow, onPressed: _startUpdate) ], ), ) ], ); } ///获取下载内容 Widget _getDownload() { String title = switch (state) { _State.downloading => getS().downloading, _State.waiting => "", _State.downloadError => getS().downloadFailed, _State.downloadSuccess => getS().downloadComplete, }; return Column( mainAxisSize: MainAxisSize.min, children: [ Text( title, style: context.titleMedium, ), SizedBox( height: 10.h, ), pi.LinearPercentIndicator( percent: _downloadProgress, lineHeight: 15.h, progressColor: context.primaryColor, barRadius: Radius.circular(15.h), center: Text( "${(_downloadProgress * 100.0).round()}%", style: TextStyle(fontSize: 12.sp, color: Colors.white), ), ), SizedBox( height: 10.h, ), _getDownloadFuncButton(), ], ); } ///获取下载功能按钮 Widget _getDownloadFuncButton() { //如果是下载中且强制更新,就没有按钮 if (state == _State.downloading && widget.isForce) { return SizedBox(); } String text = switch (state) { _State.downloading => getS().cancel, _State.waiting => "", _State.downloadError => getS().close, _State.downloadSuccess => getS().install, }; return Column( children: [ const CustomDivider(), MainButton(text: text, onPressed: _onDownloadButtonFunc) ], ); } ///取消更新 void _cancelUpdate() { Navigator.pop(context); } ///开始更新 void _startUpdate() async { PackageInfo packageInfo = await PackageInfo.fromPlatform(); if (Platform.isIOS) { logd("app更新,ios打开appstore,appid=$iosAppId"); AppInstaller.goStore(packageInfo.packageName, iosAppId); } else { //地址可能不是具体文件,所以不能根据后缀来判断 if (widget.url.isEmpty) { logd("app更新,android的url为空,打开appstore,appid=${packageInfo.packageName}"); AppInstaller.goStore(packageInfo.packageName, iosAppId); } else { logd("app更新,开始下载,url=${widget.url}"); _startDownload(widget.url); } } } ///开始下载 void _startDownload(String url) async { if (!url.endsWith(".apk")) { setState(() { state = _State.downloadError; }); } List? list = await getExternalCacheDirectories(); if (list == null || list.isEmpty) { setState(() { state = _State.downloadError; }); return; } _downloadCancelToken = CancelToken(); Directory dir = Directory("${list.first.path}/update"); if (!await dir.exists()) { try { dir = await dir.create(); } catch (e) { loge("创建文件夹失败", error: e); setState(() { state = _State.downloadError; }); return; } } String fileName = url.substring(url.lastIndexOf("/") + 1); if (fileName.isEmpty) { setState(() { state = _State.downloadError; }); return; } _filePath = "${dir.path}/$fileName"; logd("app更新,下载保存路径=$_filePath"); try { File file = File(_filePath); if (await file.exists()) { await file.delete(); } } catch (e) { loge("app更新,删除旧apk文件失败", error: e); } Http.instance.download(url, _filePath, cancelToken: _downloadCancelToken, onReceiveProgress: (downloaded, total) { setState(() { _downloadProgress = downloaded.toDouble() / total.toDouble(); logd( "app更新,下载进度=$_downloadProgress,downloaded=$downloaded,total=$total"); if (_downloadProgress >= 1) { _onDownloadComplete(); } }); }); setState(() { state = _State.downloading; }); } ///下载完毕 void _onDownloadComplete() { if (state != _State.downloading) { return; } state = _State.downloadSuccess; logd("app更新,下载完毕"); _installApk(); } ///下载按钮功能 void _onDownloadButtonFunc() { ///正在下载 if (state == _State.downloading) { //取消下载 _downloadCancelToken?.cancel(); //关闭弹窗 _cancelUpdate(); } //下载失败 else if (state == _State.downloadError) { //强制更新就退出程序 if (widget.isForce) { exitApp(); } //否则关闭弹窗 else { _cancelUpdate(); } } //下载成功 else if (state == _State.downloadSuccess) { _installApk(); } } ///安装apk void _installApk() async { if (!await Permission.requestInstallPackages.isGranted) { logd("app更新,没有安装apk权限,进行申请"); if (!await Permission.requestInstallPackages.request().isGranted) { logd("app更新,申请安装apk权限被拒绝,终止安装"); return; } } logd("app更新,安装apk,path=$_filePath"); AppInstaller.installApk(_filePath); } } enum _State { waiting, downloading, downloadError, downloadSuccess }