338 lines
9.9 KiB
Dart
338 lines
9.9 KiB
Dart
import 'package:dropdown_search/dropdown_search.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:intl/intl.dart';
|
|
import 'package:pdf_render/pdf_render_widgets.dart';
|
|
import 'dart:typed_data';
|
|
import 'package:flutter/services.dart';
|
|
|
|
class PolicyPdfViewer extends StatefulWidget {
|
|
final String? pdfUrl;
|
|
|
|
const PolicyPdfViewer({Key? key, this.pdfUrl}) : super(key: key);
|
|
|
|
@override
|
|
State<PolicyPdfViewer> createState() => _PolicyPdfViewerState();
|
|
}
|
|
|
|
class _PolicyPdfViewerState extends State<PolicyPdfViewer> {
|
|
Uint8List? _pdfBytes;
|
|
|
|
final TransformationController _transformController =
|
|
TransformationController();
|
|
|
|
double _scale = 1.0;
|
|
final double _minScale = 0.7;
|
|
final double _maxScale = 3.0;
|
|
|
|
final ScrollController _scrollController = ScrollController();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadPdf();
|
|
}
|
|
|
|
Future<void> _loadPdf() async {
|
|
if (widget.pdfUrl == null) return;
|
|
|
|
try {
|
|
final response = await http.get(Uri.parse(widget.pdfUrl!));
|
|
|
|
if (!mounted) return;
|
|
|
|
if (response.statusCode == 200) {
|
|
setState(() {
|
|
_pdfBytes = response.bodyBytes; // ✅ MUST use setState
|
|
});
|
|
}
|
|
} catch (e) {
|
|
debugPrint('PDF load error: $e');
|
|
}
|
|
}
|
|
|
|
void _zoomIn() {
|
|
setState(() {
|
|
_scale = (_scale + 0.2).clamp(_minScale, _maxScale);
|
|
_transformController.value = Matrix4.identity()..scale(_scale);
|
|
});
|
|
}
|
|
|
|
void _zoomOut() {
|
|
setState(() {
|
|
_scale = (_scale - 0.2).clamp(_minScale, _maxScale);
|
|
_transformController.value = Matrix4.identity()..scale(_scale);
|
|
});
|
|
}
|
|
|
|
void _resetZoom() {
|
|
setState(() {
|
|
_scale = 1.0;
|
|
_transformController.value = Matrix4.identity();
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_pdfBytes == null) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
final bool isZoomed = _scale > 1.0;
|
|
|
|
return Stack(
|
|
children: [
|
|
InteractiveViewer(
|
|
transformationController: _transformController,
|
|
panEnabled: isZoomed,
|
|
scaleEnabled: false,
|
|
boundaryMargin: const EdgeInsets.all(200),
|
|
minScale: _minScale,
|
|
maxScale: _maxScale,
|
|
child: PdfDocumentLoader.openData(
|
|
_pdfBytes!,
|
|
documentBuilder: (context, pdfDocument, pageCount) {
|
|
return ListView.builder(
|
|
controller: _scrollController,
|
|
physics: isZoomed
|
|
? const NeverScrollableScrollPhysics()
|
|
: const BouncingScrollPhysics(),
|
|
itemCount: pageCount,
|
|
itemBuilder: (context, index) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: PdfPageView(
|
|
pdfDocument: pdfDocument,
|
|
pageNumber: index + 1,
|
|
// renderQuality: PdfPageRenderQuality.medium, // ⚡ performance
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
|
|
// ───────── Zoom Controls ─────────
|
|
Positioned(
|
|
right: 16,
|
|
top: 16,
|
|
child: Column(
|
|
children: [
|
|
_zoomButton(Icons.add, _zoomIn),
|
|
const SizedBox(height: 8),
|
|
_zoomButton(Icons.remove, _zoomOut),
|
|
const SizedBox(height: 8),
|
|
_zoomButton(Icons.refresh, _resetZoom),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _zoomButton(IconData icon, VoidCallback onTap) {
|
|
return Material(
|
|
elevation: 4,
|
|
shape: const CircleBorder(),
|
|
color: Colors.white,
|
|
child: InkWell(
|
|
onTap: onTap,
|
|
customBorder: const CircleBorder(),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(10),
|
|
child: Icon(icon, size: 22),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
//scroll zoom code
|
|
// import 'package:dropdown_search/dropdown_search.dart';
|
|
// import 'package:flutter/gestures.dart';
|
|
// import 'package:flutter/material.dart';
|
|
// import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
// import 'package:go_router/go_router.dart';
|
|
// import 'package:google_fonts/google_fonts.dart';
|
|
// import 'package:http/http.dart' as http;
|
|
// import 'package:intl/intl.dart';
|
|
// import 'package:pdf_render/pdf_render_widgets.dart';
|
|
// import 'dart:typed_data';
|
|
// import 'package:flutter/services.dart';
|
|
//
|
|
// // ============= KEY FIX: Separate PDF Viewer Widget =============
|
|
// class PolicyPdfViewer extends StatefulWidget {
|
|
// final String? pdfUrl;
|
|
//
|
|
// const PolicyPdfViewer({Key? key, this.pdfUrl}) : super(key: key);
|
|
//
|
|
// @override
|
|
// State<PolicyPdfViewer> createState() => _PolicyPdfViewerState();
|
|
// }
|
|
//
|
|
// class _PolicyPdfViewerState extends State<PolicyPdfViewer>
|
|
// with AutomaticKeepAliveClientMixin {
|
|
// @override
|
|
// bool get wantKeepAlive => true;
|
|
//
|
|
// Uint8List? _cachedPdfBytes;
|
|
// String? _lastLoadedUrl;
|
|
//
|
|
// double _scale = 1.0;
|
|
// final double _minScale = 0.5;
|
|
// final double _maxScale = 3.0;
|
|
//
|
|
// Future<Uint8List> _loadPdf() async {
|
|
// if (_cachedPdfBytes != null && _lastLoadedUrl == widget.pdfUrl) {
|
|
// return _cachedPdfBytes!;
|
|
// }
|
|
//
|
|
// final response = await http.get(Uri.parse(widget.pdfUrl!));
|
|
// if (response.statusCode == 200) {
|
|
// _cachedPdfBytes = response.bodyBytes;
|
|
// _lastLoadedUrl = widget.pdfUrl;
|
|
// return _cachedPdfBytes!;
|
|
// } else {
|
|
// throw Exception('Failed to load PDF');
|
|
// }
|
|
// }
|
|
//
|
|
// void _onPointerSignal(PointerSignalEvent event) {
|
|
// if (event is PointerScrollEvent) {
|
|
// if (HardwareKeyboard.instance.isControlPressed) {
|
|
// setState(() {
|
|
// _scale += event.scrollDelta.dy > 0 ? -0.1 : 0.1;
|
|
// _scale = _scale.clamp(_minScale, _maxScale);
|
|
// });
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// @override
|
|
// Widget build(BuildContext context) {
|
|
// super.build(context);
|
|
//
|
|
// if (widget.pdfUrl == null) {
|
|
// return const Center(child: CircularProgressIndicator());
|
|
// }
|
|
//
|
|
// return Listener(
|
|
// onPointerSignal: _onPointerSignal,
|
|
// child: FutureBuilder<Uint8List>(
|
|
// key: ValueKey(widget.pdfUrl),
|
|
// future: _loadPdf(),
|
|
// builder: (context, snapshot) {
|
|
// if (!snapshot.hasData) {
|
|
// return const Center(child: CircularProgressIndicator());
|
|
// }
|
|
//
|
|
// return InteractiveViewer(
|
|
// minScale: _minScale,
|
|
// maxScale: _maxScale,
|
|
// scaleEnabled: true,
|
|
// panEnabled: true,
|
|
// child: Transform.scale(
|
|
// scale: _scale,
|
|
// alignment: Alignment.topCenter,
|
|
// child: PdfDocumentLoader.openData(
|
|
// snapshot.data!,
|
|
// documentBuilder: (context, pdfDocument, pageCount) {
|
|
// return ListView.builder(
|
|
// itemCount: pageCount,
|
|
// itemBuilder: (context, index) {
|
|
// return PdfPageView(
|
|
// pdfDocument: pdfDocument,
|
|
// pageNumber: index + 1,
|
|
// );
|
|
// },
|
|
// );
|
|
// },
|
|
// ),
|
|
// ),
|
|
// );
|
|
// },
|
|
// ),
|
|
// );
|
|
// }
|
|
// }
|
|
|
|
|
|
|
|
//Old Code for PDF Viewer
|
|
// class _PolicyPdfViewerState extends State<PolicyPdfViewer>
|
|
// with AutomaticKeepAliveClientMixin {
|
|
// @override
|
|
// bool get wantKeepAlive => true; // Prevents rebuilding
|
|
//
|
|
// // Cache the loaded PDF bytes
|
|
// Uint8List? _cachedPdfBytes;
|
|
// String? _lastLoadedUrl;
|
|
//
|
|
// Future<Uint8List> _loadPdf() async {
|
|
// // Return cached bytes if URL hasn't changed
|
|
// if (_cachedPdfBytes != null && _lastLoadedUrl == widget.pdfUrl) {
|
|
// return _cachedPdfBytes!;
|
|
// }
|
|
//
|
|
// // Load new PDF
|
|
// final response = await http.get(Uri.parse(widget.pdfUrl!));
|
|
// if (response.statusCode == 200) {
|
|
// _cachedPdfBytes = response.bodyBytes;
|
|
// _lastLoadedUrl = widget.pdfUrl;
|
|
// return _cachedPdfBytes!;
|
|
// } else {
|
|
// throw Exception('Failed to load PDF: ${response.statusCode}');
|
|
// }
|
|
// }
|
|
//
|
|
// @override
|
|
// Widget build(BuildContext context) {
|
|
// super.build(context); // Required for AutomaticKeepAliveClientMixin
|
|
//
|
|
// if (widget.pdfUrl == null) {
|
|
// return const Center(child: CircularProgressIndicator());
|
|
// }
|
|
//
|
|
// return FutureBuilder<Uint8List>(
|
|
// key: ValueKey(widget.pdfUrl), // Only rebuild if URL changes
|
|
// future: _loadPdf(),
|
|
// builder: (context, snapshot) {
|
|
// if (snapshot.connectionState == ConnectionState.waiting) {
|
|
// return const Center(child: CircularProgressIndicator());
|
|
// }
|
|
//
|
|
// if (snapshot.hasError) {
|
|
// return Center(child: Text('Error: ${snapshot.error}'));
|
|
// }
|
|
//
|
|
// if (!snapshot.hasData) {
|
|
// return const Center(child: CircularProgressIndicator());
|
|
// }
|
|
//
|
|
// return PdfDocumentLoader.openData(
|
|
// snapshot.data!,
|
|
// documentBuilder: (context, pdfDocument, pageCount) {
|
|
// return ListView.builder(
|
|
// itemCount: pageCount,
|
|
// itemBuilder: (context, index) {
|
|
// return Container(
|
|
// color: Colors.black12,
|
|
// child: PdfPageView(
|
|
// pdfDocument: pdfDocument,
|
|
// pageNumber: index + 1,
|
|
// ),
|
|
// );
|
|
// },
|
|
// );
|
|
// },
|
|
// );
|
|
// },
|
|
// );
|
|
// }
|
|
// } |