diff --git a/CHANGELOG.md b/CHANGELOG.md index a87b367..df9af2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ See the [Migration Guide](guides/migration_guide.md) for the details of breaking ### Fixes - Handle exceptions after all flows. +- Fix various of problems with the capture button. ## 4.0.3 diff --git a/lib/src/states/camera_picker_state.dart b/lib/src/states/camera_picker_state.dart index 82e059f..149f815 100644 --- a/lib/src/states/camera_picker_state.dart +++ b/lib/src/states/camera_picker_state.dart @@ -57,8 +57,8 @@ class CameraPickerState extends State /// 可用的相机实例 late List cameras; - /// Whether the controller is handling taking picture or recording video. - /// 相机控制器是否在处理拍照或录像 + /// Whether the controller is handling method calls. + /// 相机控制器是否在处理方法调用 bool isControllerBusy = false; /// Current exposure offset. @@ -186,6 +186,12 @@ class CameraPickerState extends State return pickerConfig.minimumRecordingDuration; } + /// Whether the capture button is displaying. + bool get shouldCaptureButtonDisplay => + isControllerBusy || + (innerController?.value.isRecordingVideo ?? false) && + isRecordingRestricted; + /// Whether the camera preview should be rotated. bool get isCameraRotated => pickerConfig.cameraQuarterTurns % 4 != 0; @@ -258,6 +264,7 @@ class CameraPickerState extends State } else if (state == AppLifecycleState.inactive) { c.dispose(); innerController = null; + isControllerBusy = false; } } @@ -265,8 +272,11 @@ class CameraPickerState extends State /// 根据 [constraints] 获取相机预览适用的缩放。 double effectiveCameraScale( BoxConstraints constraints, - CameraController controller, + CameraController? controller, ) { + if (controller == null) { + return 1; + } final int turns = cameraQuarterTurns; final String orientation = controller.value.deviceOrientation.toString(); // Fetch the biggest size from the constraints. @@ -831,7 +841,10 @@ class CameraPickerState extends State if (isControllerBusy) { return; } - isControllerBusy = true; + setState(() { + isControllerBusy = true; + isShootingButtonAnimate = true; + }); final ExposureMode previousExposureMode = controller.value.exposureMode; try { await Future.wait(>[ @@ -881,8 +894,10 @@ class CameraPickerState extends State } catch (e, s) { handleErrorWithHandler(e, s, pickerConfig.onError); } finally { - isControllerBusy = false; - safeSetState(() {}); + safeSetState(() { + isControllerBusy = false; + isShootingButtonAnimate = false; + }); } } @@ -909,14 +924,7 @@ class CameraPickerState extends State /// 将被取消,并且状态会重置。 void recordDetectionCancel(PointerUpEvent event) { recordDetectTimer?.cancel(); - if (isShootingButtonAnimate) { - safeSetState(() { - isShootingButtonAnimate = false; - }); - } if (innerController?.value.isRecordingVideo == true) { - lastShootingButtonPressedPosition = null; - safeSetState(() {}); stopRecordingVideo(); } } @@ -940,7 +948,6 @@ class CameraPickerState extends State ..reset() ..start(); } catch (e, s) { - isControllerBusy = false; if (!controller.value.isRecordingVideo) { handleErrorWithHandler(e, s, pickerConfig.onError); return; @@ -955,26 +962,31 @@ class CameraPickerState extends State recordStopwatch.stop(); } } finally { - safeSetState(() {}); + safeSetState(() { + isControllerBusy = false; + }); } } /// Stop the recording process. /// 停止录制视频 Future stopRecordingVideo() async { - void handleError() { - recordCountdownTimer?.cancel(); - isShootingButtonAnimate = false; - safeSetState(() {}); + if (isControllerBusy) { + return; } recordStopwatch.stop(); - if (!controller.value.isRecordingVideo) { - handleError(); + if (innerController == null || !controller.value.isRecordingVideo) { + recordCountdownTimer?.cancel(); + safeSetState(() { + isControllerBusy = false; + isShootingButtonAnimate = false; + }); return; } safeSetState(() { - isShootingButtonAnimate = false; + isControllerBusy = true; + lastShootingButtonPressedPosition = null; }); try { final XFile file = await controller.stopVideoRecording(); @@ -982,7 +994,7 @@ class CameraPickerState extends State pickerConfig.onMinimumRecordDurationNotMet?.call(); return; } - await controller.pausePreview(); + controller.pausePreview(); final bool? isCapturedFileHandled = pickerConfig.onXFileCaptured?.call( file, CameraPickerViewType.video, @@ -1000,12 +1012,14 @@ class CameraPickerState extends State await controller.resumePreview(); } } catch (e, s) { - handleError(); + recordCountdownTimer?.cancel(); initCameras(); handleErrorWithHandler(e, s, pickerConfig.onError); } finally { - isControllerBusy = false; - safeSetState(() {}); + safeSetState(() { + isControllerBusy = false; + isShootingButtonAnimate = false; + }); } } @@ -1319,17 +1333,17 @@ class CameraPickerState extends State ), ), ), - if ((innerController?.value.isRecordingVideo ?? false) && - isRecordingRestricted) + if (shouldCaptureButtonDisplay) RotatedBox( quarterTurns: !enableScaledPreview ? cameraQuarterTurns : 0, child: CameraProgressButton( isAnimating: isShootingButtonAnimate, + isBusy: isControllerBusy, duration: pickerConfig.maximumRecordingDuration!, - outerRadius: outerSize.width, + size: outerSize, ringsColor: theme.indicatorColor, - ringsWidth: 2, + ringsWidth: 3, ), ), ], @@ -1675,7 +1689,7 @@ class CameraPickerState extends State // Scale the preview if the config is enabled. if (enableScaledPreview) { preview = Transform.scale( - scale: effectiveCameraScale(constraints, controller), + scale: effectiveCameraScale(constraints, innerController), child: Center(child: transformedWidget ?? preview), ); // Rotated the preview if the turns is valid. diff --git a/lib/src/widgets/camera_progress_button.dart b/lib/src/widgets/camera_progress_button.dart index dad8cd0..81bbe9c 100644 --- a/lib/src/widgets/camera_progress_button.dart +++ b/lib/src/widgets/camera_progress_button.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'dart:math' as math; - import 'package:flutter/material.dart'; import '../constants/styles.dart'; @@ -13,18 +11,18 @@ class CameraProgressButton extends StatefulWidget { const CameraProgressButton({ super.key, required this.isAnimating, - required this.outerRadius, + required this.isBusy, + required this.size, required this.ringsWidth, this.ringsColor = wechatThemeColor, - this.progress = 0.0, this.duration = const Duration(seconds: 15), }); final bool isAnimating; - final double outerRadius; + final bool isBusy; + final Size size; final double ringsWidth; final Color ringsColor; - final double progress; final Duration duration; @override @@ -33,16 +31,15 @@ class CameraProgressButton extends StatefulWidget { class _CircleProgressState extends State with SingleTickerProviderStateMixin { - final GlobalKey paintKey = GlobalKey(); - - late final AnimationController progressController = AnimationController( - duration: widget.duration, - vsync: this, - )..value = widget.progress; + late final AnimationController progressController; @override void initState() { super.initState(); + progressController = AnimationController( + duration: widget.duration, + vsync: this, + ); ambiguate(WidgetsBinding.instance)?.addPostFrameCallback((_) { if (widget.isAnimating) { progressController.forward(); @@ -53,6 +50,18 @@ class _CircleProgressState extends State @override void didUpdateWidget(CameraProgressButton oldWidget) { super.didUpdateWidget(oldWidget); + if (widget.isBusy != oldWidget.isBusy) { + if (widget.isBusy) { + progressController + ..reset() + ..stop(); + } else { + progressController.value = 0.0; + if (!progressController.isAnimating) { + progressController.forward(); + } + } + } if (widget.isAnimating != oldWidget.isAnimating) { if (widget.isAnimating) { progressController.forward(); @@ -70,18 +79,19 @@ class _CircleProgressState extends State @override Widget build(BuildContext context) { - final Size size = Size.square(widget.outerRadius * 2); + if (!widget.isAnimating && !widget.isBusy) { + return const SizedBox.shrink(); + } return Center( - child: RepaintBoundary( - child: AnimatedBuilder( - animation: progressController, - builder: (_, __) => CustomPaint( - key: paintKey, - size: size, - painter: CameraProgressButtonPainter( - progress: progressController.value, - ringsWidth: widget.ringsWidth, - ringsColor: widget.ringsColor, + child: SizedBox.fromSize( + size: widget.size, + child: RepaintBoundary( + child: AnimatedBuilder( + animation: progressController, + builder: (_, __) => CircularProgressIndicator( + color: widget.ringsColor, + strokeWidth: widget.ringsWidth, + value: widget.isBusy ? null : progressController.value, ), ), ), @@ -89,48 +99,3 @@ class _CircleProgressState extends State ); } } - -class CameraProgressButtonPainter extends CustomPainter { - const CameraProgressButtonPainter({ - required this.ringsWidth, - required this.ringsColor, - required this.progress, - }); - - final double ringsWidth; - final Color ringsColor; - final double progress; - - @override - void paint(Canvas canvas, Size size) { - final double center = size.width / 2; - final Offset offsetCenter = Offset(center, center); - final double drawRadius = size.width / 2 - ringsWidth; - - final double outerRadius = center; - final double innerRadius = center - ringsWidth * 2; - - final double progressWidth = outerRadius - innerRadius; - canvas.save(); - canvas.translate(0.0, size.width); - canvas.rotate(-math.pi / 2); - final Rect arcRect = Rect.fromCircle( - center: offsetCenter, - radius: drawRadius, - ); - final Paint progressPaint = Paint() - ..color = ringsColor - ..style = PaintingStyle.stroke - ..strokeWidth = progressWidth; - canvas - ..drawArc(arcRect, 0, math.pi * 2 * progress, false, progressPaint) - ..restore(); - } - - @override - bool shouldRepaint(CameraProgressButtonPainter oldDelegate) { - return oldDelegate.ringsWidth != ringsWidth || - oldDelegate.ringsColor != ringsColor || - oldDelegate.progress != progress; - } -} diff --git a/lib/wechat_camera_picker.dart b/lib/wechat_camera_picker.dart index fcd9a4e..656b996 100644 --- a/lib/wechat_camera_picker.dart +++ b/lib/wechat_camera_picker.dart @@ -20,4 +20,3 @@ export 'src/widgets/camera_focus_point.dart'; export 'src/widgets/camera_picker.dart'; export 'src/widgets/camera_picker_page_route.dart'; export 'src/widgets/camera_picker_viewer.dart'; -export 'src/widgets/camera_progress_button.dart';