Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[camera] Add back Optional type for nullable CameraController orientations #6911

Merged
merged 15 commits into from
Jan 26, 2023
Merged
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.10.2

* Adds option to `CameraValue`s `copyWith` method to explicitly interpret optional orientation values.

## 0.10.1

* Remove usage of deprecated quiver Optional type.
Expand Down
39 changes: 31 additions & 8 deletions packages/camera/camera/lib/src/camera_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ class CameraValue {
///
/// Explicitly specified fields get the specified value, all other fields get
/// the same value of the current object.
///
/// Nullable orientations ([lockedCaptureOrientation], [recordingOrientation],
/// and [previewPauseOrientation]) default to current object's value if
/// specified as null. To have specified orientations explicitly interpreted,
/// set [clearNullOrientationsFlag] to true.
CameraValue copyWith({
bool? isInitialized,
bool? isRecordingVideo,
Expand All @@ -164,6 +169,7 @@ class CameraValue {
DeviceOrientation? recordingOrientation,
bool? isPreviewPaused,
DeviceOrientation? previewPauseOrientation,
bool clearNullOrientationsFlag = false,
}) {
return CameraValue(
isInitialized: isInitialized ?? this.isInitialized,
Expand All @@ -180,12 +186,16 @@ class CameraValue {
exposurePointSupported ?? this.exposurePointSupported,
focusPointSupported: focusPointSupported ?? this.focusPointSupported,
deviceOrientation: deviceOrientation ?? this.deviceOrientation,
lockedCaptureOrientation:
lockedCaptureOrientation ?? this.lockedCaptureOrientation,
recordingOrientation: recordingOrientation ?? this.recordingOrientation,
lockedCaptureOrientation: clearNullOrientationsFlag
? lockedCaptureOrientation
: lockedCaptureOrientation ?? this.lockedCaptureOrientation,
recordingOrientation: clearNullOrientationsFlag
? recordingOrientation
: recordingOrientation ?? this.recordingOrientation,
isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused,
previewPauseOrientation:
previewPauseOrientation ?? this.previewPauseOrientation,
previewPauseOrientation: clearNullOrientationsFlag
? previewPauseOrientation
: previewPauseOrientation ?? this.previewPauseOrientation,
);
}

Expand Down Expand Up @@ -367,7 +377,11 @@ class CameraController extends ValueNotifier<CameraValue> {
}
try {
await CameraPlatform.instance.resumePreview(_cameraId);
value = value.copyWith(isPreviewPaused: false);
value = value.copyWith(
isPreviewPaused: false,
lockedCaptureOrientation: value.lockedCaptureOrientation,
recordingOrientation: value.recordingOrientation,
clearNullOrientationsFlag: true);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
Expand Down Expand Up @@ -519,7 +533,12 @@ class CameraController extends ValueNotifier<CameraValue> {
try {
final XFile file =
await CameraPlatform.instance.stopVideoRecording(_cameraId);
value = value.copyWith(isRecordingVideo: false);
value = value.copyWith(
isRecordingVideo: false,
lockedCaptureOrientation: value.lockedCaptureOrientation,
previewPauseOrientation: value.previewPauseOrientation,
clearNullOrientationsFlag: true,
);
return file;
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
Expand Down Expand Up @@ -757,7 +776,11 @@ class CameraController extends ValueNotifier<CameraValue> {
Future<void> unlockCaptureOrientation() async {
try {
await CameraPlatform.instance.unlockCaptureOrientation(_cameraId);
value = value.copyWith();
value = value.copyWith(
recordingOrientation: value.recordingOrientation,
previewPauseOrientation: value.previewPauseOrientation,
clearNullOrientationsFlag: true,
);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
Dart.
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.10.1
version: 0.10.2

environment:
sdk: ">=2.14.0 <3.0.0"
Expand Down
86 changes: 86 additions & 0 deletions packages/camera/camera/test/camera_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,85 @@ void main() {
)));
});

test(
'stopVideoRecording() throws $CameraException when not recording video',
() async {
final CameraController cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90),
ResolutionPreset.max);

await cameraController.initialize();

cameraController.value =
cameraController.value.copyWith(isRecordingVideo: false);

expect(
cameraController.stopVideoRecording(),
throwsA(isA<CameraException>().having(
(CameraException error) => error.description,
'No video is recording',
'stopVideoRecording was called when no video is recording.',
)));
});

test('stopVideoRecording() calls $CameraPlatform', () async {
final CameraController cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90),
ResolutionPreset.max);

await cameraController.initialize();

cameraController.value =
cameraController.value.copyWith(isRecordingVideo: true);

when(CameraPlatform.instance
.stopVideoRecording(cameraController.cameraId))
.thenAnswer((_) => Future<XFile>.value(XFile('bar/foo.mpeg')));

await cameraController.stopVideoRecording();

expect(cameraController.value.recordingOrientation, equals(null));

verify(CameraPlatform.instance
.stopVideoRecording(cameraController.cameraId))
.called(1);
});

test('stopVideoRecording() throws $CameraException on $PlatformException',
() async {
final CameraController cameraController = CameraController(
const CameraDescription(
name: 'cam',
lensDirection: CameraLensDirection.back,
sensorOrientation: 90),
ResolutionPreset.max);

await cameraController.initialize();
cameraController.value =
cameraController.value.copyWith(isRecordingVideo: true);

when(CameraPlatform.instance
.stopVideoRecording(cameraController.cameraId))
.thenThrow(CameraException(
'foo',
'bar',
));

expect(
cameraController.stopVideoRecording(),
throwsA(isA<CameraException>().having(
(CameraException error) => error.description,
'foo',
'bar',
)));
});

test('getMaxZoomLevel() throws $CameraException when uninitialized',
() async {
final CameraController cameraController = CameraController(
Expand Down Expand Up @@ -1242,6 +1321,7 @@ void main() {
verify(CameraPlatform.instance.resumePreview(cameraController.cameraId))
.called(1);
expect(cameraController.value.isPreviewPaused, equals(false));
expect(cameraController.value.previewPauseOrientation, equals(null));
});

test('resumePreview() does not call $CameraPlatform when not paused',
Expand Down Expand Up @@ -1457,6 +1537,12 @@ class MockCameraPlatform extends Mock
{Duration? maxVideoDuration}) =>
Future<XFile>.value(mockVideoRecordingXFile);

@override
Future<XFile> stopVideoRecording(int cameraId) async => super.noSuchMethod(
Invocation.method(#stopVideoRecording, <Object?>[cameraId]),
returnValue: Future<XFile>.value(mockVideoRecordingXFile),
) as Future<XFile>;

@override
Future<void> lockCaptureOrientation(
int? cameraId, DeviceOrientation? orientation) async =>
Expand Down
4 changes: 4 additions & 0 deletions packages/camera/camera_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.10.3

* Adds option to `CameraValue`s `copyWith` method to explicitly interpret optional orientation values.

## 0.10.2

* Remove usage of deprecated quiver Optional type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ class CameraValue {
///
/// Explicitly specified fields get the specified value, all other fields get
/// the same value of the current object.
///
/// Nullable orientations ([lockedCaptureOrientation], [recordingOrientation],
/// and [previewPauseOrientation]) default to current object's value if
/// specified as null. To have specified orientations explicitly interpreted,
/// set [clearNullOrientationsFlag] to true.
CameraValue copyWith({
bool? isInitialized,
bool? isRecordingVideo,
Expand All @@ -112,6 +117,7 @@ class CameraValue {
DeviceOrientation? recordingOrientation,
bool? isPreviewPaused,
DeviceOrientation? previewPauseOrientation,
bool clearNullOrientationsFlag = false,
camsim99 marked this conversation as resolved.
Show resolved Hide resolved
}) {
return CameraValue(
isInitialized: isInitialized ?? this.isInitialized,
Expand All @@ -124,12 +130,16 @@ class CameraValue {
exposureMode: exposureMode ?? this.exposureMode,
focusMode: focusMode ?? this.focusMode,
deviceOrientation: deviceOrientation ?? this.deviceOrientation,
lockedCaptureOrientation:
lockedCaptureOrientation ?? this.lockedCaptureOrientation,
recordingOrientation: recordingOrientation ?? this.recordingOrientation,
lockedCaptureOrientation: clearNullOrientationsFlag
? lockedCaptureOrientation
: lockedCaptureOrientation ?? this.lockedCaptureOrientation,
recordingOrientation: clearNullOrientationsFlag
? recordingOrientation
: recordingOrientation ?? this.recordingOrientation,
isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused,
previewPauseOrientation:
previewPauseOrientation ?? this.previewPauseOrientation,
previewPauseOrientation: clearNullOrientationsFlag
? previewPauseOrientation
: previewPauseOrientation ?? this.previewPauseOrientation,
);
}

Expand Down Expand Up @@ -264,7 +274,11 @@ class CameraController extends ValueNotifier<CameraValue> {
/// Resumes the current camera preview
Future<void> resumePreview() async {
await CameraPlatform.instance.resumePreview(_cameraId);
value = value.copyWith(isPreviewPaused: false);
value = value.copyWith(
isPreviewPaused: false,
lockedCaptureOrientation: value.lockedCaptureOrientation,
recordingOrientation: value.recordingOrientation,
clearNullOrientationsFlag: true);
}

/// Captures an image and returns the file where it was saved.
Expand Down Expand Up @@ -324,6 +338,9 @@ class CameraController extends ValueNotifier<CameraValue> {
value = value.copyWith(
isRecordingVideo: false,
isRecordingPaused: false,
lockedCaptureOrientation: value.lockedCaptureOrientation,
previewPauseOrientation: value.previewPauseOrientation,
clearNullOrientationsFlag: true,
);
return file;
}
Expand Down Expand Up @@ -398,6 +415,11 @@ class CameraController extends ValueNotifier<CameraValue> {
/// Unlocks the capture orientation.
Future<void> unlockCaptureOrientation() async {
await CameraPlatform.instance.unlockCaptureOrientation(_cameraId);
value = value.copyWith(
recordingOrientation: value.recordingOrientation,
previewPauseOrientation: value.previewPauseOrientation,
clearNullOrientationsFlag: true,
);
}

/// Sets the focus mode for taking pictures.
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: camera_android
description: Android implementation of the camera plugin.
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.10.2
version: 0.10.3

environment:
sdk: ">=2.14.0 <3.0.0"
Expand Down
4 changes: 4 additions & 0 deletions packages/camera/camera_avfoundation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.11

* Adds option to `CameraValue`s `copyWith` method to explicitly interpret optional orientation values.

## 0.9.10

* Remove usage of deprecated quiver Optional type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ class CameraValue {
///
/// Explicitly specified fields get the specified value, all other fields get
/// the same value of the current object.
///
/// Nullable orientations ([lockedCaptureOrientation], [recordingOrientation],
/// and [previewPauseOrientation]) default to current object's value if
/// specified as null. To have specified orientations explicitly interpreted,
/// set [clearNullOrientationsFlag] to true.
CameraValue copyWith({
bool? isInitialized,
bool? isRecordingVideo,
Expand All @@ -112,6 +117,7 @@ class CameraValue {
DeviceOrientation? recordingOrientation,
bool? isPreviewPaused,
DeviceOrientation? previewPauseOrientation,
bool clearNullOrientationsFlag = false,
}) {
return CameraValue(
isInitialized: isInitialized ?? this.isInitialized,
Expand All @@ -124,12 +130,16 @@ class CameraValue {
exposureMode: exposureMode ?? this.exposureMode,
focusMode: focusMode ?? this.focusMode,
deviceOrientation: deviceOrientation ?? this.deviceOrientation,
lockedCaptureOrientation:
lockedCaptureOrientation ?? this.lockedCaptureOrientation,
recordingOrientation: recordingOrientation ?? this.recordingOrientation,
lockedCaptureOrientation: clearNullOrientationsFlag
? lockedCaptureOrientation
: lockedCaptureOrientation ?? this.lockedCaptureOrientation,
recordingOrientation: clearNullOrientationsFlag
? recordingOrientation
: recordingOrientation ?? this.recordingOrientation,
isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused,
previewPauseOrientation:
previewPauseOrientation ?? this.previewPauseOrientation,
previewPauseOrientation: clearNullOrientationsFlag
? previewPauseOrientation
: previewPauseOrientation ?? this.previewPauseOrientation,
);
}

Expand Down Expand Up @@ -264,7 +274,11 @@ class CameraController extends ValueNotifier<CameraValue> {
/// Resumes the current camera preview
Future<void> resumePreview() async {
await CameraPlatform.instance.resumePreview(_cameraId);
value = value.copyWith(isPreviewPaused: false);
value = value.copyWith(
isPreviewPaused: false,
lockedCaptureOrientation: value.lockedCaptureOrientation,
recordingOrientation: value.recordingOrientation,
clearNullOrientationsFlag: true);
}

/// Captures an image and returns the file where it was saved.
Expand Down Expand Up @@ -321,7 +335,12 @@ class CameraController extends ValueNotifier<CameraValue> {

final XFile file =
await CameraPlatform.instance.stopVideoRecording(_cameraId);
value = value.copyWith(isRecordingVideo: false);
value = value.copyWith(
isRecordingVideo: false,
lockedCaptureOrientation: value.lockedCaptureOrientation,
previewPauseOrientation: value.previewPauseOrientation,
clearNullOrientationsFlag: true,
);
return file;
}

Expand Down Expand Up @@ -395,6 +414,11 @@ class CameraController extends ValueNotifier<CameraValue> {
/// Unlocks the capture orientation.
Future<void> unlockCaptureOrientation() async {
await CameraPlatform.instance.unlockCaptureOrientation(_cameraId);
value = value.copyWith(
recordingOrientation: value.recordingOrientation,
previewPauseOrientation: value.previewPauseOrientation,
clearNullOrientationsFlag: true,
);
}

/// Sets the focus mode for taking pictures.
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera_avfoundation/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: camera_avfoundation
description: iOS implementation of the camera plugin.
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_avfoundation
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.9.10
version: 0.9.11

environment:
sdk: ">=2.14.0 <3.0.0"
Expand Down