feat: complete app

This commit is contained in:
2024-08-17 05:19:32 +02:00
commit 042ef8b097
141 changed files with 6510 additions and 0 deletions

View 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;
}
}

View 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(' ', '');
}

View 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
View 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());
})
],
),
),
),
);
}
}

View 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;
}
}

View 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),
);
},
);
}
}

View 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,
);
}
},
);
}
}

View 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
View 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
View 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
View 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
View 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
View 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();
}