Add keyboard shortcuts for usage with BT Pedal

This commit is contained in:
2025-09-30 08:43:28 +02:00
parent b7d2580488
commit ab0ff01dee
2 changed files with 214 additions and 160 deletions

View File

@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class BtPedalShortcuts extends StatefulWidget {
final Widget child;
final VoidCallback? onTurnPageForward;
final VoidCallback? onTurnPageBackward;
const BtPedalShortcuts({
super.key,
required this.child,
this.onTurnPageForward,
this.onTurnPageBackward,
});
@override
State<BtPedalShortcuts> createState() => _BtPedalShortcutsState();
}
class _BtPedalShortcutsState extends State<BtPedalShortcuts> {
String lastAction = "Press pedal...";
@override
Widget build(BuildContext context) {
return CallbackShortcuts(
bindings: <ShortcutActivator, VoidCallback>{
// Shortcuts for page forward
const SingleActivator(LogicalKeyboardKey.arrowUp):
widget.onTurnPageForward ?? () => {},
const SingleActivator(LogicalKeyboardKey.arrowRight):
widget.onTurnPageForward ?? () => {},
// Shortcuts for page backward
const SingleActivator(LogicalKeyboardKey.arrowDown):
widget.onTurnPageBackward ?? () => {},
const SingleActivator(LogicalKeyboardKey.arrowLeft):
widget.onTurnPageBackward ?? () => {},
},
child: Focus(autofocus: true, child: widget.child),
);
}
}

View File

@@ -5,6 +5,7 @@ import 'package:flutter_drawing_board/flutter_drawing_board.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:pdfrx/pdfrx.dart'; import 'package:pdfrx/pdfrx.dart';
import 'package:sheetless/api.dart'; import 'package:sheetless/api.dart';
import 'package:sheetless/bt_pedal_shortcuts.dart';
import 'package:sheetless/sheet.dart'; import 'package:sheetless/sheet.dart';
import 'package:sheetless/storage_helper.dart'; import 'package:sheetless/storage_helper.dart';
@@ -86,192 +87,203 @@ class _SheetViewerPageState extends State<SheetViewerPage> {
} }
} }
void turnPage(int numTurns) {
setState(() {
page += numTurns;
page = page.clamp(1, numPages);
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return BtPedalShortcuts(
appBar: AppBar( onTurnPageForward: () => turnPage(1),
title: Text(widget.sheet.name), onTurnPageBackward: () => turnPage(-1),
actions: [ child: Scaffold(
IconButton( appBar: AppBar(
onPressed: () { title: Text(widget.sheet.name),
setState(() { actions: [
if (widget.config.twoPageMode) { IconButton(
// TODO: notification that paint mode only in single page mode onPressed: () {
} else { setState(() {
paintMode = !paintMode; if (widget.config.twoPageMode) {
} // TODO: notification that paint mode only in single page mode
}); } else {
}, paintMode = !paintMode;
icon: Icon(Icons.brush), }
), });
IconButton( },
onPressed: () { icon: Icon(Icons.brush),
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,
), ),
), IconButton(
], onPressed: () {
), setState(() {
body: FutureBuilder( widget.config.twoPageMode = !widget.config.twoPageMode;
future: documentLoaded, storageHelper.writeConfig(widget.config);
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) { if (widget.config.twoPageMode) {
if (snapshot.hasData && document != null) { paintMode = false;
numPages = document!.pages.length; // 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<bool> snapshot) {
if (snapshot.hasData && document != null) {
numPages = document!.pages.length;
return Stack( return Stack(
children: [ children: [
Stack( Stack(
children: [ children: [
Visibility( Visibility(
visible: !paintMode, visible: !paintMode,
child: GestureDetector( child: GestureDetector(
onTapUp: (TapUpDetails details) { onTapUp: (TapUpDetails details) {
// Get the size of the screen // Get the size of the screen
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(
context,
).size.width;
// print("Touch at y = ${details.localPosition.dy}"); // print("Touch at y = ${details.localPosition.dy}");
// print("Touch at x = ${details.localPosition.dx}"); // print("Touch at x = ${details.localPosition.dx}");
// print("Screenwidth = ${screenWidth}"); // print("Screenwidth = ${screenWidth}");
// Check where the user tapped // Check where the user tapped
// if (details.localPosition.dy < 100) { // if (details.localPosition.dy < 100) {
// TODO // TODO
// setState(() { // setState(() {
// toggleFullscreen(); // toggleFullscreen();
// }); // });
if (details.localPosition.dx < screenWidth / 2) { if (details.localPosition.dx < screenWidth / 2) {
// Left half of the screen // Left half of the screen
setState(() { turnPage(-1);
page = page > 1 ? page - 1 : 1; } else {
}); // Right half of the screen
} else { turnPage(1);
// Right half of the screen }
setState(() { },
page = page < numPages ? page + 1 : numPages; child: Row(
}); mainAxisAlignment: MainAxisAlignment.center,
} crossAxisAlignment: CrossAxisAlignment.center,
}, spacing: 0,
child: Row( children: [
mainAxisAlignment: MainAxisAlignment.center, Expanded(
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( child: Stack(
children: [ children: [
PdfPageView( PdfPageView(
key: ValueKey(page), key: ValueKey(page),
document: document, document: document,
pageNumber: page + 1, pageNumber: page,
maximumDpi: 300, maximumDpi: 300,
alignment: Alignment.centerLeft, alignment: widget.config.twoPageMode
// alignment: Alignment.center, ? Alignment.centerRight
: Alignment.center,
), ),
Positioned.fill( Positioned.fill(
child: Container( child: Container(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
padding: EdgeInsets.only(bottom: 5), padding: EdgeInsets.only(bottom: 5),
child: Text('${page + 1} / $numPages'), child: Text('$page / $numPages'),
), ),
), ),
], ],
), ),
), ),
), Visibility(
], visible: widget.config.twoPageMode == true,
), child: Expanded(
), child: Stack(
), children: [
PdfPageView(
// Positioned.fill( key: ValueKey(page),
Visibility( document: document,
visible: paintMode, pageNumber: page + 1,
child: SizedBox.expand( maximumDpi: 300,
child: LayoutBuilder( alignment: Alignment.centerLeft,
builder: (context, constraints) { // alignment: Alignment.center,
final maxSize = Size( ),
constraints.maxWidth, Positioned.fill(
constraints.maxHeight, child: Container(
); alignment: Alignment.bottomCenter,
final pageSizeUnscaled = document!.pages padding: EdgeInsets.only(bottom: 5),
.elementAt(page) child: Text(
.size; '${page + 1} / $numPages',
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),
);
},
), ),
), ),
),
], // 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 {
} else if (snapshot.hasError) { return const Center(child: CircularProgressIndicator());
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());
}
},
), ),
); );
} }