Created
September 12, 2025 09:41
-
-
Save rubywai/37a55d523082e1b6fea82a51c6ec9a29 to your computer and use it in GitHub Desktop.
pdf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class PdfViewer extends ConsumerStatefulWidget { | |
| const PdfViewer({ | |
| super.key, | |
| this.filePath, | |
| this.fileUrl, | |
| }); | |
| final String? filePath; | |
| final String? fileUrl; | |
| @override | |
| ConsumerState createState() => _PdfViewerState(); | |
| /// Show from URL (download + cache) | |
| static Future<dynamic> showFromUrl({ | |
| required BuildContext context, | |
| required String url, | |
| }) { | |
| return showGeneralDialog( | |
| context: context, | |
| transitionDuration: const Duration(milliseconds: 0), | |
| pageBuilder: (context, __, ___) { | |
| return PdfViewer(fileUrl: url); | |
| }, | |
| ); | |
| } | |
| /// Show from local file path | |
| static Future<dynamic> showFromFile({ | |
| required BuildContext context, | |
| required String path, | |
| }) { | |
| return showGeneralDialog( | |
| context: context, | |
| transitionDuration: const Duration(milliseconds: 0), | |
| pageBuilder: (context, __, ___) { | |
| return PdfViewer(filePath: path); | |
| }, | |
| ); | |
| } | |
| } | |
| class _PdfViewerState extends ConsumerState<PdfViewer> { | |
| int _currentPage = 0; | |
| int _totalPages = 0; | |
| String? _filePath; | |
| bool _isLoading = false; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| if (widget.filePath != null) { | |
| // Local file → no loading | |
| _filePath = widget.filePath; | |
| } else if (widget.fileUrl != null) { | |
| // Remote file → show loader until cached | |
| _isLoading = true; | |
| DefaultCacheManager().getSingleFile(widget.fileUrl!).then((file) { | |
| if (mounted) { | |
| setState(() { | |
| _filePath = file.path; | |
| _isLoading = false; | |
| }); | |
| } | |
| }).catchError((e) { | |
| debugPrint("Failed to load PDF: $e"); | |
| if (mounted) { | |
| setState(() { | |
| _isLoading = false; | |
| }); | |
| } | |
| }); | |
| } | |
| ScreenRotationUtils.enableRotation(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| final falconVars = FalconUxVariables(context); | |
| final fileName = (_filePath ?? widget.fileUrl ?? '').split('/').last; | |
| return Scaffold( | |
| appBar: MobileTopHeader( | |
| leadingAction: IconButton( | |
| style: falconVars.mediumIconBtn, | |
| icon: FaIcon( | |
| const IconDataLight(FA.chevronLeft), | |
| size: falconVars.size20, | |
| color: falconVars.onSurface, | |
| ), | |
| onPressed: () => Navigator.pop(context), | |
| ), | |
| titleWidget: Expanded( | |
| child: Text( | |
| fileName, | |
| maxLines: 1, | |
| style: | |
| falconVars.headlineLarge.copyWith(fontWeight: FontWeight.w700), | |
| overflow: TextOverflow.ellipsis, | |
| ), | |
| ), | |
| secondaryActions: [ | |
| if (_filePath != null) | |
| IconButton( | |
| style: falconVars.mediumIconBtn, | |
| icon: FaIcon( | |
| const IconDataLight(FA.shareNodes), | |
| size: falconVars.size20, | |
| color: falconVars.onSurface, | |
| ), | |
| onPressed: () { | |
| ShareUtil.sharePDF(_filePath!, fileName); | |
| }, | |
| ), | |
| ], | |
| ), | |
| body: Stack( | |
| children: [ | |
| if (_filePath != null) | |
| PDFView( | |
| filePath: _filePath, | |
| enableSwipe: true, | |
| swipeHorizontal: true, | |
| onPageChanged: (int? page, int? total) { | |
| setState(() { | |
| _currentPage = (page ?? 0) + 1; | |
| _totalPages = total ?? _totalPages; | |
| }); | |
| }, | |
| onRender: (pages) { | |
| setState(() { | |
| _totalPages = pages ?? _totalPages; | |
| _currentPage = pages != null ? 1 : _currentPage; | |
| }); | |
| }, | |
| ), | |
| if (_filePath != null) | |
| Positioned( | |
| bottom: 16, | |
| left: 0, | |
| right: 0, | |
| child: Center( | |
| child: Text('$_currentPage / $_totalPages Pages'), | |
| ), | |
| ), | |
| if (_isLoading) | |
| const Positioned.fill( | |
| child: Center( | |
| child: SizedBox( | |
| width: 20, | |
| height: 20, | |
| child: CircularProgressIndicator(strokeWidth: 1), | |
| ), | |
| ), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| @override | |
| void dispose() { | |
| ScreenRotationUtils.disableRotation(); | |
| super.dispose(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment