| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  | import 'dart:async'; | 
					
						
							|  |  |  | import 'package:flutter/material.dart'; | 
					
						
							|  |  |  | import 'package:qhd_prevention/services/update_service.dart'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// 显示“发现新版本”确认对话框,点击更新后会弹出下载进度弹窗
 | 
					
						
							| 
									
										
										
										
											2025-09-18 21:45:41 +08:00
										 |  |  | Future<void> showUpdateConfirm(BuildContext context, { | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |   required String apkUrl, | 
					
						
							| 
									
										
										
										
											2025-09-18 21:45:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  | }) async { | 
					
						
							| 
									
										
										
										
											2025-09-18 21:45:41 +08:00
										 |  |  |   await showDialog( | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |     context: context, | 
					
						
							|  |  |  |     barrierDismissible: false, | 
					
						
							| 
									
										
										
										
											2025-09-18 21:45:41 +08:00
										 |  |  |     builder: (ctx) => DownloadProgressDialog(apkUrl: apkUrl), | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |   ); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// 下载进度弹窗(会在 initState 里自动开始下载)
 | 
					
						
							|  |  |  | class DownloadProgressDialog extends StatefulWidget { | 
					
						
							|  |  |  |   final String apkUrl; | 
					
						
							|  |  |  |   final String? apkFileName; | 
					
						
							|  |  |  |   const DownloadProgressDialog({Key? key, required this.apkUrl, this.apkFileName}) : super(key: key); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<DownloadProgressDialog> createState() => _DownloadProgressDialogState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _DownloadProgressDialogState extends State<DownloadProgressDialog> { | 
					
						
							|  |  |  |   final UpdateService _service = UpdateService(); | 
					
						
							|  |  |  |   StreamSubscription<UpdateEvent>? _sub; | 
					
						
							|  |  |  |   double _progress = 0.0; | 
					
						
							|  |  |  |   String _statusText = '准备下载...'; | 
					
						
							|  |  |  |   bool _isWorking = true; // 下载或安装过程中禁止重复操作
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 监听状态流
 | 
					
						
							|  |  |  |     _sub = _service.statusStream.listen((event) { | 
					
						
							|  |  |  |       // 根据事件更新 UI
 | 
					
						
							|  |  |  |       setState(() { | 
					
						
							|  |  |  |         switch (event.state) { | 
					
						
							|  |  |  |           case UpdateState.idle: | 
					
						
							|  |  |  |             _statusText = '空闲'; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           case UpdateState.starting: | 
					
						
							|  |  |  |             _statusText = '准备中...'; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           case UpdateState.downloading: | 
					
						
							|  |  |  |             _statusText = '下载中...'; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           case UpdateState.completed: | 
					
						
							|  |  |  |             _statusText = '下载完成,正在准备安装...'; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           case UpdateState.installing: | 
					
						
							|  |  |  |             _statusText = '发起安装...'; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           case UpdateState.installed: | 
					
						
							|  |  |  |             _statusText = '已触发安装'; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           case UpdateState.failed: | 
					
						
							|  |  |  |             _statusText = '失败: ${event.message ?? ''}'; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           case UpdateState.canceled: | 
					
						
							|  |  |  |             _statusText = '已取消'; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // 根据不同状态决定是否关闭弹窗或提示
 | 
					
						
							|  |  |  |       if (event.state == UpdateState.installing || event.state == UpdateState.installed) { | 
					
						
							|  |  |  |         // 已发起安装,关闭弹窗让系统安装界面出现
 | 
					
						
							|  |  |  |         if (mounted) Future.delayed(const Duration(milliseconds: 300), () { | 
					
						
							|  |  |  |           if (mounted) Navigator.of(context).pop(); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } else if (event.state == UpdateState.failed) { | 
					
						
							|  |  |  |         // 显示错误并在短时间后自动关闭弹窗
 | 
					
						
							|  |  |  |         _isWorking = false; | 
					
						
							|  |  |  |         Future.delayed(const Duration(milliseconds: 800), () { | 
					
						
							|  |  |  |           if (mounted) Navigator.of(context).pop(); | 
					
						
							|  |  |  |           if (mounted) ScaffoldMessenger.of(context).showSnackBar( | 
					
						
							|  |  |  |             SnackBar(content: Text('更新失败:${event.message ?? '未知错误'}')), | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } else if (event.state == UpdateState.canceled) { | 
					
						
							|  |  |  |         _isWorking = false; | 
					
						
							|  |  |  |         if (mounted) { | 
					
						
							|  |  |  |           Future.delayed(const Duration(milliseconds: 200), () { | 
					
						
							|  |  |  |             if (mounted) Navigator.of(context).pop(); | 
					
						
							|  |  |  |             if (mounted) ScaffoldMessenger.of(context).showSnackBar( | 
					
						
							|  |  |  |               const SnackBar(content: Text('下载已取消')), | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 监听进度 ValueNotifier
 | 
					
						
							|  |  |  |     _service.progress.addListener(_onProgressChanged); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 启动下载
 | 
					
						
							|  |  |  |     _service.downloadAndInstall(apkUrl: widget.apkUrl, apkFileName: widget.apkFileName); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void _onProgressChanged() { | 
					
						
							|  |  |  |     setState(() { | 
					
						
							|  |  |  |       _progress = _service.progress.value; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void dispose() { | 
					
						
							|  |  |  |     _sub?.cancel(); | 
					
						
							|  |  |  |     _service.progress.removeListener(_onProgressChanged); | 
					
						
							|  |  |  |     super.dispose(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void _onCancel() { | 
					
						
							|  |  |  |     if (!_isWorking) { | 
					
						
							|  |  |  |       // 如果已经不是工作中,直接关闭
 | 
					
						
							|  |  |  |       if (Navigator.of(context).canPop()) Navigator.of(context).pop(); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 取消下载
 | 
					
						
							|  |  |  |     _service.cancelDownload(); | 
					
						
							|  |  |  |     // 标记为非工作中(后续状态回调会关闭并提示)
 | 
					
						
							|  |  |  |     _isWorking = false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     final percent = (_progress * 100).clamp(0.0, 100.0); | 
					
						
							|  |  |  |     return WillPopScope( | 
					
						
							|  |  |  |       onWillPop: () async => false, // 禁止物理返回
 | 
					
						
							|  |  |  |       child: AlertDialog( | 
					
						
							|  |  |  |         title: const Text('正在更新'), | 
					
						
							|  |  |  |         content: Column( | 
					
						
							|  |  |  |           mainAxisSize: MainAxisSize.min, | 
					
						
							|  |  |  |           children: [ | 
					
						
							|  |  |  |             LinearProgressIndicator(value: _progress), | 
					
						
							|  |  |  |             const SizedBox(height: 12), | 
					
						
							|  |  |  |             Row( | 
					
						
							|  |  |  |               mainAxisAlignment: MainAxisAlignment.spaceBetween, | 
					
						
							|  |  |  |               children: [ | 
					
						
							|  |  |  |                 Text('${percent.toStringAsFixed(1)}%'), | 
					
						
							|  |  |  |                 Flexible(child: Text(_statusText, overflow: TextOverflow.ellipsis, textAlign: TextAlign.right)), | 
					
						
							|  |  |  |               ], | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ], | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         actions: [ | 
					
						
							| 
									
										
										
										
											2025-09-19 17:59:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |         ], | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |