ostp/ostp-flutter/lib/ui/qr_scanner_screen.dart

213 lines
6.2 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class QRScannerScreen extends StatefulWidget {
const QRScannerScreen({super.key});
@override
State<QRScannerScreen> createState() => _QRScannerScreenState();
}
class _QRScannerScreenState extends State<QRScannerScreen> {
final MobileScannerController controller = MobileScannerController(
detectionSpeed: DetectionSpeed.normal,
facing: CameraFacing.back,
);
@override
void dispose() {
controller.dispose();
super.dispose();
}
DateTime? lastErrorTime;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Scan QR Code'),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Stack(
alignment: Alignment.center,
children: [
MobileScanner(
controller: controller,
onDetect: (capture) {
final List<Barcode> barcodes = capture.barcodes;
for (final barcode in barcodes) {
if (barcode.rawValue != null) {
if (barcode.rawValue!.startsWith('ostp://')) {
controller.stop();
Navigator.pop(context, barcode.rawValue);
return;
} else {
final now = DateTime.now();
if (lastErrorTime == null || now.difference(lastErrorTime!) > const Duration(seconds: 3)) {
lastErrorTime = now;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Invalid QR Code. Must be an OSTP connection link.'),
backgroundColor: Colors.redAccent,
duration: Duration(seconds: 2),
),
);
}
}
}
}
},
),
Container(
decoration: ShapeDecoration(
shape: QrScannerOverlayShape(
borderColor: Theme.of(context).colorScheme.primary,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: 300,
),
),
),
],
),
);
}
}
class QrScannerOverlayShape extends ShapeBorder {
final Color borderColor;
final double borderWidth;
final double borderRadius;
final double borderLength;
final double cutOutSize;
const QrScannerOverlayShape({
this.borderColor = Colors.red,
this.borderWidth = 3.0,
this.borderRadius = 0.0,
this.borderLength = 20.0,
this.cutOutSize = 250.0,
});
@override
EdgeInsetsGeometry get dimensions => const EdgeInsets.all(10);
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
return Path()
..fillType = PathFillType.evenOdd
..addPath(getOuterPath(rect), Offset.zero);
}
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
Path path = Path()..addRect(rect);
rect = Rect.fromCenter(
center: rect.center,
width: cutOutSize,
height: cutOutSize,
);
path.addRect(rect);
return path;
}
@override
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {
final borderPaint = Paint()
..color = borderColor
..style = PaintingStyle.stroke
..strokeWidth = borderWidth;
final backgroundPaint = Paint()
..color = Colors.black54
..style = PaintingStyle.fill;
final cutOutRect = Rect.fromCenter(
center: rect.center,
width: cutOutSize,
height: cutOutSize,
);
final backgroundPath = Path()
..addRect(rect)
..addRect(cutOutRect)
..fillType = PathFillType.evenOdd;
canvas.drawPath(backgroundPath, backgroundPaint);
final path = Path();
// Top left
path.moveTo(cutOutRect.left, cutOutRect.top + borderLength);
path.lineTo(cutOutRect.left, cutOutRect.top + borderRadius);
path.arcToPoint(
Offset(cutOutRect.left + borderRadius, cutOutRect.top),
radius: Radius.circular(borderRadius),
);
path.lineTo(cutOutRect.left + borderLength, cutOutRect.top);
// Top right
path.moveTo(cutOutRect.right - borderLength, cutOutRect.top);
path.lineTo(cutOutRect.right - borderRadius, cutOutRect.top);
path.arcToPoint(
Offset(cutOutRect.right, cutOutRect.top + borderRadius),
radius: Radius.circular(borderRadius),
);
path.lineTo(cutOutRect.right, cutOutRect.top + borderLength);
// Bottom left
path.moveTo(cutOutRect.left, cutOutRect.bottom - borderLength);
path.lineTo(cutOutRect.left, cutOutRect.bottom - borderRadius);
path.arcToPoint(
Offset(cutOutRect.left + borderRadius, cutOutRect.bottom),
radius: Radius.circular(borderRadius),
clockwise: false,
);
path.lineTo(cutOutRect.left + borderLength, cutOutRect.bottom);
// Bottom right
path.moveTo(cutOutRect.right - borderLength, cutOutRect.bottom);
path.lineTo(cutOutRect.right - borderRadius, cutOutRect.bottom);
path.arcToPoint(
Offset(cutOutRect.right, cutOutRect.bottom - borderRadius),
radius: Radius.circular(borderRadius),
clockwise: false,
);
path.lineTo(cutOutRect.right, cutOutRect.bottom - borderLength);
canvas.drawPath(path, borderPaint);
// Line in the middle
final linePaint = Paint()
..color = borderColor.withOpacity(0.8)
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
canvas.drawLine(
Offset(cutOutRect.left + 20, cutOutRect.center.dy),
Offset(cutOutRect.right - 20, cutOutRect.center.dy),
linePaint,
);
}
@override
ShapeBorder scale(double t) {
return QrScannerOverlayShape(
borderColor: borderColor,
borderWidth: borderWidth * t,
borderRadius: borderRadius * t,
borderLength: borderLength * t,
cutOutSize: cutOutSize * t,
);
}
}