From 06ac7b1875697b9c9668d3f9eced8bb695cd9b69 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sat, 28 Oct 2023 14:28:47 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=90=9B=20Fix=20the=20capture=20button?= =?UTF-8?q?=20callback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/states/camera_picker_state.dart | 56 ++++++++++++++----------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/lib/src/states/camera_picker_state.dart b/lib/src/states/camera_picker_state.dart index aacc51e..7db32de 100644 --- a/lib/src/states/camera_picker_state.dart +++ b/lib/src/states/camera_picker_state.dart @@ -56,8 +56,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. @@ -185,6 +185,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; @@ -245,6 +251,7 @@ class CameraPickerState extends State } else if (state == AppLifecycleState.inactive) { c.dispose(); innerController = null; + isControllerBusy = false; } } @@ -780,7 +787,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(>[ @@ -830,8 +840,10 @@ class CameraPickerState extends State } catch (e, s) { handleErrorWithHandler(e, s, pickerConfig.onError); } finally { - isControllerBusy = false; - safeSetState(() {}); + safeSetState(() { + isControllerBusy = false; + isShootingButtonAnimate = false; + }); } } @@ -858,14 +870,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(); } } @@ -889,7 +894,6 @@ class CameraPickerState extends State ..reset() ..start(); } catch (e, s) { - isControllerBusy = false; if (!controller.value.isRecordingVideo) { handleErrorWithHandler(e, s, pickerConfig.onError); return; @@ -904,26 +908,27 @@ 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(); + recordCountdownTimer?.cancel(); return; } safeSetState(() { - isShootingButtonAnimate = false; + isControllerBusy = true; + lastShootingButtonPressedPosition = null; }); try { final XFile file = await controller.stopVideoRecording(); @@ -949,12 +954,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; + }); } } @@ -1268,8 +1275,7 @@ class CameraPickerState extends State ), ), ), - if ((innerController?.value.isRecordingVideo ?? false) && - isRecordingRestricted) + if (shouldCaptureButtonDisplay) RotatedBox( quarterTurns: !enableScaledPreview ? cameraQuarterTurns : 0, From 0b211ae88b17dc87b938e3744f71069b638ee639 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sat, 28 Oct 2023 14:29:35 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=90=9B=20Fix=20context=20lookup=20for?= =?UTF-8?q?=20scaling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/states/camera_picker_state.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/src/states/camera_picker_state.dart b/lib/src/states/camera_picker_state.dart index 7db32de..a2c49e4 100644 --- a/lib/src/states/camera_picker_state.dart +++ b/lib/src/states/camera_picker_state.dart @@ -259,8 +259,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. @@ -1611,7 +1614,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. From da4b1126efa2fd196e73d3ed0ae9a3d042a2d898 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sat, 28 Oct 2023 14:30:21 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20and=20hide?= =?UTF-8?q?=20the=20progress=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/states/camera_picker_state.dart | 5 +- lib/src/widgets/camera_progress_button.dart | 101 +++++++------------- lib/wechat_camera_picker.dart | 1 - 3 files changed, 36 insertions(+), 71 deletions(-) diff --git a/lib/src/states/camera_picker_state.dart b/lib/src/states/camera_picker_state.dart index a2c49e4..373d124 100644 --- a/lib/src/states/camera_picker_state.dart +++ b/lib/src/states/camera_picker_state.dart @@ -1284,10 +1284,11 @@ class CameraPickerState extends State !enableScaledPreview ? cameraQuarterTurns : 0, child: CameraProgressButton( isAnimating: isShootingButtonAnimate, + isBusy: isControllerBusy, duration: pickerConfig.maximumRecordingDuration!, - outerRadius: outerSize.width, + size: outerSize, ringsColor: theme.indicatorColor, - ringsWidth: 2, + ringsWidth: 3, ), ), ], 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'; From 76cf5cc4b82f3b99c37b2c1a44d29a5fedd9b53c Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sat, 28 Oct 2023 14:30:48 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Do=20not=20wait=20for?= =?UTF-8?q?=20preview=20pausing=20to=20speed=20up=20record=20stopping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/states/camera_picker_state.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/states/camera_picker_state.dart b/lib/src/states/camera_picker_state.dart index 373d124..2f80a3b 100644 --- a/lib/src/states/camera_picker_state.dart +++ b/lib/src/states/camera_picker_state.dart @@ -939,7 +939,7 @@ class CameraPickerState extends State pickerConfig.onMinimumRecordDurationNotMet?.call(); return; } - await controller.pausePreview(); + controller.pausePreview(); final bool? isCapturedFileHandled = pickerConfig.onXFileCaptured?.call( file, CameraPickerViewType.video, From 226512d45b4508bf11417b8b0003f9292978d10c Mon Sep 17 00:00:00 2001 From: Alex Li Date: Mon, 30 Oct 2023 16:29:47 +0800 Subject: [PATCH 5/6] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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 From 7c8ab0f8f447f7ffac56c8c9af8e97290e7d20b2 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Mon, 30 Oct 2023 19:32:30 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=90=9B=20Fix=20https://github.com/flu?= =?UTF-8?q?ttercandies/flutter=5Fwechat=5Fcamera=5Fpicker/issues/197#issue?= =?UTF-8?q?comment-1784985635?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/states/camera_picker_state.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/states/camera_picker_state.dart b/lib/src/states/camera_picker_state.dart index 43749c9..149f815 100644 --- a/lib/src/states/camera_picker_state.dart +++ b/lib/src/states/camera_picker_state.dart @@ -976,8 +976,12 @@ class CameraPickerState extends State } recordStopwatch.stop(); - if (!controller.value.isRecordingVideo) { + if (innerController == null || !controller.value.isRecordingVideo) { recordCountdownTimer?.cancel(); + safeSetState(() { + isControllerBusy = false; + isShootingButtonAnimate = false; + }); return; } safeSetState(() {