Skip to content

Commit

Permalink
🚀 Use sensors to determine capture orientation (#218)
Browse files Browse the repository at this point in the history
resolves #208, resolves #210
  • Loading branch information
AlexV525 authored Oct 30, 2023
1 parent dd11736 commit 5370cca
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 9 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ that can be found in the LICENSE file. -->

See the [Migration Guide](guides/migration_guide.md) for the details of breaking changes between versions.

## 4.0.4
## 4.1.0

### New features

- Automatically determine the capture orientation and lock accordingly.

### Fixes

Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: wechat_camera_picker_demo
description: A new Flutter project.
version: 4.0.4+29
version: 4.1.0+30
publish_to: none

environment:
Expand Down
82 changes: 76 additions & 6 deletions lib/src/states/camera_picker_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter/services.dart';
import 'package:sensors_plus/sensors_plus.dart';

import '../constants/config.dart';
import '../constants/constants.dart';
Expand Down Expand Up @@ -208,19 +209,30 @@ class CameraPickerState extends State<CameraPicker>
final invalidControllerMethods = <CameraDescription, Set<String>>{};
bool retriedAfterInvalidInitialize = false;

/// Subscribe to the accelerometer.
late final StreamSubscription<AccelerometerEvent> accelerometerSubscription;

/// The locked capture orientation of the current camera instance.
DeviceOrientation? lockedCaptureOrientation;

@override
void initState() {
super.initState();
ambiguate(WidgetsBinding.instance)?.addObserver(this);
Constants.textDelegate = widget.pickerConfig.textDelegate ??
cameraPickerTextDelegateFromLocale(widget.locale);
initCameras();
accelerometerSubscription = accelerometerEvents.listen(
handleAccelerometerEvent,
);
}

@override
void dispose() {
ambiguate(WidgetsBinding.instance)?.removeObserver(this);
innerController?.dispose();
final c = innerController;
innerController = null;
c?.dispose();
currentExposureOffset.dispose();
currentExposureSliderOffset.dispose();
lastExposurePoint.dispose();
Expand All @@ -231,6 +243,7 @@ class CameraPickerState extends State<CameraPicker>
exposureFadeOutTimer?.cancel();
recordDetectTimer?.cancel();
recordCountdownTimer?.cancel();
accelerometerSubscription.cancel();
super.dispose();
}

Expand Down Expand Up @@ -322,6 +335,7 @@ class CameraPickerState extends State<CameraPicker>
lastExposurePoint.value = null;
currentExposureOffset.value = 0;
currentExposureSliderOffset.value = 0;
lockedCaptureOrientation = pickerConfig.lockCaptureOrientation;
});
// **IMPORTANT**: Push methods into a post frame callback, which ensures the
// controller has already unbind from widgets.
Expand Down Expand Up @@ -467,6 +481,43 @@ class CameraPickerState extends State<CameraPicker>
});
}

/// Lock capture orientation according to the current status of the device,
/// which enables the captured file stored the correct orientation.
void handleAccelerometerEvent(AccelerometerEvent event) {
if (!mounted ||
pickerConfig.lockCaptureOrientation != null ||
innerController == null ||
!controller.value.isInitialized ||
controller.value.isPreviewPaused ||
controller.value.isRecordingVideo ||
controller.value.isTakingPicture) {
return;
}
final x = event.x, y = event.y, z = event.z;
final DeviceOrientation? newOrientation;
if (x.abs() > y.abs() && x.abs() > z.abs()) {
if (x > 0) {
newOrientation = DeviceOrientation.landscapeLeft;
} else {
newOrientation = DeviceOrientation.landscapeRight;
}
} else if (y.abs() > x.abs() && y.abs() > z.abs()) {
if (y > 0) {
newOrientation = DeviceOrientation.portraitUp;
} else {
newOrientation = DeviceOrientation.portraitDown;
}
} else {
newOrientation = null;
}
// Throttle.
if (newOrientation != null && lockedCaptureOrientation != newOrientation) {
lockedCaptureOrientation = newOrientation;
realDebugPrint('Locking new capture orientation: $newOrientation');
controller.lockCaptureOrientation(newOrientation);
}
}

/// Initializes the flash modes in [validFlashModes] for each
/// [CameraDescription].
/// 为每个 [CameraDescription][validFlashModes] 中初始化闪光灯模式。
Expand Down Expand Up @@ -1542,7 +1593,29 @@ class CameraPickerState extends State<CameraPicker>
required CameraValue cameraValue,
required BoxConstraints constraints,
}) {
Widget preview = Listener(
Widget preview = const SizedBox.shrink();
if (innerController != null) {
preview = CameraPreview(controller);
preview = ValueListenableBuilder<CameraValue>(
valueListenable: controller,
builder: (_, CameraValue value, Widget? child) {
final lockedOrientation = value.lockedCaptureOrientation;
int? quarterTurns = lockedOrientation?.index;
if (quarterTurns == null) {
return child!;
}
if (value.deviceOrientation == DeviceOrientation.landscapeLeft) {
quarterTurns--;
} else if (value.deviceOrientation ==
DeviceOrientation.landscapeRight) {
quarterTurns++;
}
return RotatedBox(quarterTurns: quarterTurns, child: child);
},
child: preview,
);
}
preview = Listener(
onPointerDown: (_) => pointers++,
onPointerUp: (_) => pointers--,
child: GestureDetector(
Expand All @@ -1551,9 +1624,7 @@ class CameraPickerState extends State<CameraPicker>
pickerConfig.enablePinchToZoom ? handleScaleUpdate : null,
// Enabled cameras switching by default if we have multiple cameras.
onDoubleTap: cameras.length > 1 ? switchCameras : null,
child: innerController != null
? CameraPreview(controller)
: const SizedBox.shrink(),
child: preview,
),
);

Expand All @@ -1568,7 +1639,6 @@ class CameraPickerState extends State<CameraPicker>
preview = Stack(
children: <Widget>[
preview,
// Image.asset('assets/1.jpg', fit: BoxFit.cover),
Positioned.fill(
child: ExcludeSemantics(
child: RotatedBox(
Expand Down
3 changes: 2 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: wechat_camera_picker
version: 4.0.4
version: 4.1.0
description: |
A camera picker for Flutter projects based on WeChat's UI,
which is also a separate runnable extension to the
Expand All @@ -26,6 +26,7 @@ dependencies:
camera_platform_interface: ^2.1.5
path: ^1.8.0
photo_manager: ^2.7.0
sensors_plus: ^3.1.0
video_player: ^2.7.0

dev_dependencies:
Expand Down

0 comments on commit 5370cca

Please sign in to comment.