mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-02-27 21:40:17 +00:00
"live" webcam image in qr code dialog
still a little janky... add debug prints thru the process to see what I mean but it works, so...
This commit is contained in:
parent
04ce0f89fc
commit
8bf8a90404
1 changed files with 196 additions and 127 deletions
|
@ -171,87 +171,27 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// WIP, copied from mobile view. Need to redo.
|
|
||||||
Future<void> _scanQr() async {
|
Future<void> _scanQr() async {
|
||||||
try {
|
try {
|
||||||
// ref
|
|
||||||
// .read(
|
|
||||||
// shouldShowLockscreenOnResumeStateProvider
|
|
||||||
// .state)
|
|
||||||
// .state = false;
|
|
||||||
if (FocusScope.of(context).hasFocus) {
|
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
// final qrResult = await scanner.scan();
|
|
||||||
await _captureImage();
|
|
||||||
// print("Image Captured: $_base64Image");
|
|
||||||
|
|
||||||
// Parse the base64 _base64Image to get the QR code data.
|
|
||||||
//
|
|
||||||
// Take the String _base64Image (encoded in base 64) and decode it to an image.
|
|
||||||
var image = img.decodeImage(base64Decode(_base64Image));
|
|
||||||
if (image == null) {
|
|
||||||
throw Exception("Failed to decode image");
|
|
||||||
}
|
|
||||||
LuminanceSource source = RGBLuminanceSource(
|
|
||||||
image.width,
|
|
||||||
image.height,
|
|
||||||
image
|
|
||||||
.convert(numChannels: 4)
|
|
||||||
.getBytes(order: img.ChannelOrder.abgr)
|
|
||||||
.buffer
|
|
||||||
.asInt32List());
|
|
||||||
var bitmap = BinaryBitmap(GlobalHistogramBinarizer(source));
|
|
||||||
|
|
||||||
// Convert to Image.memory for display in Flutter
|
|
||||||
Uint8List displayBytes = Uint8List.fromList(img.encodePng(image));
|
|
||||||
// Show dialog with bitmap displayed.
|
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return QrCodeScannerDialog(
|
||||||
title: const Text("QR Code Image"),
|
walletId: widget.walletId,
|
||||||
content: Image.memory(
|
onQrCodeDetected: (qrCodeData) {
|
||||||
Uint8List.fromList(Uint8List.fromList(img.encodePng(image)))),
|
try {
|
||||||
actions: [
|
var results = AddressUtils.parseUri(qrCodeData);
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: Text("Close"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
var reader = QRCodeReader();
|
|
||||||
var qrDecode = reader.decode(bitmap);
|
|
||||||
print("QR Code Data: ${qrDecode.text}");
|
|
||||||
|
|
||||||
final results = AddressUtils.parseUri(qrDecode.text);
|
|
||||||
Logging.instance.log("qrResult parsed: $results", level: LogLevel.Info);
|
|
||||||
|
|
||||||
if (results.isNotEmpty && results["scheme"] == coin.uriScheme) {
|
if (results.isNotEmpty && results["scheme"] == coin.uriScheme) {
|
||||||
// auto fill address
|
|
||||||
_address = (results["address"] ?? "").trim();
|
_address = (results["address"] ?? "").trim();
|
||||||
sendToController.text = _address!;
|
sendToController.text = _address!;
|
||||||
|
|
||||||
// // autofill notes field
|
|
||||||
// if (results["message"] != null) {
|
|
||||||
// noteController.text = results["message"]!;
|
|
||||||
// } else if (results["label"] != null) {
|
|
||||||
// noteController.text = results["label"]!;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// autofill amount field
|
|
||||||
if (results["amount"] != null) {
|
if (results["amount"] != null) {
|
||||||
final Amount amount = Decimal.parse(results["amount"]!).toAmount(
|
final Amount amount =
|
||||||
|
Decimal.parse(results["amount"]!).toAmount(
|
||||||
fractionDigits: coin.fractionDigits,
|
fractionDigits: coin.fractionDigits,
|
||||||
);
|
);
|
||||||
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
|
cryptoAmountController.text =
|
||||||
|
ref.read(pAmountFormatter(coin)).format(
|
||||||
amount,
|
amount,
|
||||||
withUnitName: false,
|
withUnitName: false,
|
||||||
);
|
);
|
||||||
|
@ -262,47 +202,18 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
setState(() {
|
setState(() {
|
||||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||||
});
|
});
|
||||||
|
|
||||||
// now check for non standard encoded basic address
|
|
||||||
} else {
|
|
||||||
if (results.keys.contains("address") &&
|
|
||||||
results["address"]!.isNotEmpty &&
|
|
||||||
ref
|
|
||||||
.read(pWallets)
|
|
||||||
.getWallet(walletId)
|
|
||||||
.cryptoCurrency
|
|
||||||
.validateAddress(results["address"]!)) {
|
|
||||||
_address = results["address"]!.trim();
|
|
||||||
sendToController.text = _address ?? "";
|
|
||||||
|
|
||||||
_setValidAddressProviders(_address);
|
|
||||||
setState(() {
|
|
||||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log("Error processing QR code data: $e\n$s",
|
||||||
|
level: LogLevel.Error);
|
||||||
}
|
}
|
||||||
} on PlatformException catch (e, s) {
|
},
|
||||||
// ref
|
);
|
||||||
// .read(
|
},
|
||||||
// shouldShowLockscreenOnResumeStateProvider
|
|
||||||
// .state)
|
|
||||||
// .state = true;
|
|
||||||
// here we ignore the exception caused by not giving permission
|
|
||||||
// to use the camera to scan a qr code
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
// ref
|
Logging.instance.log("Error opening QR code scanner dialog: $e\n$s",
|
||||||
// .read(
|
level: LogLevel.Error);
|
||||||
// shouldShowLockscreenOnResumeStateProvider
|
|
||||||
// .state)
|
|
||||||
// .state = true;
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to scan qr code in SendView: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -861,6 +772,11 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
|
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
|
||||||
level: LogLevel.Warning,
|
level: LogLevel.Warning,
|
||||||
);
|
);
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Failed to scan qr code in SendView: $e\n$s",
|
||||||
|
level: LogLevel.Warning,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1084,10 +1000,6 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_initializeCamera();
|
|
||||||
// Maybe we could/should do this when the QR button is pressed; camera plug-
|
|
||||||
// in docs say to init cam in initState.
|
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2066,3 +1978,160 @@ String formatAddress(String epicAddress) {
|
||||||
}
|
}
|
||||||
return epicAddress;
|
return epicAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class QrCodeScannerDialog extends StatefulWidget {
|
||||||
|
final String walletId;
|
||||||
|
final Function(String) onQrCodeDetected;
|
||||||
|
|
||||||
|
QrCodeScannerDialog({
|
||||||
|
required this.walletId,
|
||||||
|
required this.onQrCodeDetected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_QrCodeScannerDialogState createState() => _QrCodeScannerDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
|
final _cameraLinuxPlugin = CameraLinux();
|
||||||
|
late String _base64Image;
|
||||||
|
bool _isCameraOpen = false;
|
||||||
|
Image? _image;
|
||||||
|
String? _qrCodeData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initializeCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_stopCamera();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initializeCamera() async {
|
||||||
|
try {
|
||||||
|
await _cameraLinuxPlugin.initializeCamera();
|
||||||
|
Logging.instance
|
||||||
|
.log("Camera initialized successfully", level: LogLevel.Info);
|
||||||
|
_captureAndScanImage();
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance
|
||||||
|
.log("Failed to initialize camera: $e\n$s", level: LogLevel.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _captureAndScanImage() async {
|
||||||
|
while (_qrCodeData == null && mounted) {
|
||||||
|
try {
|
||||||
|
_base64Image = await _cameraLinuxPlugin.captureImage();
|
||||||
|
Logging.instance
|
||||||
|
.log("Image captured successfully", level: LogLevel.Info);
|
||||||
|
_isCameraOpen = true;
|
||||||
|
|
||||||
|
var image = img.decodeImage(base64Decode(_base64Image));
|
||||||
|
if (image == null) {
|
||||||
|
throw Exception("Failed to decode image");
|
||||||
|
}
|
||||||
|
|
||||||
|
LuminanceSource source = RGBLuminanceSource(
|
||||||
|
image.width,
|
||||||
|
image.height,
|
||||||
|
image
|
||||||
|
.convert(numChannels: 4)
|
||||||
|
.getBytes(order: img.ChannelOrder.abgr)
|
||||||
|
.buffer
|
||||||
|
.asInt32List(),
|
||||||
|
);
|
||||||
|
var bitmap = BinaryBitmap(GlobalHistogramBinarizer(source));
|
||||||
|
_image = Image.memory(Uint8List.fromList(img.encodePng(image)));
|
||||||
|
var reader = QRCodeReader();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var qrDecode = reader.decode(bitmap);
|
||||||
|
_qrCodeData = qrDecode.text;
|
||||||
|
Logging.instance.log("QR code decoded successfully: $_qrCodeData",
|
||||||
|
level: LogLevel.Info);
|
||||||
|
widget.onQrCodeDetected(_qrCodeData!);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
break; // Exit the loop once QR code is detected
|
||||||
|
} catch (e) {
|
||||||
|
Logging.instance
|
||||||
|
.log("Failed to decode QR code: $e", level: LogLevel.Error);
|
||||||
|
setState(() {
|
||||||
|
// Update the dialog with the new image.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await Future.delayed(
|
||||||
|
const Duration(milliseconds: 1000)); // Add delay here.
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log("Failed to capture and scan image: $e\n$s",
|
||||||
|
level: LogLevel.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopCamera() {
|
||||||
|
try {
|
||||||
|
_cameraLinuxPlugin.stopCamera();
|
||||||
|
Logging.instance.log("Camera stopped successfully", level: LogLevel.Info);
|
||||||
|
setState(() {
|
||||||
|
_isCameraOpen = false;
|
||||||
|
});
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance
|
||||||
|
.log("Failed to stop camera: $e\n$s", level: LogLevel.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DesktopDialog(
|
||||||
|
maxWidth: 696,
|
||||||
|
maxHeight: 600,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 32),
|
||||||
|
child: Text(
|
||||||
|
"Scan QR code",
|
||||||
|
style: STextStyles.desktopH3(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const DesktopDialogCloseButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _isCameraOpen
|
||||||
|
? _image != null
|
||||||
|
? _image!
|
||||||
|
: const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
)
|
||||||
|
: const Center(
|
||||||
|
child: Text("Camera is not open"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
|
child: PrimaryButton(
|
||||||
|
buttonHeight: ButtonHeight.l,
|
||||||
|
label: "Close",
|
||||||
|
onPressed: () {
|
||||||
|
_stopCamera();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue