diff --git a/CHANGELOG.md b/CHANGELOG.md index 2176ba7..a87b367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 6fbf4cd..502d96d 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -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: diff --git a/lib/src/states/camera_picker_state.dart b/lib/src/states/camera_picker_state.dart index aacc51e..82e059f 100644 --- a/lib/src/states/camera_picker_state.dart +++ b/lib/src/states/camera_picker_state.dart @@ -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'; @@ -208,6 +209,12 @@ class CameraPickerState extends State final invalidControllerMethods = >{}; bool retriedAfterInvalidInitialize = false; + /// Subscribe to the accelerometer. + late final StreamSubscription accelerometerSubscription; + + /// The locked capture orientation of the current camera instance. + DeviceOrientation? lockedCaptureOrientation; + @override void initState() { super.initState(); @@ -215,12 +222,17 @@ class CameraPickerState extends State 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(); @@ -231,6 +243,7 @@ class CameraPickerState extends State exposureFadeOutTimer?.cancel(); recordDetectTimer?.cancel(); recordCountdownTimer?.cancel(); + accelerometerSubscription.cancel(); super.dispose(); } @@ -322,6 +335,7 @@ class CameraPickerState extends State 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. @@ -467,6 +481,43 @@ class CameraPickerState extends State }); } + /// 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] 中初始化闪光灯模式。 @@ -1542,7 +1593,29 @@ class CameraPickerState extends State required CameraValue cameraValue, required BoxConstraints constraints, }) { - Widget preview = Listener( + Widget preview = const SizedBox.shrink(); + if (innerController != null) { + preview = CameraPreview(controller); + preview = ValueListenableBuilder( + 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( @@ -1551,9 +1624,7 @@ class CameraPickerState extends State 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, ), ); @@ -1568,7 +1639,6 @@ class CameraPickerState extends State preview = Stack( children: [ preview, - // Image.asset('assets/1.jpg', fit: BoxFit.cover), Positioned.fill( child: ExcludeSemantics( child: RotatedBox( diff --git a/pubspec.yaml b/pubspec.yaml index 4f2e15d..f91c7f1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 @@ -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: