Catch-all commit for outstanding pre-existing local changes. Mixes several themes that would normally be split: - Rename: UxPlugin → XPlugin across iOS, macOS, Android registrants. - New top-level packages under lib/src/: anim/ (animated values, panes, sheets, dock, measured), core/ (Emitter, ReactiveBuilder scaffolding, presenter/widget/value/dispose primitives), navi/ (Screen/ScreenStack/Router/hero/transitions), reactive/. - Edits across existing plugins (clipboard, crash, file, gallery, keyboard, scanner, sensor, url) to align with the new core. - Test updates and CHANGELOG/README touches accompanying the above.
117 lines
3.7 KiB
Dart
117 lines
3.7 KiB
Dart
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 XScannerPermission {
|
|
XScannerPermission._();
|
|
|
|
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 XScanner extends StatefulWidget {
|
|
const XScanner({
|
|
super.key,
|
|
required this.onCode,
|
|
this.formats = const [BarcodeFormat.qr],
|
|
});
|
|
|
|
final ValueChanged<String> onCode;
|
|
final List<BarcodeFormat> formats;
|
|
|
|
@override
|
|
State<XScanner> createState() => _XScannerState();
|
|
}
|
|
|
|
class _XScannerState extends State<XScanner> {
|
|
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();
|
|
}
|
|
}
|