Skip to content

Instantly share code, notes, and snippets.

@rubywai
Created September 12, 2025 09:41
Show Gist options
  • Select an option

  • Save rubywai/37a55d523082e1b6fea82a51c6ec9a29 to your computer and use it in GitHub Desktop.

Select an option

Save rubywai/37a55d523082e1b6fea82a51c6ec9a29 to your computer and use it in GitHub Desktop.
pdf
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