This commit is contained in:
agra
2026-05-07 09:22:01 +03:00
parent 6d8efafaa0
commit 26cdf63afc
11 changed files with 547 additions and 4 deletions

116
lib/src/scanner.dart Normal file
View File

@@ -0,0 +1,116 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
/// Barcode formats the platform decoder will look for. Today only QR is
/// supported; the enum exists for forward-compatibility with the native
/// channel arguments.
enum BarcodeFormat { qr }
/// Static helpers exposed by the platform-side scanner plugin.
class UxScannerPermission {
UxScannerPermission._();
static const _channel = MethodChannel('ux/scanner');
/// Requests camera permission. Returns `true` once the OS has reported
/// authorized; `false` if the user denied or the platform is
/// unsupported. Safe to call multiple times — already-granted returns
/// `true` immediately.
static Future<bool> requestCamera() async {
if (defaultTargetPlatform != TargetPlatform.iOS &&
defaultTargetPlatform != TargetPlatform.android) {
return false;
}
final granted = await _channel.invokeMethod<bool>('requestPermission');
return granted ?? false;
}
}
/// Camera-preview widget that emits decoded barcode payloads.
///
/// Backed by AVFoundation on iOS (built-in QR support, no extra dep) and
/// CameraX + ZXing on Android. The widget mounts a platform-view —
/// `UiKitView` on iOS, `AndroidView` on Android — and listens to a
/// per-process event channel for decoded strings.
///
/// Camera permission must be granted by the host app before mounting.
/// On platforms other than iOS / Android the widget renders an empty
/// box.
class UxScanner extends StatefulWidget {
const UxScanner({
super.key,
required this.onCode,
this.formats = const [BarcodeFormat.qr],
});
final ValueChanged<String> onCode;
final List<BarcodeFormat> formats;
@override
State<UxScanner> createState() => _UxScannerState();
}
class _UxScannerState extends State<UxScanner> {
static const _events = EventChannel('ux/scanner/events');
StreamSubscription<dynamic>? _sub;
@override
void initState() {
super.initState();
_sub = _events.receiveBroadcastStream().listen((event) {
final code = event as String?;
if (code == null || !mounted) return;
widget.onCode(code);
});
}
@override
void dispose() {
_sub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final creationParams = <String, Object?>{
'formats': widget.formats.map((e) => e.name).toList(),
};
if (defaultTargetPlatform == TargetPlatform.iOS) {
return UiKitView(
viewType: 'ux/scanner',
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
if (defaultTargetPlatform == TargetPlatform.android) {
return PlatformViewLink(
viewType: 'ux/scanner',
surfaceFactory: (context, controller) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
onCreatePlatformView: (params) {
return PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: 'ux/scanner',
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
onFocus: () => params.onFocusChanged(true),
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
},
);
}
return const SizedBox.shrink();
}
}