179 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Dart
		
	
	
			
		
		
	
	
			179 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Dart
		
	
	
| import 'dart:async';
 | ||
| import 'dart:io';
 | ||
| import 'package:flutter/material.dart';
 | ||
| import 'package:pdfx/pdfx.dart';
 | ||
| import 'package:path_provider/path_provider.dart';
 | ||
| import 'package:qhd_prevention/customWidget/toast_util.dart';
 | ||
| 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;
 | ||
| 
 | ||
|   // 用于短文档(<=3页)的停留计时器兜底
 | ||
|   Timer? _pageViewTimer;
 | ||
| 
 | ||
|   @override
 | ||
|   void initState() {
 | ||
|     super.initState();
 | ||
|     _secondsRemaining = widget.countdownSeconds;
 | ||
|     _startCountdown();
 | ||
|     _downloadAndLoad();
 | ||
|     LoadingDialogHelper.hide();
 | ||
|   }
 | ||
| 
 | ||
|   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;
 | ||
|       });
 | ||
| 
 | ||
|       ToastUtil.showNormal(context, '文件加载失败: $e');
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   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();
 | ||
|     _pageViewTimer?.cancel();
 | ||
|     if (!_isLoading) {
 | ||
|       _pdfController.dispose();
 | ||
|     }
 | ||
|     super.dispose();
 | ||
|   }
 | ||
| 
 | ||
|   // 当页面改变时调用(pdfx 的 page 是 1-based)
 | ||
|   void _onPageChanged(int page) {
 | ||
|     // 取消可能存在的短页面计时器(重新开始)
 | ||
|     _pageViewTimer?.cancel();
 | ||
| 
 | ||
|     // 如果到达最后一页(注意页码从 1 开始)
 | ||
|     if (_totalPages > 0 && page >= _totalPages) {
 | ||
|       // 直接标记为已翻到底
 | ||
|       setState(() => _hasScrolledToBottom = true);
 | ||
|       return;
 | ||
|     }
 | ||
| 
 | ||
|     // 对于特别短的文档(例如 <=3 页),我们做一个停留计时器:如果用户停留在最后一页一小段时间,也视为已浏览完毕
 | ||
|     if (_totalPages > 0 && _totalPages <= 3) {
 | ||
|       // 如果当前页已经是最后一页(page == _totalPages),启动短计时器
 | ||
|       if (page == _totalPages) {
 | ||
|         _pageViewTimer = Timer(const Duration(seconds: 1), () {
 | ||
|           if (mounted) setState(() => _hasScrolledToBottom = true);
 | ||
|         });
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   @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;
 | ||
|                     // 如果文档只有 1 页,直接视为已看完
 | ||
|                     if (_totalPages <= 1) {
 | ||
|                       _hasScrolledToBottom = true;
 | ||
|                     }
 | ||
|                   });
 | ||
|                 },
 | ||
|                 onPageChanged: (page) {
 | ||
|                   _onPageChanged(page);
 | ||
|                 },
 | ||
|               ),
 | ||
|             ),
 | ||
|             Padding(
 | ||
|               padding: const EdgeInsets.all(16),
 | ||
|               child: CustomButton(
 | ||
|                 backgroundColor: isButtonEnabled ? Colors.blue : Colors.grey,
 | ||
|                 text: isButtonEnabled
 | ||
|                     ? '我已学习完毕'
 | ||
|                     : _secondsRemaining == 0 ? '我已学习完毕' : '($_secondsRemaining s)我已学习完毕',
 | ||
|                 onPressed: isButtonEnabled
 | ||
|                     ? () {
 | ||
|                   // TODO: 完成回调
 | ||
|                   Navigator.pop(context, true);
 | ||
|                 }
 | ||
|                     : null,
 | ||
|               ),
 | ||
|             ),
 | ||
|           ],
 | ||
|         ),
 | ||
|       ),
 | ||
|     );
 | ||
|   }
 | ||
| }
 |