From b261c95bd1a8ed7e04d4971648cb51fda74ed61d Mon Sep 17 00:00:00 2001 From: SurendarSuri30 Date: Fri, 23 Jan 2026 19:05:59 +0530 Subject: [PATCH] pdf zooming --- lib/core/config/env.dart | 4 +- .../screens/staff/policy/policyPdf.dart | 351 +++++++++++++++--- web/index.html | 4 +- 3 files changed, 303 insertions(+), 56 deletions(-) diff --git a/lib/core/config/env.dart b/lib/core/config/env.dart index d204ef4..98a3dfd 100644 --- a/lib/core/config/env.dart +++ b/lib/core/config/env.dart @@ -5,8 +5,8 @@ class Env { ); static const String apiUrl = String.fromEnvironment( 'API_URL', - defaultValue: 'https://partner.nhanceindia.in/partner_api/api/', /* Live build (enable index.html line 18) */ - // defaultValue: 'https://venbait.in/nhance/partner/dev/api/', /* Test build (enable index.html line 19) */ + // defaultValue: 'https://partner.nhanceindia.in/partner_api/api/', /* Live build (enable index.html line 18) */ + defaultValue: 'https://venbait.in/nhance/partner/dev/api/', /* Test build (enable index.html line 19) */ // defaultValue: 'http://localhost/nhance_partner_be/', /* localhost build (enable index.html line 19) */ ); // static const String baseUrl = String.fromEnvironment( diff --git a/lib/presentation/screens/staff/policy/policyPdf.dart b/lib/presentation/screens/staff/policy/policyPdf.dart index d234fbb..093bf64 100644 --- a/lib/presentation/screens/staff/policy/policyPdf.dart +++ b/lib/presentation/screens/staff/policy/policyPdf.dart @@ -1,4 +1,5 @@ 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'; @@ -9,7 +10,6 @@ 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; @@ -19,74 +19,321 @@ class PolicyPdfViewer extends StatefulWidget { State createState() => _PolicyPdfViewerState(); } -class _PolicyPdfViewerState extends State - with AutomaticKeepAliveClientMixin { - @override - bool get wantKeepAlive => true; // Prevents rebuilding +class _PolicyPdfViewerState extends State { + Uint8List? _pdfBytes; + String? _loadedUrl; - // Cache the loaded PDF bytes - Uint8List? _cachedPdfBytes; - String? _lastLoadedUrl; + final TransformationController _transformController = + TransformationController(); - Future _loadPdf() async { - // Return cached bytes if URL hasn't changed - if (_cachedPdfBytes != null && _lastLoadedUrl == widget.pdfUrl) { - return _cachedPdfBytes!; - } + double _scale = 1.0; + final double _minScale = 0.7; + final double _maxScale = 3.0; + + final ScrollController _scrollController = ScrollController(); + + Future _loadPdf() async { + if (_pdfBytes != null && _loadedUrl == widget.pdfUrl) return; - // 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}'); + // setState(() { + _pdfBytes = response.bodyBytes; + _loadedUrl = widget.pdfUrl; + // }); + } + } + + void _applyTransform() { + _transformController.value = Matrix4.identity()..scale(_scale); + } + + void _zoomIn() { + setState(() { + _scale = (_scale + 0.2).clamp(_minScale, _maxScale); + _applyTransform(); + }); + } + + void _zoomOut() { + setState(() { + _scale = (_scale - 0.2).clamp(_minScale, _maxScale); + _applyTransform(); + }); + } + + void _resetZoom() { + setState(() { + _scale = 1.0; + _transformController.value = Matrix4.identity(); + }); + } + + @override + void initState() { + super.initState(); + if (widget.pdfUrl != null) { + _loadPdf(); } } @override Widget build(BuildContext context) { - super.build(context); // Required for AutomaticKeepAliveClientMixin - - if (widget.pdfUrl == null) { + if (_pdfBytes == 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()); - } + final bool isZoomed = _scale > 1.0; - 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, + return Stack( + children: [ + InteractiveViewer( + transformationController: _transformController, + panEnabled: isZoomed, // 👈 ONLY pan when zoomed + scaleEnabled: false, // zoom via buttons only + boundaryMargin: const EdgeInsets.all(200), + minScale: _minScale, + maxScale: _maxScale, + child: SingleChildScrollView( + controller: _scrollController, + physics: isZoomed + ? const NeverScrollableScrollPhysics() // ❌ no scroll when zoomed + : const BouncingScrollPhysics(), // ✅ normal scroll + child: PdfDocumentLoader.openData( + _pdfBytes!, + documentBuilder: (context, pdfDocument, pageCount) { + return Column( + children: List.generate( + pageCount, + (index) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: PdfPageView( + pdfDocument: pdfDocument, + pageNumber: index + 1, + ), + ), ), ); }, - ); - }, - ); - }, + ), + ), + ), + + // ─────────── Zoom buttons ─────────── + 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, +// ), +// ); +// }, +// ); +// }, +// ); +// }, +// ); +// } +// } diff --git a/web/index.html b/web/index.html index 5a7d254..a5862da 100644 --- a/web/index.html +++ b/web/index.html @@ -15,8 +15,8 @@ the `--base-href` argument provided to `flutter build`. --> - - + + Testing build: also check env.dart (line 9)