Update example app on Flutter plugin (#220)

* Update example app on Flutter plugin

* Expose `empty_wallet_cache` through Dart bindings (#224)
This commit is contained in:
Erdem Yerebasmaz
2024-05-24 12:12:29 +03:00
committed by GitHub
parent c7b46314f4
commit 7b1b78a2d9
38 changed files with 1821 additions and 167 deletions

View File

@@ -0,0 +1,114 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_breez_liquid_example/routes/home/widgets/qr_scan/scan_overlay.dart';
import 'package:flutter_breez_liquid_example/routes/home/widgets/qr_scan/scanner_error_widget.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class BarcodeScanner extends StatefulWidget {
const BarcodeScanner({super.key});
@override
State<BarcodeScanner> createState() => _BarcodeScannerState();
}
class _BarcodeScannerState extends State<BarcodeScanner> with WidgetsBindingObserver {
bool popped = false;
final MobileScannerController controller = MobileScannerController(
autoStart: false,
torchEnabled: false,
useNewCameraSelector: true,
);
Barcode? _barcode;
StreamSubscription<Object?>? _subscription;
void _handleBarcode(BarcodeCapture barcodes) {
if (mounted) {
setState(() {
_barcode = barcodes.barcodes.firstOrNull;
});
if (popped) {
debugPrint("Skipping, already popped");
return;
}
popped = true;
final code = _barcode?.rawValue;
if (code == null) {
debugPrint("Failed to scan QR code.");
} else {
popped = true;
debugPrint("Popping read QR code: $code");
Navigator.of(context).pop(code);
}
}
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_subscription = controller.barcodes.listen(_handleBarcode);
unawaited(controller.start());
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (!controller.value.isInitialized) {
return;
}
switch (state) {
case AppLifecycleState.detached:
case AppLifecycleState.hidden:
case AppLifecycleState.paused:
return;
case AppLifecycleState.resumed:
_subscription = controller.barcodes.listen(_handleBarcode);
unawaited(controller.start());
case AppLifecycleState.inactive:
unawaited(_subscription?.cancel());
_subscription = null;
unawaited(controller.stop());
}
}
@override
Widget build(BuildContext context) {
var scanWindowDimension = MediaQuery.of(context).size.width - 72;
return Scaffold(
body: Stack(
children: [
MobileScanner(
scanWindow: Rect.fromCenter(
center: MediaQuery.sizeOf(context).center(Offset.zero),
width: scanWindowDimension,
height: scanWindowDimension,
),
controller: controller,
errorBuilder: (context, error, child) {
return ScannerErrorWidget(error: error);
},
overlayBuilder: (context, constraints) {
return const ScanOverlay();
},
fit: BoxFit.cover,
),
],
),
);
}
@override
Future<void> dispose() async {
WidgetsBinding.instance.removeObserver(this);
unawaited(_subscription?.cancel());
_subscription = null;
super.dispose();
await controller.dispose();
}
}

View File

@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
class ScanOverlay extends StatelessWidget {
const ScanOverlay({
super.key,
});
@override
Widget build(BuildContext context) {
var dimension = MediaQuery.of(context).size.width - 72;
return Center(
child: CustomPaint(
painter: BorderPainter(),
child: SizedBox(
width: dimension,
height: dimension,
),
),
);
}
}
class BorderPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
const width = 4.0;
const radius = 16.0;
const tRadius = 2 * radius;
final rect = Rect.fromLTWH(
width,
width,
size.width - 2 * width,
size.height - 2 * width,
);
final rrect = RRect.fromRectAndRadius(rect, const Radius.circular(radius));
const clippingRect0 = Rect.fromLTWH(
0,
0,
tRadius,
tRadius,
);
final clippingRect1 = Rect.fromLTWH(
size.width - tRadius,
0,
tRadius,
tRadius,
);
final clippingRect2 = Rect.fromLTWH(
0,
size.height - tRadius,
tRadius,
tRadius,
);
final clippingRect3 = Rect.fromLTWH(
size.width - tRadius,
size.height - tRadius,
tRadius,
tRadius,
);
final path = Path()
..addRect(clippingRect0)
..addRect(clippingRect1)
..addRect(clippingRect2)
..addRect(clippingRect3);
canvas.clipPath(path);
canvas.drawRRect(
rrect,
Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = width,
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

View File

@@ -0,0 +1,130 @@
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
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),
),
],
),
),
);
}
}