| 
									
										
										
										
											2025-07-22 13:34:34 +08:00
										 |  |  |  | import 'dart:async'; | 
					
						
							|  |  |  |  | import 'dart:io'; | 
					
						
							|  |  |  |  | import 'package:flutter/material.dart'; | 
					
						
							|  |  |  |  | import 'package:pdfx/pdfx.dart'; | 
					
						
							|  |  |  |  | import 'package:path_provider/path_provider.dart'; | 
					
						
							| 
									
										
										
										
											2025-09-05 09:16:54 +08:00
										 |  |  |  | import 'package:qhd_prevention/customWidget/toast_util.dart'; | 
					
						
							| 
									
										
										
										
											2025-07-22 13:34:34 +08:00
										 |  |  |  | import 'package:qhd_prevention/pages/my_appbar.dart'; | 
					
						
							|  |  |  |  | import 'package:qhd_prevention/customWidget/custom_button.dart'; | 
					
						
							|  |  |  |  | import 'package:dio/dio.dart'; | 
					
						
							|  |  |  |  | import 'package:qhd_prevention/tools/tools.dart'; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | class RemoteFilePage extends StatefulWidget { | 
					
						
							|  |  |  |  |   final String fileUrl; | 
					
						
							|  |  |  |  |   final int countdownSeconds; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   const RemoteFilePage({ | 
					
						
							|  |  |  |  |     Key? key, | 
					
						
							|  |  |  |  |     required this.fileUrl, | 
					
						
							|  |  |  |  |     this.countdownSeconds = 3, | 
					
						
							|  |  |  |  |   }) : super(key: key); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   @override | 
					
						
							|  |  |  |  |   _RemoteFilePageState createState() => _RemoteFilePageState(); | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | class _RemoteFilePageState extends State<RemoteFilePage> { | 
					
						
							|  |  |  |  |   String? _localPath; | 
					
						
							|  |  |  |  |   bool _isLoading = true; | 
					
						
							|  |  |  |  |   bool _hasScrolledToBottom = false; | 
					
						
							|  |  |  |  |   bool _timerFinished = false; | 
					
						
							|  |  |  |  |   late int _secondsRemaining; | 
					
						
							|  |  |  |  |   Timer? _countdownTimer; | 
					
						
							|  |  |  |  |   late PdfControllerPinch _pdfController; | 
					
						
							|  |  |  |  |   int _totalPages = 0; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   @override | 
					
						
							|  |  |  |  |   void initState() { | 
					
						
							|  |  |  |  |     super.initState(); | 
					
						
							|  |  |  |  |     _secondsRemaining = widget.countdownSeconds; | 
					
						
							|  |  |  |  |     _startCountdown(); | 
					
						
							|  |  |  |  |     _downloadAndLoad(); | 
					
						
							| 
									
										
										
										
											2025-09-15 15:54:03 +08:00
										 |  |  |  |     LoadingDialogHelper.hide(); | 
					
						
							| 
									
										
										
										
											2025-07-22 13:34:34 +08:00
										 |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   Future<void> _downloadAndLoad() async { | 
					
						
							|  |  |  |  |     try { | 
					
						
							|  |  |  |  |       final url = widget.fileUrl; | 
					
						
							|  |  |  |  |       final filename = url.split('/').last; | 
					
						
							|  |  |  |  |       final dir = await getTemporaryDirectory(); | 
					
						
							|  |  |  |  |       final filePath = '${dir.path}/$filename'; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       final dio = Dio(); | 
					
						
							|  |  |  |  |       final response = await dio.get<List<int>>( | 
					
						
							|  |  |  |  |         url, | 
					
						
							|  |  |  |  |         options: Options(responseType: ResponseType.bytes), | 
					
						
							|  |  |  |  |       ); | 
					
						
							|  |  |  |  |       final file = File(filePath); | 
					
						
							|  |  |  |  |       await file.writeAsBytes(response.data!); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       // 加载 PDF 控制器
 | 
					
						
							|  |  |  |  |       _pdfController = PdfControllerPinch( | 
					
						
							|  |  |  |  |         document: PdfDocument.openFile(filePath), | 
					
						
							|  |  |  |  |       ); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       setState(() { | 
					
						
							|  |  |  |  |         _localPath = filePath; | 
					
						
							|  |  |  |  |         _isLoading = false; | 
					
						
							|  |  |  |  |       }); | 
					
						
							|  |  |  |  |     } catch (e) { | 
					
						
							|  |  |  |  |       // 下载或加载失败
 | 
					
						
							|  |  |  |  |       setState(() { | 
					
						
							|  |  |  |  |         _isLoading = false; | 
					
						
							|  |  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2025-09-05 09:16:54 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |       ToastUtil.showNormal(context, '文件加载失败: $e'); | 
					
						
							| 
									
										
										
										
											2025-07-22 13:34:34 +08:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   void _startCountdown() { | 
					
						
							|  |  |  |  |     _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { | 
					
						
							|  |  |  |  |       setState(() { | 
					
						
							|  |  |  |  |         if (_secondsRemaining > 1) { | 
					
						
							|  |  |  |  |           _secondsRemaining--; | 
					
						
							|  |  |  |  |         } else { | 
					
						
							|  |  |  |  |           _secondsRemaining = 0; | 
					
						
							|  |  |  |  |           _timerFinished = true; | 
					
						
							|  |  |  |  |           _countdownTimer?.cancel(); | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |       }); | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   @override | 
					
						
							|  |  |  |  |   void dispose() { | 
					
						
							|  |  |  |  |     _countdownTimer?.cancel(); | 
					
						
							|  |  |  |  |     if (!_isLoading) { | 
					
						
							|  |  |  |  |       _pdfController.dispose(); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |     super.dispose(); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   @override | 
					
						
							|  |  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |  |     final isButtonEnabled = _timerFinished && _hasScrolledToBottom; | 
					
						
							|  |  |  |  |     return Scaffold( | 
					
						
							|  |  |  |  |       appBar: MyAppbar(title: '资料学习'), | 
					
						
							|  |  |  |  |       backgroundColor: Colors.white, | 
					
						
							|  |  |  |  |       body: SafeArea( | 
					
						
							|  |  |  |  |         child: Column( | 
					
						
							|  |  |  |  |           children: [ | 
					
						
							|  |  |  |  |             Expanded( | 
					
						
							|  |  |  |  |               child: _isLoading | 
					
						
							|  |  |  |  |                   ? const Center(child: CircularProgressIndicator()) | 
					
						
							|  |  |  |  |                   : PdfViewPinch( | 
					
						
							|  |  |  |  |                 controller: _pdfController, | 
					
						
							|  |  |  |  |                 scrollDirection: Axis.vertical, | 
					
						
							|  |  |  |  |                 onDocumentLoaded: (document) { | 
					
						
							|  |  |  |  |                   setState(() { | 
					
						
							|  |  |  |  |                     _totalPages = document.pagesCount; | 
					
						
							|  |  |  |  |                   }); | 
					
						
							|  |  |  |  |                 }, | 
					
						
							|  |  |  |  |                 onPageChanged: (page) { | 
					
						
							|  |  |  |  |                   if (page == _totalPages - 1) { | 
					
						
							|  |  |  |  |                     setState(() => _hasScrolledToBottom = true); | 
					
						
							|  |  |  |  |                   } | 
					
						
							|  |  |  |  |                 }, | 
					
						
							|  |  |  |  |               ), | 
					
						
							|  |  |  |  |             ), | 
					
						
							|  |  |  |  |             Padding( | 
					
						
							|  |  |  |  |               padding: const EdgeInsets.all(16), | 
					
						
							|  |  |  |  |               child: CustomButton( | 
					
						
							|  |  |  |  |                 backgroundColor: isButtonEnabled ? Colors.blue : Colors.grey, | 
					
						
							|  |  |  |  |                 text: isButtonEnabled | 
					
						
							|  |  |  |  |                     ? '我已学习完毕' | 
					
						
							|  |  |  |  |                     : _secondsRemaining == 0 ? '我已学习完毕' : '($_secondsRemaining s)我已学习完毕', | 
					
						
							|  |  |  |  |                 onPressed: isButtonEnabled | 
					
						
							|  |  |  |  |                     ? () { | 
					
						
							|  |  |  |  |                   // TODO: 完成回调
 | 
					
						
							| 
									
										
										
										
											2025-09-15 15:54:03 +08:00
										 |  |  |  |                   Navigator.pop(context, true); | 
					
						
							| 
									
										
										
										
											2025-07-22 13:34:34 +08:00
										 |  |  |  |                 } | 
					
						
							|  |  |  |  |                     : null, | 
					
						
							|  |  |  |  |               ), | 
					
						
							|  |  |  |  |             ), | 
					
						
							|  |  |  |  |           ], | 
					
						
							|  |  |  |  |         ), | 
					
						
							|  |  |  |  |       ), | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | } |