app_update_dialog.dart 7.7 KB

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