mobile_nebula/lib/screens/siteConfig/ScanQRScreen.dart

230 lines
6.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class ScanQRScreen extends StatefulWidget {
@override
State<ScanQRScreen> createState() => _ScanQRScreenState();
}
class _ScanQRScreenState extends State<ScanQRScreen> {
final MobileScannerController cameraController = MobileScannerController(
autoStart: true,
formats: [BarcodeFormat.qrCode],
);
@override
Widget build(BuildContext context) {
final scanWindow = Rect.fromCenter(
center: MediaQuery.sizeOf(context).center(Offset.zero),
width: 250,
height: 250,
);
return Scaffold(
appBar: AppBar(title: const Text('Scan QR')),
backgroundColor: Colors.black,
body: Stack(fit: StackFit.expand, children: [
Center(
child: MobileScanner(
fit: BoxFit.contain,
controller: cameraController,
scanWindow: scanWindow,
onDetect: (BarcodeCapture barcodes) {
var barcode = barcodes.barcodes.firstOrNull;
if (barcode != null && mounted) {
cameraController.stop().then((_) {
Navigator.pop(context, barcode.rawValue);
});
}
}),
),
ValueListenableBuilder(
valueListenable: cameraController,
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: cameraController),
SwitchCameraButton(controller: cameraController),
],
),
),
),
]));
}
}
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) {
// 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;
}
}
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,
color: Colors.white,
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, controllerState, child) {
if (!controllerState.isInitialized || !controllerState.isRunning) {
return const SizedBox.shrink();
}
switch (controllerState.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 SizedBox.square(
dimension: 48.0,
child: Icon(
Icons.no_flash,
size: 32.0,
color: Colors.grey,
),
);
}
},
);
}
}