mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2025-12-22 16:34:23 +01:00
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:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user