diff --git a/lib/sheet_viewer_page.dart b/lib/sheet_viewer_page.dart index 5e23ff0..7f09ffa 100644 --- a/lib/sheet_viewer_page.dart +++ b/lib/sheet_viewer_page.dart @@ -31,11 +31,12 @@ class _SheetViewerPageState extends State final log = Logger("SheetViewerPage"); final StorageHelper storageHelper = StorageHelper(); - int page = 1; + int currentPageNumber = 1; int numPages = 1; late Future documentLoaded; PdfDocument? document; bool paintMode = false; + Pages? pages; @override void initState() { @@ -84,58 +85,67 @@ class _SheetViewerPageState extends State void turnPage(int numTurns) { setState(() { - page += numTurns; - page = page.clamp(1, numPages); + currentPageNumber += numTurns; + currentPageNumber = currentPageNumber.clamp(1, numPages); }); } + AppBar? buildAppBar() { + if (widget.config.fullscreen) { + return null; + } + return 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, + ), + ), + ], + ); + } + @override Widget build(BuildContext context) { return BtPedalShortcuts( onTurnPageForward: () => turnPage(1), onTurnPageBackward: () => turnPage(-1), child: Scaffold( - appBar: widget.config.fullscreen - ? null - : 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, - ), - ), - ], - ), + appBar: buildAppBar(), body: FutureBuilder( future: documentLoaded, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData && document != null) { numPages = document!.pages.length; + pages = Pages( + document: document!, + numPages: numPages, + config: widget.config, + currentPageNumber: currentPageNumber, + ); return Stack( children: [ @@ -143,127 +153,22 @@ class _SheetViewerPageState extends State 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(); - }); - } else if (details.localPosition.dx < - screenWidth / 2) { - // Left half of the screen - turnPage(-1); - } else { - // Right half of the screen - turnPage(1); - } + child: TouchablePages( + pages: pages!, + onToggleFullscreen: () { + toggleFullscreen(); + }, + onExitSheetViewer: () { + Navigator.pop(context); + }, + onTurnPage: (int numTurns) { + turnPage(numTurns); }, - 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), - ); - }, - ), - ), + child: PaintablePages(pages: pages!), ), ], ), @@ -287,25 +192,183 @@ class _SheetViewerPageState extends State ), ); } +} - Size calcScaledPageSize(Size parentSize, Size pageSize) { +typedef PageturnCallback = void Function(int numTurns); + +class TouchablePages extends StatelessWidget { + final Pages pages; + final VoidCallback onToggleFullscreen; + final VoidCallback onExitSheetViewer; + final PageturnCallback onTurnPage; + + const TouchablePages({ + super.key, + required this.pages, + required this.onToggleFullscreen, + required this.onExitSheetViewer, + required this.onTurnPage, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: + HitTestBehavior.opaque, // Also register when outside of pdf pages + onTapUp: (TapUpDetails details) { + final mediaQuery = MediaQuery.of(context); + final pixelsPerInch = + mediaQuery.devicePixelRatio * + 160; // 160 dpi = 1 logical inch baseline + final pixelsPerCm = pixelsPerInch / 2.54; + + final touchAreaWidth = mediaQuery.size.width; + + if (details.localPosition.dy < 2 * pixelsPerCm && + details.localPosition.dx >= touchAreaWidth - 2 * pixelsPerCm && + pages.config.fullscreen) { + onExitSheetViewer(); + } else if (details.localPosition.dy < 2 * pixelsPerCm) { + onToggleFullscreen(); + } else if (details.localPosition.dx < touchAreaWidth / 4 && + pages.config.twoPageMode) { + onTurnPage(-2); + } else if (details.localPosition.dx < touchAreaWidth / 2) { + onTurnPage(-1); + } else if (details.localPosition.dx > touchAreaWidth * 3 / 4 && + pages.config.twoPageMode) { + onTurnPage(2); + } else { + onTurnPage(1); + } + }, + child: pages, + ); + } +} + +class PaintablePages extends StatelessWidget { + final Pages pages; + + const PaintablePages({super.key, required this.pages}); + + @override + Widget build(BuildContext context) { + return SizedBox.expand( + child: LayoutBuilder( + builder: (context, constraints) { + final maxSize = Size(constraints.maxWidth, constraints.maxHeight); + final pageSizeScaled = pages.calcCurrentPageSizeScaled(maxSize); + return DrawingBoard( + background: SizedBox( + width: pageSizeScaled.width, + height: pageSizeScaled.height, + child: pages, + ), + // showDefaultTools: true, + // showDefaultActions: true, + boardConstrained: true, + minScale: 1, + maxScale: 3, + alignment: Alignment.topRight, + boardBoundaryMargin: EdgeInsets.all(0), + ); + }, + ), + ); + } +} + +class Pages extends StatelessWidget { + final PdfDocument document; + final int numPages; + final int currentPageNumber; + final Config config; + + const Pages({ + super.key, + required this.document, + required this.numPages, + required this.currentPageNumber, + required this.config, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + spacing: 0, + children: [ + Expanded( + child: Stack( + children: [ + PdfPageView( + key: ValueKey(currentPageNumber), + document: document, + pageNumber: currentPageNumber, + maximumDpi: 300, + alignment: config.twoPageMode + ? Alignment.centerRight + : Alignment.center, + ), + Positioned.fill( + child: Container( + alignment: Alignment.bottomCenter, + padding: EdgeInsets.only(bottom: 5), + child: Text('$currentPageNumber / $numPages'), + ), + ), + ], + ), + ), + Visibility( + visible: config.twoPageMode == true, + child: Expanded( + child: Stack( + children: [ + PdfPageView( + key: ValueKey(currentPageNumber), + document: document, + pageNumber: currentPageNumber + 1, + maximumDpi: 300, + alignment: Alignment.centerLeft, + // alignment: Alignment.center, + ), + Positioned.fill( + child: Container( + alignment: Alignment.bottomCenter, + padding: EdgeInsets.only(bottom: 5), + child: Text('${currentPageNumber + 1} / $numPages'), + ), + ), + ], + ), + ), + ), + ], + ); + } + + Size calcCurrentPageSizeScaled(Size parentSize) { + return _calcScaledPageSize(parentSize, _getCurrentPageSizeUnscaled()); + } + + Size _getCurrentPageSizeUnscaled() { + return document.pages.elementAt(currentPageNumber).size; + } + + 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); } }