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 createState() => _PolicyPdfViewerState(); } class _PolicyPdfViewerState extends State { 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 _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 createState() => _PolicyPdfViewerState(); // } // // class _PolicyPdfViewerState extends State // 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 _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( // 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 // with AutomaticKeepAliveClientMixin { // @override // bool get wantKeepAlive => true; // Prevents rebuilding // // // Cache the loaded PDF bytes // Uint8List? _cachedPdfBytes; // String? _lastLoadedUrl; // // Future _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( // 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, // ), // ); // }, // ); // }, // ); // }, // ); // } // }