app_update_dialog.dart 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. import 'dart:io';
  2. import 'package:app_installer/app_installer.dart';
  3. import 'package:dio/dio.dart';
  4. import 'package:eitc_erm_dental_flutter/exts.dart';
  5. import 'package:eitc_erm_dental_flutter/funcs.dart';
  6. import 'package:eitc_erm_dental_flutter/widget/custom_divider.dart';
  7. import 'package:eitc_erm_dental_flutter/widget/main_button.dart';
  8. import 'package:flutter/material.dart';
  9. import 'package:flutter_screenutil/flutter_screenutil.dart';
  10. import 'package:package_info_plus/package_info_plus.dart';
  11. import 'package:path_provider/path_provider.dart';
  12. import 'package:percent_indicator/percent_indicator.dart' as pi;
  13. import 'package:permission_handler/permission_handler.dart';
  14. import '../global.dart';
  15. import '../http/http.dart';
  16. ///app更新弹窗
  17. class AppUpdateDialog extends StatefulWidget {
  18. final String version;
  19. final String content;
  20. final String url;
  21. final bool isForce;
  22. const AppUpdateDialog(
  23. {super.key,
  24. required this.version,
  25. required this.content,
  26. required this.url,
  27. required this.isForce});
  28. @override
  29. State<AppUpdateDialog> createState() => _AppUpdateDialogState();
  30. }
  31. class _AppUpdateDialogState extends State<AppUpdateDialog> {
  32. _State state = _State.waiting;
  33. double _downloadProgress = 0.0;
  34. CancelToken? _downloadCancelToken;
  35. String _filePath = "";
  36. @override
  37. Widget build(BuildContext context) {
  38. return PopScope(
  39. //强制更新或下载时不允许返回关闭
  40. canPop: !widget.isForce && state != _State.downloading,
  41. child: Dialog(
  42. shape:
  43. RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.r)),
  44. child: Padding(
  45. padding: EdgeInsets.all(20.r),
  46. child:
  47. state == _State.waiting ? _getUpdateContent() : _getDownload(),
  48. ),
  49. ));
  50. }
  51. ///获取更新内容
  52. Widget _getUpdateContent() {
  53. return Column(
  54. mainAxisSize: MainAxisSize.min,
  55. crossAxisAlignment: CrossAxisAlignment.start,
  56. children: [
  57. Text(
  58. getS().hasNewVersion,
  59. style: context.titleMedium,
  60. ),
  61. Text("V${widget.version}"),
  62. SizedBox(
  63. height: 10.h,
  64. ),
  65. Text(widget.content),
  66. SizedBox(
  67. height: 10.h,
  68. ),
  69. Center(
  70. child: Row(
  71. mainAxisSize: MainAxisSize.min,
  72. children: [
  73. Visibility(
  74. visible: !widget.isForce,
  75. child: Row(
  76. children: [
  77. MainButton(
  78. text: getS().nextTime,
  79. onPressed: _cancelUpdate,
  80. isOutlined: true,
  81. ),
  82. SizedBox(
  83. width: 15.w,
  84. )
  85. ],
  86. ),
  87. ),
  88. MainButton(text: getS().updateNow, onPressed: _startUpdate)
  89. ],
  90. ),
  91. )
  92. ],
  93. );
  94. }
  95. ///获取下载内容
  96. Widget _getDownload() {
  97. String title = switch (state) {
  98. _State.downloading => getS().downloading,
  99. _State.waiting => "",
  100. _State.downloadError => getS().downloadFailed,
  101. _State.downloadSuccess => getS().downloadComplete,
  102. };
  103. return Column(
  104. mainAxisSize: MainAxisSize.min,
  105. children: [
  106. Text(
  107. title,
  108. style: context.titleMedium,
  109. ),
  110. SizedBox(
  111. height: 10.h,
  112. ),
  113. pi.LinearPercentIndicator(
  114. percent: _downloadProgress,
  115. lineHeight: 15.h,
  116. progressColor: context.primaryColor,
  117. barRadius: Radius.circular(15.h),
  118. center: Text(
  119. "${(_downloadProgress * 100.0).round()}%",
  120. style: TextStyle(fontSize: 12.sp, color: Colors.white),
  121. ),
  122. ),
  123. SizedBox(
  124. height: 10.h,
  125. ),
  126. _getDownloadFuncButton(),
  127. ],
  128. );
  129. }
  130. ///获取下载功能按钮
  131. Widget _getDownloadFuncButton() {
  132. //如果是下载中且强制更新,就没有按钮
  133. if (state == _State.downloading && widget.isForce) {
  134. return SizedBox();
  135. }
  136. String text = switch (state) {
  137. _State.downloading => getS().cancel,
  138. _State.waiting => "",
  139. _State.downloadError => getS().close,
  140. _State.downloadSuccess => getS().install,
  141. };
  142. return Column(
  143. children: [
  144. const CustomDivider(),
  145. MainButton(text: text, onPressed: _onDownloadButtonFunc)
  146. ],
  147. );
  148. }
  149. ///取消更新
  150. void _cancelUpdate() {
  151. Navigator.pop(context);
  152. }
  153. ///开始更新
  154. void _startUpdate() async {
  155. PackageInfo packageInfo = await PackageInfo.fromPlatform();
  156. if (Platform.isIOS) {
  157. logd("app更新,ios打开appstore,appid=$iosAppId");
  158. AppInstaller.goStore(packageInfo.packageName, iosAppId);
  159. } else {
  160. //地址可能不是具体文件,所以不能根据后缀来判断
  161. if (widget.url.isEmpty) {
  162. logd("app更新,android的url为空,打开appstore,appid=${packageInfo.packageName}");
  163. AppInstaller.goStore(packageInfo.packageName, iosAppId);
  164. } else {
  165. logd("app更新,开始下载,url=${widget.url}");
  166. _startDownload(widget.url);
  167. }
  168. }
  169. }
  170. ///开始下载
  171. void _startDownload(String url) async {
  172. if (!url.endsWith(".apk")) {
  173. setState(() {
  174. state = _State.downloadError;
  175. });
  176. }
  177. List<Directory>? list = await getExternalCacheDirectories();
  178. if (list == null || list.isEmpty) {
  179. setState(() {
  180. state = _State.downloadError;
  181. });
  182. return;
  183. }
  184. _downloadCancelToken = CancelToken();
  185. Directory dir = Directory("${list.first.path}/update");
  186. if (!await dir.exists()) {
  187. try {
  188. dir = await dir.create();
  189. } catch (e) {
  190. loge("创建文件夹失败", error: e);
  191. setState(() {
  192. state = _State.downloadError;
  193. });
  194. return;
  195. }
  196. }
  197. String fileName = url.substring(url.lastIndexOf("/") + 1);
  198. if (fileName.isEmpty) {
  199. setState(() {
  200. state = _State.downloadError;
  201. });
  202. return;
  203. }
  204. _filePath = "${dir.path}/$fileName";
  205. logd("app更新,下载保存路径=$_filePath");
  206. try {
  207. File file = File(_filePath);
  208. if (await file.exists()) {
  209. await file.delete();
  210. }
  211. } catch (e) {
  212. loge("app更新,删除旧apk文件失败", error: e);
  213. }
  214. Http.instance.download(url, _filePath, cancelToken: _downloadCancelToken,
  215. onReceiveProgress: (downloaded, total) {
  216. setState(() {
  217. _downloadProgress = downloaded.toDouble() / total.toDouble();
  218. logd(
  219. "app更新,下载进度=$_downloadProgress,downloaded=$downloaded,total=$total");
  220. if (_downloadProgress >= 1) {
  221. _onDownloadComplete();
  222. }
  223. });
  224. });
  225. setState(() {
  226. state = _State.downloading;
  227. });
  228. }
  229. ///下载完毕
  230. void _onDownloadComplete() {
  231. if (state != _State.downloading) {
  232. return;
  233. }
  234. state = _State.downloadSuccess;
  235. logd("app更新,下载完毕");
  236. _installApk();
  237. }
  238. ///下载按钮功能
  239. void _onDownloadButtonFunc() {
  240. ///正在下载
  241. if (state == _State.downloading) {
  242. //取消下载
  243. _downloadCancelToken?.cancel();
  244. //关闭弹窗
  245. _cancelUpdate();
  246. }
  247. //下载失败
  248. else if (state == _State.downloadError) {
  249. //强制更新就退出程序
  250. if (widget.isForce) {
  251. exitApp();
  252. }
  253. //否则关闭弹窗
  254. else {
  255. _cancelUpdate();
  256. }
  257. }
  258. //下载成功
  259. else if (state == _State.downloadSuccess) {
  260. _installApk();
  261. }
  262. }
  263. ///安装apk
  264. void _installApk() async {
  265. if (!await Permission.requestInstallPackages.isGranted) {
  266. logd("app更新,没有安装apk权限,进行申请");
  267. if (!await Permission.requestInstallPackages.request().isGranted) {
  268. logd("app更新,申请安装apk权限被拒绝,终止安装");
  269. return;
  270. }
  271. }
  272. logd("app更新,安装apk,path=$_filePath");
  273. AppInstaller.installApk(_filePath);
  274. }
  275. }
  276. enum _State { waiting, downloading, downloadError, downloadSuccess }