feat: complete app
This commit is contained in:
227
lib/barcode_scanner_window.dart
Normal file
227
lib/barcode_scanner_window.dart
Normal file
@ -0,0 +1,227 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/scanned_barcode_label.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/scanner_error_widget.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
class BarcodeScannerWithScanWindow extends StatefulWidget {
|
||||
const BarcodeScannerWithScanWindow({super.key});
|
||||
|
||||
@override
|
||||
State<BarcodeScannerWithScanWindow> createState() =>
|
||||
_BarcodeScannerWithScanWindowState();
|
||||
}
|
||||
|
||||
class _BarcodeScannerWithScanWindowState
|
||||
extends State<BarcodeScannerWithScanWindow> {
|
||||
final MobileScannerController controller = MobileScannerController();
|
||||
|
||||
Widget _buildBarcodeOverlay() {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: controller,
|
||||
builder: (context, value, child) {
|
||||
// Not ready.
|
||||
if (!value.isInitialized || !value.isRunning || value.error != null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return StreamBuilder<BarcodeCapture>(
|
||||
stream: controller.barcodes,
|
||||
builder: (context, snapshot) {
|
||||
final BarcodeCapture? barcodeCapture = snapshot.data;
|
||||
|
||||
// No barcode.
|
||||
if (barcodeCapture == null || barcodeCapture.barcodes.isEmpty) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
final scannedBarcode = barcodeCapture.barcodes.first;
|
||||
|
||||
// No barcode corners, or size, or no camera preview size.
|
||||
if (scannedBarcode.corners.isEmpty ||
|
||||
value.size.isEmpty ||
|
||||
barcodeCapture.size.isEmpty) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return CustomPaint(
|
||||
painter: BarcodeOverlay(
|
||||
barcodeCorners: scannedBarcode.corners,
|
||||
barcodeSize: barcodeCapture.size,
|
||||
boxFit: BoxFit.contain,
|
||||
cameraPreviewSize: value.size,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScanWindow(Rect scanWindowRect) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: controller,
|
||||
builder: (context, value, child) {
|
||||
// Not ready.
|
||||
if (!value.isInitialized ||
|
||||
!value.isRunning ||
|
||||
value.error != null ||
|
||||
value.size.isEmpty) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return CustomPaint(
|
||||
painter: ScannerOverlay(scanWindowRect),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scanWindow = Rect.fromCenter(
|
||||
center: MediaQuery.sizeOf(context).center(Offset.zero),
|
||||
width: 200,
|
||||
height: 200,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('With Scan window')),
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
MobileScanner(
|
||||
fit: BoxFit.contain,
|
||||
scanWindow: scanWindow,
|
||||
controller: controller,
|
||||
errorBuilder: (context, error, child) {
|
||||
return ScannerErrorWidget(error: error);
|
||||
},
|
||||
),
|
||||
_buildBarcodeOverlay(),
|
||||
_buildScanWindow(scanWindow),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
height: 100,
|
||||
color: Colors.black.withOpacity(0.4),
|
||||
child: ScannedBarcodeLabel(barcodes: controller.barcodes),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
super.dispose();
|
||||
await controller.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class ScannerOverlay extends CustomPainter {
|
||||
ScannerOverlay(this.scanWindow);
|
||||
|
||||
final Rect scanWindow;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
// TODO: use `Offset.zero & size` instead of Rect.largest
|
||||
// we need to pass the size to the custom paint widget
|
||||
final backgroundPath = Path()..addRect(Rect.largest);
|
||||
final cutoutPath = Path()..addRect(scanWindow);
|
||||
|
||||
final backgroundPaint = Paint()
|
||||
..color = Colors.black.withOpacity(0.5)
|
||||
..style = PaintingStyle.fill
|
||||
..blendMode = BlendMode.dstOut;
|
||||
|
||||
final backgroundWithCutout = Path.combine(
|
||||
PathOperation.difference,
|
||||
backgroundPath,
|
||||
cutoutPath,
|
||||
);
|
||||
canvas.drawPath(backgroundWithCutout, backgroundPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class BarcodeOverlay extends CustomPainter {
|
||||
BarcodeOverlay({
|
||||
required this.barcodeCorners,
|
||||
required this.barcodeSize,
|
||||
required this.boxFit,
|
||||
required this.cameraPreviewSize,
|
||||
});
|
||||
|
||||
final List<Offset> barcodeCorners;
|
||||
final Size barcodeSize;
|
||||
final BoxFit boxFit;
|
||||
final Size cameraPreviewSize;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (barcodeCorners.isEmpty ||
|
||||
barcodeSize.isEmpty ||
|
||||
cameraPreviewSize.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final adjustedSize = applyBoxFit(boxFit, cameraPreviewSize, size);
|
||||
|
||||
double verticalPadding = size.height - adjustedSize.destination.height;
|
||||
double horizontalPadding = size.width - adjustedSize.destination.width;
|
||||
if (verticalPadding > 0) {
|
||||
verticalPadding = verticalPadding / 2;
|
||||
} else {
|
||||
verticalPadding = 0;
|
||||
}
|
||||
|
||||
if (horizontalPadding > 0) {
|
||||
horizontalPadding = horizontalPadding / 2;
|
||||
} else {
|
||||
horizontalPadding = 0;
|
||||
}
|
||||
|
||||
final double ratioWidth;
|
||||
final double ratioHeight;
|
||||
|
||||
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.iOS) {
|
||||
ratioWidth = barcodeSize.width / adjustedSize.destination.width;
|
||||
ratioHeight = barcodeSize.height / adjustedSize.destination.height;
|
||||
} else {
|
||||
ratioWidth = cameraPreviewSize.width / adjustedSize.destination.width;
|
||||
ratioHeight = cameraPreviewSize.height / adjustedSize.destination.height;
|
||||
}
|
||||
|
||||
final List<Offset> adjustedOffset = [
|
||||
for (final offset in barcodeCorners)
|
||||
Offset(
|
||||
offset.dx / ratioWidth + horizontalPadding,
|
||||
offset.dy / ratioHeight + verticalPadding,
|
||||
),
|
||||
];
|
||||
|
||||
final cutoutPath = Path()..addPolygon(adjustedOffset, true);
|
||||
|
||||
final backgroundPaint = Paint()
|
||||
..color = Colors.red.withOpacity(0.3)
|
||||
..style = PaintingStyle.fill
|
||||
..blendMode = BlendMode.dstOut;
|
||||
|
||||
canvas.drawPath(cutoutPath, backgroundPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return false;
|
||||
}
|
||||
}
|
87
lib/controller/gamecontroller.dart
Normal file
87
lib/controller/gamecontroller.dart
Normal file
@ -0,0 +1,87 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
class GameController extends GetxController {
|
||||
final String markerSequence;
|
||||
final TextEditingController markerSequenceController =
|
||||
TextEditingController();
|
||||
|
||||
final String adminPass;
|
||||
|
||||
final Map<String, String> markerNumbersToDeclaration = {
|
||||
"4511338007860": "YG06", // Yellowish Green
|
||||
"4511338007778": "YR07", // Pale Cherry Pink
|
||||
"4511338007655": "RV04", // Shock Pink
|
||||
"4511338007808": "Y06", // Yellow
|
||||
"4511338007983": "BG09", // Blue Green
|
||||
"4511338007716": "R29", // Lipstick Red
|
||||
"4511338008058": "B29", // Ultramarine
|
||||
"4511338008171": "E29", // Brown
|
||||
"4511338007921": "G17", // Forest Green
|
||||
"4511338007594": "V09", // Violet
|
||||
"4511338008232": "100" // Black
|
||||
};
|
||||
|
||||
final String calendarQrData;
|
||||
final TextEditingController calendarQrDataController =
|
||||
TextEditingController();
|
||||
|
||||
final String finalCode;
|
||||
final TextEditingController finalCodeController = TextEditingController();
|
||||
|
||||
String storyTitle = "Das Verlassene Museum";
|
||||
String story =
|
||||
"Die Sonne stand hoch am Himmel, als Jonas durch die stillen Straßen seiner kleinen Stadt schlich. "
|
||||
"Der Wind trug das Flüstern der Bäume und das entfernte Lachen spielender Kinder mit sich, "
|
||||
"doch Jonas hatte nur ein Ziel vor Augen: das verlassene Museum am Stadtrand.\n\n"
|
||||
"Seit Jahren hörte er Geschichten über diesen geheimnisvollen Ort, und heute war der Tag, an dem er den Mut fand, ihn selbst zu erkunden. "
|
||||
"Mit klopfendem Herzen näherte sich Jonas den schweren Holztüren des Museums. "
|
||||
"Ein leises Knarren durchbrach die Stille, als er sie langsam öffnete, und ein Schauer lief ihm über den Rücken. "
|
||||
"Die Luft im Inneren war kühl und roch nach Staub und alten Geheimnissen. "
|
||||
"Jonas zückte seine Taschenlampe und trat ein.\n\n"
|
||||
"Die Strahlen der Lampe tanzten über verstaubte Vitrinen, in denen antike Artefakte lagen, die von längst vergangenen Zeiten erzählten. "
|
||||
"Verblasste Gemälde hingen an den Wänden, ihre Farben von der Zeit verschluckt, und seltsame Skulpturen warfen lange Schatten auf den Boden. "
|
||||
"Jonas spürte eine Mischung aus Ehrfurcht und Aufregung. "
|
||||
"Jeder Schritt, den er machte, ließ den Boden leise knarren, als ob das Museum selbst ihm seine Geschichten zuflüstern wollte.\n\n"
|
||||
"In einem der hinteren Räume hielt Jonas inne. "
|
||||
"Vor ihm stand ein alter Mechanismus, bedeckt mit Symbolen und Zahlen, die ihm fremd waren. "
|
||||
"Fasziniert trat er näher und strich mit den Fingern über die kalte Oberfläche. "
|
||||
"Es war, als ob das Rätsel ihn rief, als ob es darauf wartete, von ihm gelöst zu werden.\n\n"
|
||||
"Jonas setzte sich auf den Boden und begann, die Symbole zu studieren. "
|
||||
"Sein Verstand arbeitete fieberhaft, während er versuchte, die Bedeutung hinter den Zeichen zu entschlüsseln. "
|
||||
"Die Welt um ihn herum schien zu verschwinden, und er war allein mit dem Geheimnis, das das Museum verbarg.\n\n"
|
||||
"Stunden vergingen, oder vielleicht waren es nur Minuten, als Jonas schließlich aufstand. "
|
||||
"Er hatte das Rätsel nicht gelöst, doch er fühlte sich nicht enttäuscht. "
|
||||
"Stattdessen erfüllte ihn ein Gefühl der Erfüllung. "
|
||||
"Er hatte den Mut gefunden, sich seinen Ängsten zu stellen und das Abenteuer seines Lebens zu erleben.\n\n"
|
||||
"Als Jonas das Museum verließ, war die Sonne bereits untergegangen, und die ersten Sterne funkelten am Himmel. "
|
||||
"Er blickte zurück auf das alte Gebäude, das nun in der Dunkelheit lag, und lächelte. "
|
||||
"Die Welt war voller Geheimnisse, und er wusste, dass dies nur der Anfang seiner Entdeckungsreise war.";
|
||||
|
||||
final MobileScannerController markerPageScannerController =
|
||||
MobileScannerController(
|
||||
facing: CameraFacing.back,
|
||||
formats: [BarcodeFormat.ean13],
|
||||
autoStart: false,
|
||||
detectionSpeed: DetectionSpeed.noDuplicates,
|
||||
);
|
||||
|
||||
final MobileScannerController qrPageController = MobileScannerController(
|
||||
facing: CameraFacing.back,
|
||||
cameraResolution: const Size(600, 600),
|
||||
formats: [BarcodeFormat.qrCode],
|
||||
autoStart: true,
|
||||
detectionSpeed: DetectionSpeed.noDuplicates,
|
||||
);
|
||||
|
||||
GameController(
|
||||
{required this.markerSequence,
|
||||
required this.calendarQrData,
|
||||
this.finalCode = "17824",
|
||||
required this.adminPass});
|
||||
|
||||
checkMarkerString() =>
|
||||
markerSequenceController.text.toUpperCase().trim().replaceAll(' ', '') ==
|
||||
markerSequence.toUpperCase().trim().replaceAll(' ', '');
|
||||
}
|
13
lib/controller/mainpagecontroller.dart
Normal file
13
lib/controller/mainpagecontroller.dart
Normal file
@ -0,0 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/controller/gamecontroller.dart';
|
||||
|
||||
class MainPageController extends GetxController {
|
||||
final TextEditingController markerSeqController = TextEditingController();
|
||||
final TextEditingController calendarQrDataController =
|
||||
TextEditingController();
|
||||
final TextEditingController finalCodeController =
|
||||
TextEditingController(text: '17824');
|
||||
final TextEditingController adminPassController =
|
||||
TextEditingController(text: 'lottisGeburtstag2024!');
|
||||
}
|
118
lib/main.dart
Normal file
118
lib/main.dart
Normal file
@ -0,0 +1,118 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/barcode_scanner_window.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/controller/gamecontroller.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/controller/mainpagecontroller.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/mobile_scanner_overlay.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/screens/marker.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetMaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData.dark().copyWith(
|
||||
colorScheme: const ColorScheme.dark(
|
||||
primary: Colors.deepPurple,
|
||||
secondary: Colors.deepPurpleAccent,
|
||||
),
|
||||
),
|
||||
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MainPageController c = Get.put(MainPageController());
|
||||
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BarcodeScannerWithOverlay(),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('MobileScanner with Overlay'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BarcodeScannerWithScanWindow(),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('MobileScanner with Scan Window'),
|
||||
),
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Marker Sequence',
|
||||
hintText: 'Enter marker sequence',
|
||||
),
|
||||
controller: c.markerSeqController,
|
||||
),
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Calendar QR Data',
|
||||
hintText: 'Enter calendar QR data',
|
||||
),
|
||||
controller: c.calendarQrDataController,
|
||||
),
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Final Code',
|
||||
hintText: 'Enter final code',
|
||||
),
|
||||
controller: c.finalCodeController,
|
||||
),
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Admin Pass',
|
||||
hintText: 'Enter admin pass',
|
||||
),
|
||||
controller: c.adminPassController,
|
||||
),
|
||||
ElevatedButton(
|
||||
child: const Text('Start Game'),
|
||||
onPressed: () {
|
||||
Get.put(
|
||||
GameController(
|
||||
markerSequence: c.markerSeqController.text,
|
||||
calendarQrData: c.calendarQrDataController.text,
|
||||
finalCode: c.finalCodeController.text,
|
||||
adminPass: c.adminPassController.text),
|
||||
permanent: true);
|
||||
Get.offAll(const MarkerScreen());
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
180
lib/mobile_scanner_overlay.dart
Normal file
180
lib/mobile_scanner_overlay.dart
Normal file
@ -0,0 +1,180 @@
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/scanned_barcode_label.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/scanner_button_widgets.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/scanner_error_widget.dart';
|
||||
|
||||
class BarcodeScannerWithOverlay extends StatefulWidget {
|
||||
final MobileScannerController? controller;
|
||||
|
||||
const BarcodeScannerWithOverlay({Key? key, this.controller})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_BarcodeScannerWithOverlayState createState() =>
|
||||
_BarcodeScannerWithOverlayState();
|
||||
}
|
||||
|
||||
class _BarcodeScannerWithOverlayState extends State<BarcodeScannerWithOverlay> {
|
||||
late MobileScannerController _controller;
|
||||
final AudioPlayer _audioPlayer = AudioPlayer();
|
||||
|
||||
Future<void> _playBeepSound() async {
|
||||
await _audioPlayer.play(AssetSource('sounds/beep.mp3'));
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = widget.controller ??
|
||||
MobileScannerController(
|
||||
formats: const [BarcodeFormat.ean13],
|
||||
detectionSpeed: DetectionSpeed.noDuplicates);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scanWindow = Rect.fromCenter(
|
||||
center: MediaQuery.sizeOf(context).center(Offset.zero),
|
||||
width: 300,
|
||||
height: 40,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
title: const Text('Scanner with Overlay Example app'),
|
||||
),
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: MobileScanner(
|
||||
fit: BoxFit.contain,
|
||||
controller: _controller,
|
||||
scanWindow: scanWindow,
|
||||
errorBuilder: (context, error, child) {
|
||||
return ScannerErrorWidget(error: error);
|
||||
},
|
||||
overlayBuilder: (context, constraints) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: ScannedBarcodeLabel(barcodes: _controller.barcodes),
|
||||
),
|
||||
);
|
||||
},
|
||||
onDetect: (barcodes) {
|
||||
if (barcodes.barcodes.isNotEmpty) {
|
||||
print("hi");
|
||||
_playBeepSound();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _controller,
|
||||
builder: (context, value, child) {
|
||||
if (!value.isInitialized ||
|
||||
!value.isRunning ||
|
||||
value.error != null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return CustomPaint(
|
||||
painter: ScannerOverlay(scanWindow: scanWindow),
|
||||
);
|
||||
},
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ToggleFlashlightButton(controller: _controller),
|
||||
SwitchCameraButton(controller: _controller),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
super.dispose();
|
||||
if (widget.controller == null) {
|
||||
await _controller.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ScannerOverlay extends CustomPainter {
|
||||
const ScannerOverlay({
|
||||
required this.scanWindow,
|
||||
this.borderRadius = 12.0,
|
||||
});
|
||||
|
||||
final Rect scanWindow;
|
||||
final double borderRadius;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
// TODO: use `Offset.zero & size` instead of Rect.largest
|
||||
// we need to pass the size to the custom paint widget
|
||||
final backgroundPath = Path()..addRect(Rect.largest);
|
||||
|
||||
final cutoutPath = Path()
|
||||
..addRRect(
|
||||
RRect.fromRectAndCorners(
|
||||
scanWindow,
|
||||
topLeft: Radius.circular(borderRadius),
|
||||
topRight: Radius.circular(borderRadius),
|
||||
bottomLeft: Radius.circular(borderRadius),
|
||||
bottomRight: Radius.circular(borderRadius),
|
||||
),
|
||||
);
|
||||
|
||||
final backgroundPaint = Paint()
|
||||
..color = Colors.black.withOpacity(0.5)
|
||||
..style = PaintingStyle.fill
|
||||
..blendMode = BlendMode.dstOut;
|
||||
|
||||
final backgroundWithCutout = Path.combine(
|
||||
PathOperation.difference,
|
||||
backgroundPath,
|
||||
cutoutPath,
|
||||
);
|
||||
|
||||
final borderPaint = Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 4.0;
|
||||
|
||||
final borderRect = RRect.fromRectAndCorners(
|
||||
scanWindow,
|
||||
topLeft: Radius.circular(borderRadius),
|
||||
topRight: Radius.circular(borderRadius),
|
||||
bottomLeft: Radius.circular(borderRadius),
|
||||
bottomRight: Radius.circular(borderRadius),
|
||||
);
|
||||
|
||||
// First, draw the background,
|
||||
// with a cutout area that is a bit larger than the scan window.
|
||||
// Finally, draw the scan window itself.
|
||||
canvas.drawPath(backgroundWithCutout, backgroundPaint);
|
||||
canvas.drawRRect(borderRect, borderPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(ScannerOverlay oldDelegate) {
|
||||
return scanWindow != oldDelegate.scanWindow ||
|
||||
borderRadius != oldDelegate.borderRadius;
|
||||
}
|
||||
}
|
35
lib/scanned_barcode_label.dart
Normal file
35
lib/scanned_barcode_label.dart
Normal file
@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
class ScannedBarcodeLabel extends StatelessWidget {
|
||||
const ScannedBarcodeLabel({
|
||||
super.key,
|
||||
required this.barcodes,
|
||||
});
|
||||
|
||||
final Stream<BarcodeCapture> barcodes;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder(
|
||||
stream: barcodes,
|
||||
builder: (context, snapshot) {
|
||||
final scannedBarcodes = snapshot.data?.barcodes ?? [];
|
||||
|
||||
if (scannedBarcodes.isEmpty) {
|
||||
return const Text(
|
||||
'Scan something!',
|
||||
overflow: TextOverflow.fade,
|
||||
style: TextStyle(color: Colors.white),
|
||||
);
|
||||
}
|
||||
|
||||
return Text(
|
||||
scannedBarcodes.first.displayValue ?? 'No display value.',
|
||||
overflow: TextOverflow.fade,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
177
lib/scanner_button_widgets.dart
Normal file
177
lib/scanner_button_widgets.dart
Normal file
@ -0,0 +1,177 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
class AnalyzeImageFromGalleryButton extends StatelessWidget {
|
||||
const AnalyzeImageFromGalleryButton({required this.controller, super.key});
|
||||
|
||||
final MobileScannerController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
color: Colors.white,
|
||||
icon: const Icon(Icons.image),
|
||||
iconSize: 32.0,
|
||||
onPressed: () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
|
||||
final XFile? image = await picker.pickImage(
|
||||
source: ImageSource.gallery,
|
||||
);
|
||||
|
||||
if (image == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BarcodeCapture? barcodes = await controller.analyzeImage(
|
||||
image.path,
|
||||
);
|
||||
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SnackBar snackbar = barcodes != null
|
||||
? const SnackBar(
|
||||
content: Text('Barcode found!'),
|
||||
backgroundColor: Colors.green,
|
||||
)
|
||||
: const SnackBar(
|
||||
content: Text('No barcode found!'),
|
||||
backgroundColor: Colors.red,
|
||||
);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackbar);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StartStopMobileScannerButton extends StatelessWidget {
|
||||
const StartStopMobileScannerButton({required this.controller, super.key});
|
||||
|
||||
final MobileScannerController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: controller,
|
||||
builder: (context, state, child) {
|
||||
if (!state.isInitialized || !state.isRunning) {
|
||||
return IconButton(
|
||||
color: Colors.white,
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
iconSize: 32.0,
|
||||
onPressed: () async {
|
||||
await controller.start();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return IconButton(
|
||||
color: Colors.white,
|
||||
icon: const Icon(Icons.stop),
|
||||
iconSize: 32.0,
|
||||
onPressed: () async {
|
||||
await controller.stop();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SwitchCameraButton extends StatelessWidget {
|
||||
const SwitchCameraButton({required this.controller, super.key});
|
||||
|
||||
final MobileScannerController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: controller,
|
||||
builder: (context, state, child) {
|
||||
if (!state.isInitialized || !state.isRunning) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final int? availableCameras = state.availableCameras;
|
||||
|
||||
if (availableCameras != null && availableCameras < 2) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final Widget icon;
|
||||
|
||||
switch (state.cameraDirection) {
|
||||
case CameraFacing.front:
|
||||
icon = const Icon(Icons.camera_front);
|
||||
case CameraFacing.back:
|
||||
icon = const Icon(Icons.camera_rear);
|
||||
}
|
||||
|
||||
return IconButton(
|
||||
iconSize: 32.0,
|
||||
icon: icon,
|
||||
onPressed: () async {
|
||||
await controller.switchCamera();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleFlashlightButton extends StatelessWidget {
|
||||
const ToggleFlashlightButton({required this.controller, super.key});
|
||||
|
||||
final MobileScannerController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: controller,
|
||||
builder: (context, state, child) {
|
||||
if (!state.isInitialized || !state.isRunning) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
switch (state.torchState) {
|
||||
case TorchState.auto:
|
||||
return IconButton(
|
||||
color: Colors.white,
|
||||
iconSize: 32.0,
|
||||
icon: const Icon(Icons.flash_auto),
|
||||
onPressed: () async {
|
||||
await controller.toggleTorch();
|
||||
},
|
||||
);
|
||||
case TorchState.off:
|
||||
return IconButton(
|
||||
color: Colors.white,
|
||||
iconSize: 32.0,
|
||||
icon: const Icon(Icons.flash_off),
|
||||
onPressed: () async {
|
||||
await controller.toggleTorch();
|
||||
},
|
||||
);
|
||||
case TorchState.on:
|
||||
return IconButton(
|
||||
color: Colors.white,
|
||||
iconSize: 32.0,
|
||||
icon: const Icon(Icons.flash_on),
|
||||
onPressed: () async {
|
||||
await controller.toggleTorch();
|
||||
},
|
||||
);
|
||||
case TorchState.unavailable:
|
||||
return const Icon(
|
||||
Icons.no_flash,
|
||||
color: Colors.grey,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
48
lib/scanner_error_widget.dart
Normal file
48
lib/scanner_error_widget.dart
Normal file
@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
class ScannerErrorWidget extends StatelessWidget {
|
||||
const ScannerErrorWidget({super.key, required this.error});
|
||||
|
||||
final MobileScannerException error;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String errorMessage;
|
||||
|
||||
switch (error.errorCode) {
|
||||
case MobileScannerErrorCode.controllerUninitialized:
|
||||
errorMessage = 'Controller not ready.';
|
||||
case MobileScannerErrorCode.permissionDenied:
|
||||
errorMessage = 'Permission denied';
|
||||
case MobileScannerErrorCode.unsupported:
|
||||
errorMessage = 'Scanning is unsupported on this device';
|
||||
default:
|
||||
errorMessage = 'Generic Error';
|
||||
break;
|
||||
}
|
||||
|
||||
return ColoredBox(
|
||||
color: Colors.black,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
child: Icon(Icons.error, color: Colors.white),
|
||||
),
|
||||
Text(
|
||||
errorMessage,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
Text(
|
||||
error.errorDetails?.message ?? '',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
100
lib/screens/admin.dart
Normal file
100
lib/screens/admin.dart
Normal file
@ -0,0 +1,100 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/controller/gamecontroller.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/screens/marker.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/screens/pattern.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/screens/qr.dart';
|
||||
|
||||
void showAdminLoginPopup() {
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
final GameController c = Get.find();
|
||||
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: const Text('Admin Login'),
|
||||
content: TextField(
|
||||
controller: passwordController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter admin password',
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (passwordController.text == c.adminPass) {
|
||||
Get.back();
|
||||
Get.to(() => const AdminPage());
|
||||
} else {
|
||||
// Get.back();
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Incorrect password',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('Login'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class AdminPage extends StatelessWidget {
|
||||
const AdminPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
GameController c = Get.find();
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('Admin Page'),
|
||||
Text('Marker Sequence: ${c.markerSequence}'),
|
||||
Text('Calendar QR Data: ${c.calendarQrData}'),
|
||||
Text('Final Code: ${c.finalCode}'),
|
||||
Text('Admin Password: ${c.adminPass}'),
|
||||
const Text("Go to Challenge directly: "),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Get.offAll(const MarkerScreen());
|
||||
},
|
||||
child: const Text('Marker'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Get.offAll(const QrScreen());
|
||||
},
|
||||
child: const Text('Story'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Get.offAll(const PatternScreen());
|
||||
// Add functionality for Pattern button
|
||||
},
|
||||
child: const Text('Pattern'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
c.qrPageController.stop();
|
||||
},
|
||||
child: const Text('stop qr scanner qr page'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
117
lib/screens/marker.dart
Normal file
117
lib/screens/marker.dart
Normal file
@ -0,0 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/controller/gamecontroller.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/scanner_error_widget.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/screens/admin.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/screens/qr.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/util.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
class MarkerScreen extends StatelessWidget {
|
||||
const MarkerScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
GameController c = Get.find();
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Lotti\'s Escape Room - Rätsel 1'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.smart_toy_outlined),
|
||||
onPressed: () {
|
||||
showAdminLoginPopup();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Center(
|
||||
child: Text('Zum entsperren, bitte Code eingeben.'),
|
||||
),
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Code',
|
||||
),
|
||||
keyboardType: TextInputType.text,
|
||||
controller: c.markerSequenceController,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (c.checkMarkerString()) {
|
||||
Get.offAll(const QrScreen());
|
||||
} else {
|
||||
Get.snackbar("Falscher Code",
|
||||
"Der Code '${c.markerSequenceController.text}' ist falsch.");
|
||||
}
|
||||
},
|
||||
child: const Text('Code eingeben'),
|
||||
),
|
||||
Container(
|
||||
height: 400,
|
||||
child: MobileScanner(
|
||||
fit: BoxFit.contain,
|
||||
controller: c.markerPageScannerController,
|
||||
errorBuilder: (context, error, child) {
|
||||
return ScannerErrorWidget(error: error);
|
||||
},
|
||||
overlayBuilder: (context, constraints) {
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
height: 60,
|
||||
width: 200,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Colors.white,
|
||||
width: 3,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
));
|
||||
},
|
||||
onDetect: (barcodes) {
|
||||
if (barcodes.barcodes.isNotEmpty) {
|
||||
for (var barcode in barcodes.barcodes) {
|
||||
if (barcode.rawValue == null) {
|
||||
continue;
|
||||
}
|
||||
playBeepSound();
|
||||
if (c.markerNumbersToDeclaration.keys
|
||||
.contains(barcode.rawValue)) {
|
||||
c.markerSequenceController.text =
|
||||
"${c.markerSequenceController.text}${c.markerNumbersToDeclaration[barcode.rawValue]}";
|
||||
} else {
|
||||
Get.snackbar("Unbekannter Code",
|
||||
"Der Code '${barcode.rawValue}' konnte nicht erkannt werden.\nProbiere es doch nochmal :)");
|
||||
}
|
||||
c.markerPageScannerController.stop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// start scanner
|
||||
if (c.markerPageScannerController.value.isRunning) {
|
||||
c.markerPageScannerController.stop();
|
||||
} else {
|
||||
c.markerPageScannerController.start();
|
||||
}
|
||||
},
|
||||
child: const Text('Barcode\nscannen'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
28
lib/screens/pattern.dart
Normal file
28
lib/screens/pattern.dart
Normal file
@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/controller/gamecontroller.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/scanned_barcode_label.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/scanner_error_widget.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/screens/admin.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/util.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
class PatternScreen extends StatelessWidget {
|
||||
const PatternScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
GameController c = Get.find();
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: Scaffold(
|
||||
body: Image.asset(
|
||||
'assets/images/pattern.jpg', // Replace with your image path
|
||||
fit: BoxFit.contain,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
106
lib/screens/qr.dart
Normal file
106
lib/screens/qr.dart
Normal file
@ -0,0 +1,106 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/controller/gamecontroller.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/scanned_barcode_label.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/scanner_error_widget.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/screens/admin.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/screens/pattern.dart';
|
||||
import 'package:lottis_birthday_escaperoom_app/util.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
|
||||
class QrScreen extends StatelessWidget {
|
||||
const QrScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
GameController c = Get.find();
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Lotti\'s Escape Room - Rätsel 2'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.smart_toy_outlined),
|
||||
onPressed: () {
|
||||
showAdminLoginPopup();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(c.storyTitle, style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
height: 600,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
c.story,
|
||||
style: const TextStyle(fontSize: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Center(
|
||||
child: Text('Zum entsperren, bitte QR Code scannen.'),
|
||||
),
|
||||
Container(
|
||||
height: 300,
|
||||
child: MobileScanner(
|
||||
fit: BoxFit.contain,
|
||||
controller: c.qrPageController,
|
||||
errorBuilder: (context, error, child) {
|
||||
return ScannerErrorWidget(error: error);
|
||||
},
|
||||
overlayBuilder: (context, constraints) {
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
height: 200,
|
||||
width: 200,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Colors.white,
|
||||
width: 3,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
));
|
||||
},
|
||||
onDetect: (barcodes) {
|
||||
if (barcodes.barcodes.isNotEmpty) {
|
||||
for (var barcode in barcodes.barcodes) {
|
||||
if (barcode.rawValue == null) {
|
||||
continue;
|
||||
}
|
||||
playBeepSound();
|
||||
if (barcode.rawValue == c.calendarQrData) {
|
||||
c.qrPageController.stop();
|
||||
Get.offAll(PatternScreen());
|
||||
} else {
|
||||
Get.snackbar("Falscher Code",
|
||||
"Der Code '${barcode.rawValue}' ist falsch.");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
24
lib/util.dart
Normal file
24
lib/util.dart
Normal file
@ -0,0 +1,24 @@
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
|
||||
class AudioPlayerSingleton {
|
||||
static AudioPlayerSingleton? _instance;
|
||||
late AudioPlayer _audioPlayer;
|
||||
|
||||
AudioPlayerSingleton._() {
|
||||
_audioPlayer = AudioPlayer();
|
||||
}
|
||||
|
||||
static AudioPlayerSingleton get instance {
|
||||
_instance ??= AudioPlayerSingleton._();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
Future<void> playBeepSound() async {
|
||||
await _audioPlayer.play(AssetSource('sounds/beep.mp3'));
|
||||
}
|
||||
}
|
||||
|
||||
// Usage:
|
||||
Future<void> playBeepSound() async {
|
||||
await AudioPlayerSingleton.instance.playBeepSound();
|
||||
}
|
Reference in New Issue
Block a user