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

Commit 43ee609

Browse files
[camera_platform_interface] Add platform interface methods for locking capture orientation. (#3389)
* Expand platform interface to support reporting device orientation * Switch to flutter DeviceOrientation enum * Add interface methods for (un)locking the capture orientation. * Update capture orientation interfaces and add unit tests. * Made device orientation mandatory for locking capture orientation in the platform interface. * Update comment * Update comment. * Update changelog and pubspec version * Update packages/camera/camera_platform_interface/lib/src/events/device_event.dart Co-authored-by: Maurits van Beusekom <[email protected]> * Update packages/camera/camera_platform_interface/lib/src/events/device_event.dart Co-authored-by: Maurits van Beusekom <[email protected]> * Update packages/camera/camera_platform_interface/lib/src/events/device_event.dart Co-authored-by: Maurits van Beusekom <[email protected]> * Update packages/camera/camera_platform_interface/lib/src/events/device_event.dart Co-authored-by: Maurits van Beusekom <[email protected]> * Update packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart Co-authored-by: Maurits van Beusekom <[email protected]> * Update packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart Co-authored-by: Maurits van Beusekom <[email protected]> Co-authored-by: Maurits van Beusekom <[email protected]> Co-authored-by: Maurits van Beusekom <[email protected]>
1 parent dac4b6f commit 43ee609

File tree

12 files changed

+384
-23
lines changed

12 files changed

+384
-23
lines changed

packages/camera/camera_platform_interface/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 1.5.0
2+
3+
- Introduces interface methods for locking and unlocking the capture orientation.
4+
- Introduces interface method for listening to the device orientation.
5+
16
## 1.4.0
27

38
- Added interface methods to support auto focus.

packages/camera/camera_platform_interface/lib/camera_platform_interface.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
export 'src/events/camera_event.dart';
6+
export 'src/events/device_event.dart';
67
export 'src/platform_interface/camera_platform.dart';
78
export 'src/types/types.dart';
89

packages/camera/camera_platform_interface/lib/src/events/camera_event.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import 'package:camera_platform_interface/src/types/focus_mode.dart';
66

77
import '../../camera_platform_interface.dart';
88

9-
/// Generic Event coming from the native side of Camera.
9+
/// Generic Event coming from the native side of Camera,
10+
/// related to a specific camera module.
1011
///
1112
/// All [CameraEvent]s contain the `cameraId` that originated the event. This
1213
/// should never be `null`.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:camera_platform_interface/src/utils/utils.dart';
6+
import 'package:flutter/services.dart';
7+
8+
/// Generic Event coming from the native side of Camera,
9+
/// not related to a specific camera module.
10+
///
11+
/// This class is used as a base class for all the events that might be
12+
/// triggered from a device, but it is never used directly as an event type.
13+
///
14+
/// Do NOT instantiate new events like `DeviceEvent()` directly,
15+
/// use a specific class instead:
16+
///
17+
/// Do `class NewEvent extend DeviceEvent` when creating your own events.
18+
/// See below for examples: `DeviceOrientationChangedEvent`...
19+
/// These events are more semantic and more pleasant to use than raw generics.
20+
/// They can be (and in fact, are) filtered by the `instanceof`-operator.
21+
abstract class DeviceEvent {}
22+
23+
/// The [DeviceOrientationChangedEvent] is fired every time the user changes the
24+
/// physical orientation of the device.
25+
class DeviceOrientationChangedEvent extends DeviceEvent {
26+
/// The new orientation of the device
27+
final DeviceOrientation orientation;
28+
29+
/// Build a new orientation changed event.
30+
DeviceOrientationChangedEvent(this.orientation);
31+
32+
/// Converts the supplied [Map] to an instance of the [DeviceOrientationChangedEvent]
33+
/// class.
34+
DeviceOrientationChangedEvent.fromJson(Map<String, dynamic> json)
35+
: orientation = deserializeDeviceOrientation(json['orientation']);
36+
37+
/// Converts the [DeviceOrientationChangedEvent] instance into a [Map] instance that
38+
/// can be serialized to JSON.
39+
Map<String, dynamic> toJson() => {
40+
'orientation': serializeDeviceOrientation(orientation),
41+
};
42+
43+
@override
44+
bool operator ==(Object other) =>
45+
identical(this, other) ||
46+
other is DeviceOrientationChangedEvent &&
47+
runtimeType == other.runtimeType &&
48+
orientation == other.orientation;
49+
50+
@override
51+
int get hashCode => orientation.hashCode;
52+
}

packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66
import 'dart:math';
77

88
import 'package:camera_platform_interface/camera_platform_interface.dart';
9+
import 'package:camera_platform_interface/src/events/device_event.dart';
910
import 'package:camera_platform_interface/src/types/focus_mode.dart';
1011
import 'package:camera_platform_interface/src/types/image_format_group.dart';
1112
import 'package:camera_platform_interface/src/utils/utils.dart';
@@ -22,7 +23,7 @@ class MethodChannelCamera extends CameraPlatform {
2223
final Map<int, MethodChannel> _channels = {};
2324

2425
/// The controller we need to broadcast the different events coming
25-
/// from handleMethodCall.
26+
/// from handleMethodCall, specific to camera events.
2627
///
2728
/// It is a `broadcast` because multiple controllers will connect to
2829
/// different stream views of this Controller.
@@ -32,10 +33,28 @@ class MethodChannelCamera extends CameraPlatform {
3233
final StreamController<CameraEvent> cameraEventStreamController =
3334
StreamController<CameraEvent>.broadcast();
3435

35-
Stream<CameraEvent> _events(int cameraId) =>
36+
/// The controller we need to broadcast the different events coming
37+
/// from handleMethodCall, specific to general device events.
38+
///
39+
/// It is a `broadcast` because multiple controllers will connect to
40+
/// different stream views of this Controller.
41+
/// This is only exposed for test purposes. It shouldn't be used by clients of
42+
/// the plugin as it may break or change at any time.
43+
@visibleForTesting
44+
final StreamController<DeviceEvent> deviceEventStreamController =
45+
StreamController<DeviceEvent>.broadcast();
46+
47+
Stream<CameraEvent> _cameraEvents(int cameraId) =>
3648
cameraEventStreamController.stream
3749
.where((event) => event.cameraId == cameraId);
3850

51+
/// Construct a new method channel camera instance.
52+
MethodChannelCamera() {
53+
final channel = MethodChannel('flutter.io/cameraPlugin/device');
54+
channel.setMethodCallHandler(
55+
(MethodCall call) => handleDeviceMethodCall(call));
56+
}
57+
3958
@override
4059
Future<List<CameraDescription>> availableCameras() async {
4160
try {
@@ -83,7 +102,7 @@ class MethodChannelCamera extends CameraPlatform {
83102
_channels.putIfAbsent(cameraId, () {
84103
final channel = MethodChannel('flutter.io/cameraPlugin/camera$cameraId');
85104
channel.setMethodCallHandler(
86-
(MethodCall call) => handleMethodCall(call, cameraId));
105+
(MethodCall call) => handleCameraMethodCall(call, cameraId));
87106
return channel;
88107
});
89108

@@ -119,22 +138,48 @@ class MethodChannelCamera extends CameraPlatform {
119138

120139
@override
121140
Stream<CameraInitializedEvent> onCameraInitialized(int cameraId) {
122-
return _events(cameraId).whereType<CameraInitializedEvent>();
141+
return _cameraEvents(cameraId).whereType<CameraInitializedEvent>();
123142
}
124143

125144
@override
126145
Stream<CameraResolutionChangedEvent> onCameraResolutionChanged(int cameraId) {
127-
return _events(cameraId).whereType<CameraResolutionChangedEvent>();
146+
return _cameraEvents(cameraId).whereType<CameraResolutionChangedEvent>();
128147
}
129148

130149
@override
131150
Stream<CameraClosingEvent> onCameraClosing(int cameraId) {
132-
return _events(cameraId).whereType<CameraClosingEvent>();
151+
return _cameraEvents(cameraId).whereType<CameraClosingEvent>();
133152
}
134153

135154
@override
136155
Stream<CameraErrorEvent> onCameraError(int cameraId) {
137-
return _events(cameraId).whereType<CameraErrorEvent>();
156+
return _cameraEvents(cameraId).whereType<CameraErrorEvent>();
157+
}
158+
159+
@override
160+
Stream<DeviceOrientationChangedEvent> onDeviceOrientationChanged() {
161+
return deviceEventStreamController.stream
162+
.whereType<DeviceOrientationChangedEvent>();
163+
}
164+
165+
@override
166+
Future<void> lockCaptureOrientation(
167+
int cameraId, DeviceOrientation orientation) async {
168+
await _channel.invokeMethod<String>(
169+
'lockCaptureOrientation',
170+
<String, dynamic>{
171+
'cameraId': cameraId,
172+
'orientation': serializeDeviceOrientation(orientation)
173+
},
174+
);
175+
}
176+
177+
@override
178+
Future<void> unlockCaptureOrientation(int cameraId) async {
179+
await _channel.invokeMethod<String>(
180+
'unlockCaptureOrientation',
181+
<String, dynamic>{'cameraId': cameraId},
182+
);
138183
}
139184

140185
@override
@@ -343,12 +388,27 @@ class MethodChannelCamera extends CameraPlatform {
343388
}
344389
}
345390

346-
/// Converts messages received from the native platform into events.
391+
/// Converts messages received from the native platform into device events.
392+
///
393+
/// This is only exposed for test purposes. It shouldn't be used by clients of
394+
/// the plugin as it may break or change at any time.
395+
Future<dynamic> handleDeviceMethodCall(MethodCall call) async {
396+
switch (call.method) {
397+
case 'orientation_changed':
398+
deviceEventStreamController.add(DeviceOrientationChangedEvent(
399+
deserializeDeviceOrientation(call.arguments['orientation'])));
400+
break;
401+
default:
402+
throw MissingPluginException();
403+
}
404+
}
405+
406+
/// Converts messages received from the native platform into camera events.
347407
///
348408
/// This is only exposed for test purposes. It shouldn't be used by clients of
349409
/// the plugin as it may break or change at any time.
350410
@visibleForTesting
351-
Future<dynamic> handleMethodCall(MethodCall call, int cameraId) async {
411+
Future<dynamic> handleCameraMethodCall(MethodCall call, int cameraId) async {
352412
switch (call.method) {
353413
case 'initialized':
354414
cameraEventStreamController.add(CameraInitializedEvent(

packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import 'dart:async';
66
import 'dart:math';
77

88
import 'package:camera_platform_interface/camera_platform_interface.dart';
9+
import 'package:camera_platform_interface/src/events/device_event.dart';
910
import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart';
1011
import 'package:camera_platform_interface/src/types/exposure_mode.dart';
1112
import 'package:camera_platform_interface/src/types/focus_mode.dart';
1213
import 'package:camera_platform_interface/src/types/image_format_group.dart';
1314
import 'package:cross_file/cross_file.dart';
15+
import 'package:flutter/services.dart';
1416
import 'package:flutter/widgets.dart';
1517
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
1618

@@ -85,6 +87,27 @@ abstract class CameraPlatform extends PlatformInterface {
8587
throw UnimplementedError('onCameraError() is not implemented.');
8688
}
8789

90+
/// The device orientation changed.
91+
///
92+
/// Implementations for this:
93+
/// - Should support all 4 orientations.
94+
/// - Should not emit new values when the screen orientation is locked.
95+
Stream<DeviceOrientationChangedEvent> onDeviceOrientationChanged() {
96+
throw UnimplementedError(
97+
'onDeviceOrientationChanged() is not implemented.');
98+
}
99+
100+
/// Locks the capture orientation.
101+
Future<void> lockCaptureOrientation(
102+
int cameraId, DeviceOrientation orientation) {
103+
throw UnimplementedError('lockCaptureOrientation() is not implemented.');
104+
}
105+
106+
/// Unlocks the capture orientation.
107+
Future<void> unlockCaptureOrientation(int cameraId) {
108+
throw UnimplementedError('unlockCaptureOrientation() is not implemented.');
109+
}
110+
88111
/// Captures an image and returns the file where it was saved.
89112
Future<XFile> takePicture(int cameraId) {
90113
throw UnimplementedError('takePicture() is not implemented.');

packages/camera/camera_platform_interface/lib/src/utils/utils.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:camera_platform_interface/camera_platform_interface.dart';
2+
import 'package:flutter/services.dart';
23

34
/// Parses a string into a corresponding CameraLensDirection.
45
CameraLensDirection parseCameraLensDirection(String string) {
@@ -12,3 +13,35 @@ CameraLensDirection parseCameraLensDirection(String string) {
1213
}
1314
throw ArgumentError('Unknown CameraLensDirection value');
1415
}
16+
17+
/// Returns the device orientation as a String.
18+
String serializeDeviceOrientation(DeviceOrientation orientation) {
19+
switch (orientation) {
20+
case DeviceOrientation.portraitUp:
21+
return 'portraitUp';
22+
case DeviceOrientation.portraitDown:
23+
return 'portraitDown';
24+
case DeviceOrientation.landscapeRight:
25+
return 'landscapeRight';
26+
case DeviceOrientation.landscapeLeft:
27+
return 'landscapeLeft';
28+
default:
29+
throw ArgumentError('Unknown DeviceOrientation value');
30+
}
31+
}
32+
33+
/// Returns the device orientation for a given String.
34+
DeviceOrientation deserializeDeviceOrientation(String str) {
35+
switch (str) {
36+
case "portraitUp":
37+
return DeviceOrientation.portraitUp;
38+
case "portraitDown":
39+
return DeviceOrientation.portraitDown;
40+
case "landscapeRight":
41+
return DeviceOrientation.landscapeRight;
42+
case "landscapeLeft":
43+
return DeviceOrientation.landscapeLeft;
44+
default:
45+
throw ArgumentError('"$str" is not a valid DeviceOrientation value');
46+
}
47+
}

packages/camera/camera_platform_interface/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ description: A common platform interface for the camera plugin.
33
homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera_platform_interface
44
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
55
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
6-
version: 1.4.0
6+
version: 1.5.0
77

88
dependencies:
99
flutter:

packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,45 @@ void main() {
9696
);
9797
});
9898

99+
test(
100+
'Default implementation of onDeviceOrientationChanged() should throw unimplemented error',
101+
() {
102+
// Arrange
103+
final cameraPlatform = ExtendsCameraPlatform();
104+
105+
// Act & Assert
106+
expect(
107+
() => cameraPlatform.onDeviceOrientationChanged(),
108+
throwsUnimplementedError,
109+
);
110+
});
111+
112+
test(
113+
'Default implementation of lockCaptureOrientation() should throw unimplemented error',
114+
() {
115+
// Arrange
116+
final cameraPlatform = ExtendsCameraPlatform();
117+
118+
// Act & Assert
119+
expect(
120+
() => cameraPlatform.lockCaptureOrientation(1, null),
121+
throwsUnimplementedError,
122+
);
123+
});
124+
125+
test(
126+
'Default implementation of unlockCaptureOrientation() should throw unimplemented error',
127+
() {
128+
// Arrange
129+
final cameraPlatform = ExtendsCameraPlatform();
130+
131+
// Act & Assert
132+
expect(
133+
() => cameraPlatform.unlockCaptureOrientation(1),
134+
throwsUnimplementedError,
135+
);
136+
});
137+
99138
test('Default implementation of dispose() should throw unimplemented error',
100139
() {
101140
// Arrange

0 commit comments

Comments
 (0)