mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-02-25 20:40:19 +00:00
use camera_windows fork for windows webcam support
I wish I could use the CameraPlatformInterface in a platform-agnostic way windows desktop webcam scan fixes and log cleanup
This commit is contained in:
parent
63ee105aea
commit
6fa56bfe6d
3 changed files with 79 additions and 64 deletions
lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets
pubspec.lockscripts/app_config/templates
|
@ -13,6 +13,8 @@ import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:camera_linux/camera_linux.dart';
|
import 'package:camera_linux/camera_linux.dart';
|
||||||
|
import 'package:camera_windows/camera_windows.dart';
|
||||||
|
import 'package:camera_platform_interface/camera_platform_interface.dart';
|
||||||
import 'package:cw_core/monero_transaction_priority.dart';
|
import 'package:cw_core/monero_transaction_priority.dart';
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
|
@ -22,6 +24,7 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:image/image.dart' as img;
|
import 'package:image/image.dart' as img;
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:zxing2/qrcode.dart';
|
import 'package:zxing2/qrcode.dart';
|
||||||
|
|
||||||
import '../../../../models/isar/models/contact_entry.dart';
|
import '../../../../models/isar/models/contact_entry.dart';
|
||||||
|
@ -146,32 +149,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
||||||
"Calculating..",
|
"Calculating..",
|
||||||
"Calculating...",
|
"Calculating...",
|
||||||
];
|
];
|
||||||
final _cameraLinuxPlugin = CameraLinux();
|
final CameraLinux? _cameraLinuxPlugin = Platform.isLinux ? CameraLinux() : null;
|
||||||
bool _isCameraOpen = false;
|
|
||||||
late String _base64Image;
|
|
||||||
|
|
||||||
// Below this point is WIP.
|
|
||||||
// Open Default Camera
|
|
||||||
Future<void> _initializeCamera() async {
|
|
||||||
await _cameraLinuxPlugin.initializeCamera();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capture The Image
|
|
||||||
Future<void> _captureImage() async {
|
|
||||||
_base64Image = await _cameraLinuxPlugin.captureImage();
|
|
||||||
setState(() {
|
|
||||||
_isCameraOpen = true;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close The Camera
|
|
||||||
void _stopCamera() {
|
|
||||||
_cameraLinuxPlugin.stopCamera();
|
|
||||||
setState(() {
|
|
||||||
_isCameraOpen = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> scanWebcam() async {
|
Future<void> scanWebcam() async {
|
||||||
try {
|
try {
|
||||||
|
@ -2011,10 +1989,12 @@ class QrCodeScannerDialog extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
final _cameraLinuxPlugin = CameraLinux();
|
final CameraLinux? _cameraLinuxPlugin = Platform.isLinux ? CameraLinux() : null;
|
||||||
|
final CameraWindows? _cameraWindowsPlugin = Platform.isWindows ? CameraWindows() : null;
|
||||||
bool _isCameraOpen = false;
|
bool _isCameraOpen = false;
|
||||||
Image? _image;
|
Image? _image;
|
||||||
bool _isScanning = false;
|
bool _isScanning = false;
|
||||||
|
int _cameraId = -1;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -2030,19 +2010,38 @@ class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
|
|
||||||
Future<void> _initializeCamera() async {
|
Future<void> _initializeCamera() async {
|
||||||
try {
|
try {
|
||||||
await _cameraLinuxPlugin.initializeCamera();
|
if (Platform.isLinux && _cameraLinuxPlugin != null) {
|
||||||
Logging.instance
|
await _cameraLinuxPlugin!.initializeCamera();
|
||||||
.log("Camera initialized successfully", level: LogLevel.Info);
|
Logging.instance.log("Linux Camera initialized", level: LogLevel.Info);
|
||||||
|
} else if (Platform.isWindows && _cameraWindowsPlugin != null) {
|
||||||
|
final List<CameraDescription> cameras = await _cameraWindowsPlugin!.availableCameras();
|
||||||
|
if (cameras.isEmpty) {
|
||||||
|
throw CameraException('No cameras available', 'No cameras found.');
|
||||||
|
}
|
||||||
|
final CameraDescription camera = cameras[0]; // Could be user-selected.
|
||||||
|
_cameraId = await _cameraWindowsPlugin!.createCameraWithSettings(
|
||||||
|
camera,
|
||||||
|
const MediaSettings(
|
||||||
|
resolutionPreset: ResolutionPreset.low,
|
||||||
|
fps: 4,
|
||||||
|
videoBitrate: 200000,
|
||||||
|
enableAudio: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await _cameraWindowsPlugin!.initializeCamera(_cameraId);
|
||||||
|
// await _cameraWindowsPlugin!.onCameraInitialized(_cameraId).first;
|
||||||
|
// TODO [prio=low]: Make this work. ^^^
|
||||||
|
Logging.instance.log("Windows Camera initialized with ID: $_cameraId", level: LogLevel.Info);
|
||||||
|
}
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isCameraOpen = true;
|
_isCameraOpen = true;
|
||||||
_isScanning = true;
|
_isScanning = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_captureAndScanImage();
|
unawaited(_captureAndScanImage()); // Could be awaited.
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance
|
Logging.instance.log("Failed to initialize camera: $e\n$s", level: LogLevel.Error);
|
||||||
.log("Failed to initialize camera: $e\n$s", level: LogLevel.Error);
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
widget.onSnackbar("Failed to initialize camera. Please try again.");
|
widget.onSnackbar("Failed to initialize camera. Please try again.");
|
||||||
}
|
}
|
||||||
|
@ -2051,11 +2050,19 @@ class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
|
|
||||||
Future<void> _stopCamera() async {
|
Future<void> _stopCamera() async {
|
||||||
try {
|
try {
|
||||||
_cameraLinuxPlugin.stopCamera();
|
if (Platform.isLinux && _cameraLinuxPlugin != null) {
|
||||||
Logging.instance.log("Camera stopped successfully", level: LogLevel.Info);
|
_cameraLinuxPlugin!.stopCamera();
|
||||||
|
Logging.instance.log("Linux Camera stopped", level: LogLevel.Info);
|
||||||
|
} else if (Platform.isWindows && _cameraWindowsPlugin != null) {
|
||||||
|
if (_cameraId >= 0) {
|
||||||
|
await _cameraWindowsPlugin!.dispose(_cameraId);
|
||||||
|
Logging.instance.log("Windows Camera stopped with ID: $_cameraId", level: LogLevel.Info);
|
||||||
|
} else {
|
||||||
|
Logging.instance.log("Windows Camera ID is null. Cannot dispose.", level: LogLevel.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance
|
Logging.instance.log("Failed to stop camera: $e\n$s", level: LogLevel.Error);
|
||||||
.log("Failed to stop camera: $e\n$s", level: LogLevel.Error);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -2069,7 +2076,19 @@ class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
Future<void> _captureAndScanImage() async {
|
Future<void> _captureAndScanImage() async {
|
||||||
while (_isCameraOpen && _isScanning) {
|
while (_isCameraOpen && _isScanning) {
|
||||||
try {
|
try {
|
||||||
final base64Image = await _cameraLinuxPlugin.captureImage();
|
String? base64Image;
|
||||||
|
if (Platform.isLinux && _cameraLinuxPlugin != null) {
|
||||||
|
base64Image = await _cameraLinuxPlugin!.captureImage();
|
||||||
|
} else if (Platform.isWindows) {
|
||||||
|
final XFile xfile = await _cameraWindowsPlugin!.takePicture(_cameraId);
|
||||||
|
final bytes = await xfile.readAsBytes();
|
||||||
|
base64Image = base64Encode(bytes);
|
||||||
|
}
|
||||||
|
if (base64Image == null || base64Image.isEmpty) {
|
||||||
|
Logging.instance.log("Failed to capture image", level: LogLevel.Info);
|
||||||
|
await Future.delayed(const Duration(milliseconds: 250));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
final img.Image? image = img.decodeImage(base64Decode(base64Image));
|
final img.Image? image = img.decodeImage(base64Decode(base64Image));
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
Logging.instance.log("Failed to decode image", level: LogLevel.Info);
|
Logging.instance.log("Failed to decode image", level: LogLevel.Info);
|
||||||
|
@ -2080,7 +2099,7 @@ class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_image = Image.memory(
|
_image = Image.memory(
|
||||||
base64Decode(base64Image),
|
base64Decode(base64Image!),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -2094,8 +2113,8 @@ class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
Logging.instance
|
// Logging.instance.log("No QR code found in the image", level: LogLevel.Info);
|
||||||
.log("No QR code found in the image", level: LogLevel.Info);
|
// Spammy.
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
widget.onSnackbar("No QR code found in the image.");
|
widget.onSnackbar("No QR code found in the image.");
|
||||||
}
|
}
|
||||||
|
@ -2103,11 +2122,10 @@ class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
|
|
||||||
await Future.delayed(const Duration(milliseconds: 250));
|
await Future.delayed(const Duration(milliseconds: 250));
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log("Failed to capture and scan image: $e\n$s",
|
// Logging.instance.log("Failed to capture and scan image: $e\n$s", level: LogLevel.Error);
|
||||||
level: LogLevel.Error);
|
// Spammy.
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
widget.onSnackbar(
|
widget.onSnackbar("Error capturing or scanning the image. Please try again.");
|
||||||
"Error capturing or scanning the image. Please try again.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2124,8 +2142,7 @@ class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
.buffer
|
.buffer
|
||||||
.asInt32List(),
|
.asInt32List(),
|
||||||
);
|
);
|
||||||
final BinaryBitmap bitmap =
|
final BinaryBitmap bitmap = BinaryBitmap(GlobalHistogramBinarizer(source));
|
||||||
BinaryBitmap(GlobalHistogramBinarizer(source));
|
|
||||||
|
|
||||||
final QRCodeReader reader = QRCodeReader();
|
final QRCodeReader reader = QRCodeReader();
|
||||||
final qrDecode = reader.decode(bitmap);
|
final qrDecode = reader.decode(bitmap);
|
||||||
|
@ -2134,8 +2151,8 @@ class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
}
|
}
|
||||||
return qrDecode.text;
|
return qrDecode.text;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance
|
// Logging.instance.log("Failed to decode QR code: $e\n$s", level: LogLevel.Error);
|
||||||
.log("Failed to decode QR code: $e\n$s", level: LogLevel.Error);
|
// Spammy.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2164,13 +2181,13 @@ class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _isCameraOpen
|
child: _isCameraOpen
|
||||||
? _image != null
|
? _image != null
|
||||||
? _image!
|
? _image!
|
||||||
: const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
)
|
|
||||||
: const Center(
|
: const Center(
|
||||||
child: Text("Camera is not open"),
|
child: CircularProgressIndicator(),
|
||||||
),
|
)
|
||||||
|
: const Center(
|
||||||
|
child: Text("Camera is not open"),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
|
@ -2196,10 +2213,9 @@ class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
final filePath = result.files.single.path!;
|
final filePath = result.files.single.path!;
|
||||||
try {
|
try {
|
||||||
final img.Image? image =
|
final img.Image? image =
|
||||||
img.decodeImage(File(filePath).readAsBytesSync());
|
img.decodeImage(File(filePath).readAsBytesSync());
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
widget.onSnackbar(
|
widget.onSnackbar("Failed to decode image. Please select a valid image file.");
|
||||||
"Failed to decode image. Please select a valid image file.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2211,10 +2227,8 @@ class _QrCodeScannerDialogState extends State<QrCodeScannerDialog> {
|
||||||
widget.onSnackbar("No QR code found in the image.");
|
widget.onSnackbar("No QR code found in the image.");
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log("Failed to decode image: $e\n$s",
|
Logging.instance.log("Failed to decode image: $e\n$s", level: LogLevel.Error);
|
||||||
level: LogLevel.Error);
|
widget.onSnackbar("Error processing the image. Please try again.");
|
||||||
widget.onSnackbar(
|
|
||||||
"Error processing the image. Please try again.");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -255,7 +255,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.8"
|
version: "0.0.8"
|
||||||
camera_platform_interface:
|
camera_platform_interface:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: camera_platform_interface
|
name: camera_platform_interface
|
||||||
sha256: b3ede1f171532e0d83111fe0980b46d17f1aa9788a07a2fbed07366bbdbb9061
|
sha256: b3ede1f171532e0d83111fe0980b46d17f1aa9788a07a2fbed07366bbdbb9061
|
||||||
|
|
|
@ -193,9 +193,10 @@ dependencies:
|
||||||
camera_linux: ^0.0.8
|
camera_linux: ^0.0.8
|
||||||
zxing2: ^0.2.3
|
zxing2: ^0.2.3
|
||||||
camera_windows:
|
camera_windows:
|
||||||
git:
|
git: # TODO [prio=low]: Revert to official after https://github.com/flutter/packages/pull/7067.
|
||||||
url: https://github.com/cypherstack/packages.git
|
url: https://github.com/cypherstack/packages.git
|
||||||
path: packages/camera/camera_windows
|
path: packages/camera/camera_windows
|
||||||
|
camera_platform_interface: ^2.8.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue