didvan-app/lib/views/pdf_viewer/pdf_viewer_page.dart

270 lines
8.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<PdfViewerPage> createState() => _PdfViewerPageState();
}
class _PdfViewerPageState extends State<PdfViewerPage> {
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<void> _downloadAndOpen() async {
try {
final headers = (_fullPdfUrl.contains('didvan.com') ||
_fullPdfUrl.contains('didvan.app'))
? {'Authorization': 'Bearer ${RequestService.token}'}
: const <String, String>{};
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();
}
}