import 'dart:io'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:flutter/foundation.dart'; import 'package:didvan/services/network/request.dart'; import 'package:http/http.dart' as http; import 'package:path_provider/path_provider.dart'; import 'package:universal_html/html.dart' as html; class PdfViewerPage extends StatefulWidget { final String pdfUrl; final String title; const PdfViewerPage({ super.key, required this.pdfUrl, required this.title, }); @override State createState() => _PdfViewerPageState(); } class _PdfViewerPageState extends State { final PdfViewerController _pdfViewerController = PdfViewerController(); bool _isLoading = true; String? _errorMessage; late String _fullPdfUrl; File? _localPdfFile; @override void initState() { super.initState(); // اگر URL کامل (http/https) بود همان را استفاده کن؛ در غیر این صورت // به دامنه‌ی legacy می‌چسبد. URLهای جدید content-service از s3-service // به‌صورت presigned کامل می‌آیند. _fullPdfUrl = widget.pdfUrl.startsWith('http') ? widget.pdfUrl : 'https://api.didvan.app${widget.pdfUrl}'; if (kDebugMode) { print('Original PDF URL: ${widget.pdfUrl}'); print('Full PDF URL : $_fullPdfUrl'); } // برای وب، PDF را مستقیماً در مرورگر باز می‌کند if (kIsWeb) { WidgetsBinding.instance.addPostFrameCallback((_) { _openPdfInBrowser(); }); } else { // در موبایل: PDF را به temp دانلود می‌کنیم و سپس با SfPdfViewer.file // نمایش می‌دهیم. SfPdfViewer.network با presigned URLهای Arvan که // query params زیاد و امضای AWS دارند گاهی fail می‌شود؛ دانلود // محلی این مشکل را دور می‌زند. _downloadAndOpen(); } } Future _downloadAndOpen() async { try { final headers = (_fullPdfUrl.contains('didvan.com') || _fullPdfUrl.contains('didvan.app')) ? {'Authorization': 'Bearer ${RequestService.token}'} : const {}; final res = await http.get(Uri.parse(_fullPdfUrl), headers: headers); if (res.statusCode != 200) { if (mounted) { setState(() { _isLoading = false; _errorMessage = 'HTTP ${res.statusCode}'; }); } return; } final tempDir = await getTemporaryDirectory(); final file = File( '${tempDir.path}/pdf_${DateTime.now().millisecondsSinceEpoch}.pdf', ); await file.writeAsBytes(res.bodyBytes, flush: true); if (!mounted) return; setState(() { _localPdfFile = file; // isLoading remains true until SfPdfViewer's onDocumentLoaded fires }); } catch (e) { if (kDebugMode) print('PDF download failed: $e'); if (mounted) { setState(() { _isLoading = false; _errorMessage = e.toString(); }); } } } void _openPdfInBrowser() { final token = RequestService.token; final pdfUrlWithAuth = '$_fullPdfUrl${_fullPdfUrl.contains('?') ? '&' : '?'}token=$token'; // روش سازگار با تمام مرورگرها شامل Safari try { final anchor = html.AnchorElement(href: pdfUrlWithAuth) ..setAttribute('target', '_blank') ..setAttribute('rel', 'noopener'); html.document.body?.append(anchor); anchor.click(); anchor.remove(); if (kDebugMode) { print('PDF opened successfully in new tab'); } } catch (e) { if (kDebugMode) { print('Error opening PDF: $e'); } } Future.delayed(const Duration(milliseconds: 500), () { if (mounted) { Navigator.of(context).pop(); } }); } @override Widget build(BuildContext context) { // برای وب، صفحه‌ای نشان می‌دهد که PDF در تب جدیدی باز می‌شود if (kIsWeb) { return Scaffold( resizeToAvoidBottomInset: true, appBar: AppBar( title: DidvanText( widget.title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), centerTitle: true, elevation: 0, backgroundColor: Theme.of(context).colorScheme.surface, ), body: const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: 16), DidvanText( 'درحال باز کردن PDF در مرورگر...', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ), ), ); } // برای پلتفرم‌های دیگر، از SfPdfViewer استفاده کن return Scaffold( resizeToAvoidBottomInset: true, appBar: AppBar( title: DidvanText( widget.title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), centerTitle: true, elevation: 0, backgroundColor: Theme.of(context).colorScheme.surface, ), body: Stack( children: [ if (_localPdfFile != null) SfPdfViewer.file( _localPdfFile!, controller: _pdfViewerController, enableDoubleTapZooming: true, enableTextSelection: true, canShowScrollHead: true, canShowScrollStatus: true, pageLayoutMode: PdfPageLayoutMode.continuous, onDocumentLoaded: (PdfDocumentLoadedDetails details) { setState(() { _isLoading = false; }); if (kDebugMode) { print( 'PDF loaded successfully with ${details.document.pages.count} pages'); } }, onDocumentLoadFailed: (PdfDocumentLoadFailedDetails details) { setState(() { _isLoading = false; _errorMessage = details.error; }); if (kDebugMode) { print('PDF load failed: ${details.error}'); print('Description: ${details.description}'); } }, ), if (_isLoading) const Center( child: CircularProgressIndicator(), ), if (_errorMessage != null) Center( child: Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.error_outline, size: 64, color: Colors.red, ), const SizedBox(height: 16), const DidvanText( 'خطا در بارگذاری PDF', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), DidvanText( _errorMessage!, textAlign: TextAlign.center, style: const TextStyle(fontSize: 14), ), const SizedBox(height: 16), ElevatedButton( onPressed: () { setState(() { _isLoading = true; _errorMessage = null; }); }, child: const DidvanText('تلاش مجدد'), ), ], ), ), ), ], ), ); } @override void dispose() { _pdfViewerController.dispose(); super.dispose(); } }