Skip to content

Commit

Permalink
[camera_platform_interface] Add platform interface methods for lockin…
Browse files Browse the repository at this point in the history
…g capture orientation. (flutter#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 <maurits@vnbskm.nl>

* Update packages/camera/camera_platform_interface/lib/src/events/device_event.dart

Co-authored-by: Maurits van Beusekom <maurits@vnbskm.nl>

* Update packages/camera/camera_platform_interface/lib/src/events/device_event.dart

Co-authored-by: Maurits van Beusekom <maurits@vnbskm.nl>

* Update packages/camera/camera_platform_interface/lib/src/events/device_event.dart

Co-authored-by: Maurits van Beusekom <maurits@vnbskm.nl>

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

Co-authored-by: Maurits van Beusekom <maurits@vnbskm.nl>

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

Co-authored-by: Maurits van Beusekom <maurits@vnbskm.nl>

Co-authored-by: Maurits van Beusekom <maurits@vnbskm.nl>
Co-authored-by: Maurits van Beusekom <maurits@baseflow.com>
  • Loading branch information
3 people authored and adsonpleal committed Feb 26, 2021
1 parent 892af68 commit 3af7078
Show file tree
Hide file tree
Showing 12 changed files with 384 additions and 23 deletions.
5 changes: 5 additions & 0 deletions packages/camera/camera_platform_interface/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 1.5.0

- Introduces interface methods for locking and unlocking the capture orientation.
- Introduces interface method for listening to the device orientation.

## 1.4.0

- Added interface methods to support auto focus.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

export 'src/events/camera_event.dart';
export 'src/events/device_event.dart';
export 'src/platform_interface/camera_platform.dart';
export 'src/types/types.dart';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import 'package:camera_platform_interface/src/types/focus_mode.dart';

import '../../camera_platform_interface.dart';

/// Generic Event coming from the native side of Camera.
/// Generic Event coming from the native side of Camera,
/// related to a specific camera module.
///
/// All [CameraEvent]s contain the `cameraId` that originated the event. This
/// should never be `null`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:camera_platform_interface/src/utils/utils.dart';
import 'package:flutter/services.dart';

/// Generic Event coming from the native side of Camera,
/// not related to a specific camera module.
///
/// This class is used as a base class for all the events that might be
/// triggered from a device, but it is never used directly as an event type.
///
/// Do NOT instantiate new events like `DeviceEvent()` directly,
/// use a specific class instead:
///
/// Do `class NewEvent extend DeviceEvent` when creating your own events.
/// See below for examples: `DeviceOrientationChangedEvent`...
/// These events are more semantic and more pleasant to use than raw generics.
/// They can be (and in fact, are) filtered by the `instanceof`-operator.
abstract class DeviceEvent {}

/// The [DeviceOrientationChangedEvent] is fired every time the user changes the
/// physical orientation of the device.
class DeviceOrientationChangedEvent extends DeviceEvent {
/// The new orientation of the device
final DeviceOrientation orientation;

/// Build a new orientation changed event.
DeviceOrientationChangedEvent(this.orientation);

/// Converts the supplied [Map] to an instance of the [DeviceOrientationChangedEvent]
/// class.
DeviceOrientationChangedEvent.fromJson(Map<String, dynamic> json)
: orientation = deserializeDeviceOrientation(json['orientation']);

/// Converts the [DeviceOrientationChangedEvent] instance into a [Map] instance that
/// can be serialized to JSON.
Map<String, dynamic> toJson() => {
'orientation': serializeDeviceOrientation(orientation),
};

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is DeviceOrientationChangedEvent &&
runtimeType == other.runtimeType &&
orientation == other.orientation;

@override
int get hashCode => orientation.hashCode;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:math';

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

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

Stream<CameraEvent> _events(int cameraId) =>
/// The controller we need to broadcast the different events coming
/// from handleMethodCall, specific to general device events.
///
/// It is a `broadcast` because multiple controllers will connect to
/// different stream views of this Controller.
/// This is only exposed for test purposes. It shouldn't be used by clients of
/// the plugin as it may break or change at any time.
@visibleForTesting
final StreamController<DeviceEvent> deviceEventStreamController =
StreamController<DeviceEvent>.broadcast();

Stream<CameraEvent> _cameraEvents(int cameraId) =>
cameraEventStreamController.stream
.where((event) => event.cameraId == cameraId);

/// Construct a new method channel camera instance.
MethodChannelCamera() {
final channel = MethodChannel('flutter.io/cameraPlugin/device');
channel.setMethodCallHandler(
(MethodCall call) => handleDeviceMethodCall(call));
}

@override
Future<List<CameraDescription>> availableCameras() async {
try {
Expand Down Expand Up @@ -83,7 +102,7 @@ class MethodChannelCamera extends CameraPlatform {
_channels.putIfAbsent(cameraId, () {
final channel = MethodChannel('flutter.io/cameraPlugin/camera$cameraId');
channel.setMethodCallHandler(
(MethodCall call) => handleMethodCall(call, cameraId));
(MethodCall call) => handleCameraMethodCall(call, cameraId));
return channel;
});

Expand Down Expand Up @@ -119,22 +138,48 @@ class MethodChannelCamera extends CameraPlatform {

@override
Stream<CameraInitializedEvent> onCameraInitialized(int cameraId) {
return _events(cameraId).whereType<CameraInitializedEvent>();
return _cameraEvents(cameraId).whereType<CameraInitializedEvent>();
}

@override
Stream<CameraResolutionChangedEvent> onCameraResolutionChanged(int cameraId) {
return _events(cameraId).whereType<CameraResolutionChangedEvent>();
return _cameraEvents(cameraId).whereType<CameraResolutionChangedEvent>();
}

@override
Stream<CameraClosingEvent> onCameraClosing(int cameraId) {
return _events(cameraId).whereType<CameraClosingEvent>();
return _cameraEvents(cameraId).whereType<CameraClosingEvent>();
}

@override
Stream<CameraErrorEvent> onCameraError(int cameraId) {
return _events(cameraId).whereType<CameraErrorEvent>();
return _cameraEvents(cameraId).whereType<CameraErrorEvent>();
}

@override
Stream<DeviceOrientationChangedEvent> onDeviceOrientationChanged() {
return deviceEventStreamController.stream
.whereType<DeviceOrientationChangedEvent>();
}

@override
Future<void> lockCaptureOrientation(
int cameraId, DeviceOrientation orientation) async {
await _channel.invokeMethod<String>(
'lockCaptureOrientation',
<String, dynamic>{
'cameraId': cameraId,
'orientation': serializeDeviceOrientation(orientation)
},
);
}

@override
Future<void> unlockCaptureOrientation(int cameraId) async {
await _channel.invokeMethod<String>(
'unlockCaptureOrientation',
<String, dynamic>{'cameraId': cameraId},
);
}

@override
Expand Down Expand Up @@ -343,12 +388,27 @@ class MethodChannelCamera extends CameraPlatform {
}
}

/// Converts messages received from the native platform into events.
/// Converts messages received from the native platform into device events.
///
/// This is only exposed for test purposes. It shouldn't be used by clients of
/// the plugin as it may break or change at any time.
Future<dynamic> handleDeviceMethodCall(MethodCall call) async {
switch (call.method) {
case 'orientation_changed':
deviceEventStreamController.add(DeviceOrientationChangedEvent(
deserializeDeviceOrientation(call.arguments['orientation'])));
break;
default:
throw MissingPluginException();
}
}

/// Converts messages received from the native platform into camera events.
///
/// This is only exposed for test purposes. It shouldn't be used by clients of
/// the plugin as it may break or change at any time.
@visibleForTesting
Future<dynamic> handleMethodCall(MethodCall call, int cameraId) async {
Future<dynamic> handleCameraMethodCall(MethodCall call, int cameraId) async {
switch (call.method) {
case 'initialized':
cameraEventStreamController.add(CameraInitializedEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import 'dart:async';
import 'dart:math';

import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:camera_platform_interface/src/events/device_event.dart';
import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart';
import 'package:camera_platform_interface/src/types/exposure_mode.dart';
import 'package:camera_platform_interface/src/types/focus_mode.dart';
import 'package:camera_platform_interface/src/types/image_format_group.dart';
import 'package:cross_file/cross_file.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';

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

/// The device orientation changed.
///
/// Implementations for this:
/// - Should support all 4 orientations.
/// - Should not emit new values when the screen orientation is locked.
Stream<DeviceOrientationChangedEvent> onDeviceOrientationChanged() {
throw UnimplementedError(
'onDeviceOrientationChanged() is not implemented.');
}

/// Locks the capture orientation.
Future<void> lockCaptureOrientation(
int cameraId, DeviceOrientation orientation) {
throw UnimplementedError('lockCaptureOrientation() is not implemented.');
}

/// Unlocks the capture orientation.
Future<void> unlockCaptureOrientation(int cameraId) {
throw UnimplementedError('unlockCaptureOrientation() is not implemented.');
}

/// Captures an image and returns the file where it was saved.
Future<XFile> takePicture(int cameraId) {
throw UnimplementedError('takePicture() is not implemented.');
Expand Down
33 changes: 33 additions & 0 deletions packages/camera/camera_platform_interface/lib/src/utils/utils.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:flutter/services.dart';

/// Parses a string into a corresponding CameraLensDirection.
CameraLensDirection parseCameraLensDirection(String string) {
Expand All @@ -12,3 +13,35 @@ CameraLensDirection parseCameraLensDirection(String string) {
}
throw ArgumentError('Unknown CameraLensDirection value');
}

/// Returns the device orientation as a String.
String serializeDeviceOrientation(DeviceOrientation orientation) {
switch (orientation) {
case DeviceOrientation.portraitUp:
return 'portraitUp';
case DeviceOrientation.portraitDown:
return 'portraitDown';
case DeviceOrientation.landscapeRight:
return 'landscapeRight';
case DeviceOrientation.landscapeLeft:
return 'landscapeLeft';
default:
throw ArgumentError('Unknown DeviceOrientation value');
}
}

/// Returns the device orientation for a given String.
DeviceOrientation deserializeDeviceOrientation(String str) {
switch (str) {
case "portraitUp":
return DeviceOrientation.portraitUp;
case "portraitDown":
return DeviceOrientation.portraitDown;
case "landscapeRight":
return DeviceOrientation.landscapeRight;
case "landscapeLeft":
return DeviceOrientation.landscapeLeft;
default:
throw ArgumentError('"$str" is not a valid DeviceOrientation value');
}
}
2 changes: 1 addition & 1 deletion packages/camera/camera_platform_interface/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: A common platform interface for the camera plugin.
homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera_platform_interface
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
version: 1.4.0
version: 1.5.0

dependencies:
flutter:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,45 @@ void main() {
);
});

test(
'Default implementation of onDeviceOrientationChanged() should throw unimplemented error',
() {
// Arrange
final cameraPlatform = ExtendsCameraPlatform();

// Act & Assert
expect(
() => cameraPlatform.onDeviceOrientationChanged(),
throwsUnimplementedError,
);
});

test(
'Default implementation of lockCaptureOrientation() should throw unimplemented error',
() {
// Arrange
final cameraPlatform = ExtendsCameraPlatform();

// Act & Assert
expect(
() => cameraPlatform.lockCaptureOrientation(1, null),
throwsUnimplementedError,
);
});

test(
'Default implementation of unlockCaptureOrientation() should throw unimplemented error',
() {
// Arrange
final cameraPlatform = ExtendsCameraPlatform();

// Act & Assert
expect(
() => cameraPlatform.unlockCaptureOrientation(1),
throwsUnimplementedError,
);
});

test('Default implementation of dispose() should throw unimplemented error',
() {
// Arrange
Expand Down
Loading

0 comments on commit 3af7078

Please sign in to comment.