import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_drawing_board/flutter_drawing_board.dart'; import 'package:logging/logging.dart'; import 'package:pdfrx/pdfrx.dart'; import 'package:sheetless/api.dart'; import 'package:sheetless/sheet.dart'; import 'package:sheetless/storage_helper.dart'; class SheetViewerPage extends StatefulWidget { final Sheet sheet; final ApiClient apiClient; final Config config; const SheetViewerPage({ super.key, required this.sheet, required this.apiClient, required this.config, }); @override State createState() => _SheetViewerPageState(); } class _SheetViewerPageState extends State { final log = Logger("SheetViewerPage"); final StorageHelper storageHelper = StorageHelper(); int page = 1; int numPages = 1; late Future documentLoaded; PdfDocument? document; bool paintMode = false; @override void initState() { super.initState(); documentLoaded = loadPdf(); } @override void dispose() { document?.dispose(); // Make sure document gets garbage collected super.dispose(); } Future loadPdf() async { if (kIsWeb) { var data = await widget.apiClient.fetchPdfFileData(widget.sheet.uuid); if (data == null) { throw Exception("Failed fetching pdf file"); } document = await PdfDocument.openData(data); } else { var file = await widget.apiClient.getPdfFileCached(widget.sheet.uuid); if (file == null) { throw Exception("Failed fetching pdf file"); } document = await PdfDocument.openFile(file.path); } return true; } void toggleFullscreen() { widget.config.fullscreen = !widget.config.fullscreen; storageHelper.writeConfig(widget.config); if (widget.config.fullscreen) { log.info("enter fullscreen"); // enter fullscreen SystemChrome.setEnabledSystemUIMode( SystemUiMode.immersiveSticky, overlays: [], ); } else { // exit fullscreen log.info("exit fullscreen"); SystemChrome.setEnabledSystemUIMode( SystemUiMode.edgeToEdge, overlays: SystemUiOverlay.values, ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.sheet.name), actions: [ IconButton( onPressed: () { setState(() { if (widget.config.twoPageMode) { // TODO: notification that paint mode only in single page mode } else { paintMode = !paintMode; } }); }, icon: Icon(Icons.brush), ), IconButton( onPressed: () { setState(() { widget.config.twoPageMode = !widget.config.twoPageMode; storageHelper.writeConfig(widget.config); if (widget.config.twoPageMode) { paintMode = false; // TODO: notification that paint mode was deactivated since only possible in single page mode } }); }, icon: Icon( widget.config.twoPageMode ? Icons.filter_1 : Icons.filter_2, ), ), ], ), body: FutureBuilder( future: documentLoaded, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData && document != null) { numPages = document!.pages.length; return Stack( children: [ Stack( children: [ Visibility( visible: !paintMode, child: GestureDetector( onTapUp: (TapUpDetails details) { // Get the size of the screen final screenWidth = MediaQuery.of(context).size.width; // print("Touch at y = ${details.localPosition.dy}"); // print("Touch at x = ${details.localPosition.dx}"); // print("Screenwidth = ${screenWidth}"); // Check where the user tapped // if (details.localPosition.dy < 100) { // TODO // setState(() { // toggleFullscreen(); // }); if (details.localPosition.dx < screenWidth / 2) { // Left half of the screen setState(() { page = page > 1 ? page - 1 : 1; }); } else { // Right half of the screen setState(() { page = page < numPages ? page + 1 : numPages; }); } }, child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, spacing: 0, children: [ Expanded( child: Stack( children: [ PdfPageView( key: ValueKey(page), document: document, pageNumber: page, maximumDpi: 300, alignment: widget.config.twoPageMode ? Alignment.centerRight : Alignment.center, ), Positioned.fill( child: Container( alignment: Alignment.bottomCenter, padding: EdgeInsets.only(bottom: 5), child: Text('$page / $numPages'), ), ), ], ), ), Visibility( visible: widget.config.twoPageMode == true, child: Expanded( child: Stack( children: [ PdfPageView( key: ValueKey(page), document: document, pageNumber: page + 1, maximumDpi: 300, alignment: Alignment.centerLeft, // alignment: Alignment.center, ), Positioned.fill( child: Container( alignment: Alignment.bottomCenter, padding: EdgeInsets.only(bottom: 5), child: Text('${page + 1} / $numPages'), ), ), ], ), ), ), ], ), ), ), // Positioned.fill( Visibility( visible: paintMode, child: SizedBox.expand( child: LayoutBuilder( builder: (context, constraints) { final maxSize = Size( constraints.maxWidth, constraints.maxHeight, ); final pageSizeUnscaled = document!.pages .elementAt(page) .size; final pageSizeScaled = calcScaledPageSize( maxSize, pageSizeUnscaled, ); return DrawingBoard( background: SizedBox( width: pageSizeScaled.width, height: pageSizeScaled.height, child: PdfPageView( document: document, pageNumber: page, alignment: Alignment.center, ), ), // showDefaultTools: true, // showDefaultActions: true, boardConstrained: true, minScale: 1, maxScale: 3, alignment: Alignment.topRight, boardBoundaryMargin: EdgeInsets.all(0), ); }, ), ), ), ], ), ], ); } else if (snapshot.hasError) { return Center( child: Text( style: Theme.of( context, ).textTheme.displaySmall!.copyWith(color: Colors.red), textAlign: TextAlign.center, snapshot.error.toString(), ), ); } else { return const Center(child: CircularProgressIndicator()); } }, ), ); } Size calcScaledPageSize(Size parentSize, Size pageSize) { // page restricted by height log.info("ParentSize: ${parentSize.width}, ${parentSize.height}"); log.info("ParentSizeRatio: ${parentSize.aspectRatio}"); log.info("PageSizeRatio: ${pageSize.aspectRatio}"); if (parentSize.aspectRatio > pageSize.aspectRatio) { log.info("Restricted by height"); final height = parentSize.height; final width = height * pageSize.aspectRatio; log.info("Size: $width, $height"); return Size(width, height); } // page restricted by width else { log.info("Restricted by height"); final width = parentSize.width; final height = width / pageSize.aspectRatio; log.info("Size: $width, $height"); return Size(width, height); } } }