pdf zooming

This commit is contained in:
Surendiran 2026-01-23 19:05:59 +05:30
parent 97759378de
commit b261c95bd1
3 changed files with 303 additions and 56 deletions

View File

@ -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(

View File

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

View File

@ -15,8 +15,8 @@
the `--base-href` argument provided to `flutter build`.
-->
<!-- <base href="$FLUTTER_BASE_HREF">-->
<base href="/partner/"> <!-- Live build: also check env.dart (line 8) -->
<!--<base href="/nhance/partner/app/"> Testing build: also check env.dart (line 9) -->
<!-- <base href="/partner/"> &lt;!&ndash; Live build: also check env.dart (line 8) &ndash;&gt;-->
<base href="/nhance/partner/app/"> Testing build: also check env.dart (line 9)
<meta charset="UTF-8">