Skip to content

Commit

Permalink
🐛 Fix various of problems with the capture button (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexV525 authored Oct 31, 2023
1 parent 5370cca commit 2203168
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 100 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
76 changes: 45 additions & 31 deletions lib/src/states/camera_picker_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ class CameraPickerState extends State<CameraPicker>
/// 可用的相机实例
late List<CameraDescription> cameras;

/// Whether the controller is handling taking picture or recording video.
/// 相机控制器是否在处理拍照或录像
/// Whether the controller is handling method calls.
/// 相机控制器是否在处理方法调用
bool isControllerBusy = false;

/// Current exposure offset.
Expand Down Expand Up @@ -186,6 +186,12 @@ class CameraPickerState extends State<CameraPicker>
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;

Expand Down Expand Up @@ -258,15 +264,19 @@ class CameraPickerState extends State<CameraPicker>
} else if (state == AppLifecycleState.inactive) {
c.dispose();
innerController = null;
isControllerBusy = false;
}
}

/// Adjust the proper scale type according to the [constraints].
/// 根据 [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.
Expand Down Expand Up @@ -831,7 +841,10 @@ class CameraPickerState extends State<CameraPicker>
if (isControllerBusy) {
return;
}
isControllerBusy = true;
setState(() {
isControllerBusy = true;
isShootingButtonAnimate = true;
});
final ExposureMode previousExposureMode = controller.value.exposureMode;
try {
await Future.wait(<Future<void>>[
Expand Down Expand Up @@ -881,8 +894,10 @@ class CameraPickerState extends State<CameraPicker>
} catch (e, s) {
handleErrorWithHandler(e, s, pickerConfig.onError);
} finally {
isControllerBusy = false;
safeSetState(() {});
safeSetState(() {
isControllerBusy = false;
isShootingButtonAnimate = false;
});
}
}

Expand All @@ -909,14 +924,7 @@ class CameraPickerState extends State<CameraPicker>
/// 将被取消,并且状态会重置。
void recordDetectionCancel(PointerUpEvent event) {
recordDetectTimer?.cancel();
if (isShootingButtonAnimate) {
safeSetState(() {
isShootingButtonAnimate = false;
});
}
if (innerController?.value.isRecordingVideo == true) {
lastShootingButtonPressedPosition = null;
safeSetState(() {});
stopRecordingVideo();
}
}
Expand All @@ -940,7 +948,6 @@ class CameraPickerState extends State<CameraPicker>
..reset()
..start();
} catch (e, s) {
isControllerBusy = false;
if (!controller.value.isRecordingVideo) {
handleErrorWithHandler(e, s, pickerConfig.onError);
return;
Expand All @@ -955,34 +962,39 @@ class CameraPickerState extends State<CameraPicker>
recordStopwatch.stop();
}
} finally {
safeSetState(() {});
safeSetState(() {
isControllerBusy = false;
});
}
}

/// Stop the recording process.
/// 停止录制视频
Future<void> 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();
if (recordStopwatch.elapsed < minimumRecordingDuration) {
pickerConfig.onMinimumRecordDurationNotMet?.call();
return;
}
await controller.pausePreview();
controller.pausePreview();
final bool? isCapturedFileHandled = pickerConfig.onXFileCaptured?.call(
file,
CameraPickerViewType.video,
Expand All @@ -1000,12 +1012,14 @@ class CameraPickerState extends State<CameraPicker>
await controller.resumePreview();
}
} catch (e, s) {
handleError();
recordCountdownTimer?.cancel();
initCameras();
handleErrorWithHandler(e, s, pickerConfig.onError);
} finally {
isControllerBusy = false;
safeSetState(() {});
safeSetState(() {
isControllerBusy = false;
isShootingButtonAnimate = false;
});
}
}

Expand Down Expand Up @@ -1319,17 +1333,17 @@ class CameraPickerState extends State<CameraPicker>
),
),
),
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,
),
),
],
Expand Down Expand Up @@ -1675,7 +1689,7 @@ class CameraPickerState extends State<CameraPicker>
// 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.
Expand Down
101 changes: 33 additions & 68 deletions lib/src/widgets/camera_progress_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
Expand All @@ -33,16 +31,15 @@ class CameraProgressButton extends StatefulWidget {

class _CircleProgressState extends State<CameraProgressButton>
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();
Expand All @@ -53,6 +50,18 @@ class _CircleProgressState extends State<CameraProgressButton>
@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();
Expand All @@ -70,67 +79,23 @@ class _CircleProgressState extends State<CameraProgressButton>

@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,
),
),
),
),
);
}
}

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;
}
}
1 change: 0 additions & 1 deletion lib/wechat_camera_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

0 comments on commit 2203168

Please sign in to comment.