mirror of
https://github.com/block/buzz.git
synced 2026-11-02 15:31:27 +00:00
Fade in the camera preview when startup completes
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
part of '../pairing_qr_scanner.dart';
|
||||
|
||||
class _QrScannerCamera extends StatelessWidget {
|
||||
class _QrScannerCamera extends HookWidget {
|
||||
const _QrScannerCamera({required this.controller, required this.onDetect});
|
||||
|
||||
final MobileScannerController controller;
|
||||
@@ -8,46 +8,79 @@ class _QrScannerCamera extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MobileScanner(
|
||||
controller: controller,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error) {
|
||||
final message = switch (error.errorCode) {
|
||||
MobileScannerErrorCode.permissionDenied =>
|
||||
'Camera permission is required to scan QR codes.\n\n'
|
||||
'Please grant camera access in your device settings.',
|
||||
_ =>
|
||||
'Could not start camera: '
|
||||
"${error.errorDetails?.message ?? 'unknown error'}",
|
||||
};
|
||||
return ColoredBox(
|
||||
color: Colors.black,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(Grid.sm),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
BuzzIcons.cameraOff,
|
||||
size: 48,
|
||||
color: Colors.white.withValues(alpha: 0.72),
|
||||
),
|
||||
const SizedBox(height: Grid.xs),
|
||||
Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
final cameraState = useState(controller.value);
|
||||
useEffect(() {
|
||||
var active = true;
|
||||
void publish() {
|
||||
if (active) cameraState.value = controller.value;
|
||||
}
|
||||
|
||||
void changed() {
|
||||
// The scanner can publish initialization state while its child builds.
|
||||
if (WidgetsBinding.instance.schedulerPhase ==
|
||||
SchedulerPhase.persistentCallbacks) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => publish());
|
||||
} else {
|
||||
publish();
|
||||
}
|
||||
}
|
||||
|
||||
controller.addListener(changed);
|
||||
return () {
|
||||
active = false;
|
||||
controller.removeListener(changed);
|
||||
};
|
||||
}, [controller]);
|
||||
final state = cameraState.value;
|
||||
final ready = state.isInitialized && state.isRunning;
|
||||
return AnimatedOpacity(
|
||||
key: const ValueKey('qr-camera-ready-fade'),
|
||||
opacity: ready || state.error != null ? 1 : 0,
|
||||
duration: MediaQuery.disableAnimationsOf(context) || state.error != null
|
||||
? Duration.zero
|
||||
: const Duration(milliseconds: 120),
|
||||
curve: Curves.easeOut,
|
||||
child: MobileScanner(
|
||||
controller: controller,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error) {
|
||||
final message = switch (error.errorCode) {
|
||||
MobileScannerErrorCode.permissionDenied =>
|
||||
'Camera permission is required to scan QR codes.\n\n'
|
||||
'Please grant camera access in your device settings.',
|
||||
_ =>
|
||||
'Could not start camera: '
|
||||
"${error.errorDetails?.message ?? 'unknown error'}",
|
||||
};
|
||||
return ColoredBox(
|
||||
color: Colors.black,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(Grid.sm),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
BuzzIcons.cameraOff,
|
||||
size: 48,
|
||||
color: Colors.white.withValues(alpha: 0.72),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: Grid.xs),
|
||||
Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.white.withValues(alpha: 0.72),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
onDetect: onDetect,
|
||||
);
|
||||
},
|
||||
onDetect: onDetect,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,8 @@ void main() {
|
||||
await tester.binding.setSurfaceSize(const Size(393, 852));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
final cameraStart = Completer<void>();
|
||||
fakeScannerPlatform.startGate = cameraStart.future;
|
||||
var scannerClosed = false;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
@@ -212,6 +214,21 @@ void main() {
|
||||
expect(openingRadius, inExclusiveRange(18, 40));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
final readyFade = find.byKey(const ValueKey('qr-camera-ready-fade'));
|
||||
final previewFade = find
|
||||
.descendant(of: readyFade, matching: find.byType(FadeTransition))
|
||||
.first;
|
||||
expect(tester.widget<FadeTransition>(previewFade).opacity.value, 0);
|
||||
cameraStart.complete();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expect(
|
||||
tester.widget<FadeTransition>(previewFade).opacity.value,
|
||||
inExclusiveRange(0, 1),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.widget<FadeTransition>(previewFade).opacity.value, 1);
|
||||
expect(
|
||||
tester.widget<ClipRSuperellipse>(portal).borderRadius,
|
||||
BorderRadius.circular(40),
|
||||
@@ -466,6 +483,7 @@ class _FakeMobileScannerPlatform extends MobileScannerPlatform {
|
||||
var _isDisposed = false;
|
||||
var stopCalls = 0;
|
||||
var startCalls = 0;
|
||||
Future<void>? startGate;
|
||||
|
||||
@override
|
||||
Stream<BarcodeCapture?> get barcodesStream => _barcodes.stream;
|
||||
@@ -480,6 +498,7 @@ class _FakeMobileScannerPlatform extends MobileScannerPlatform {
|
||||
@override
|
||||
Future<MobileScannerViewAttributes> start(StartOptions startOptions) async {
|
||||
startCalls++;
|
||||
await startGate;
|
||||
return const MobileScannerViewAttributes(
|
||||
cameraDirection: CameraFacing.back,
|
||||
currentTorchMode: TorchState.unavailable,
|
||||
|
||||
Reference in New Issue
Block a user