diff --git a/ios/camerawesome/Sources/camerawesome/CameraPreview/SingleCameraPreview/SingleCameraPreview.m b/ios/camerawesome/Sources/camerawesome/CameraPreview/SingleCameraPreview/SingleCameraPreview.m index 7f49e8ed..fbeeae4c 100644 --- a/ios/camerawesome/Sources/camerawesome/CameraPreview/SingleCameraPreview/SingleCameraPreview.m +++ b/ios/camerawesome/Sources/camerawesome/CameraPreview/SingleCameraPreview/SingleCameraPreview.m @@ -66,7 +66,16 @@ - (instancetype)initWithCameraSensor:(PigeonSensorPosition)sensor _physicalButtonController = [[PhysicalButtonController alloc] init]; [_motionController startMotionDetection]; - + + // Keep the capture connection locked to portrait so the preview texture + // never rotates with the device — mimics the native iOS Camera app. + __weak typeof(self) weakSelf = self; + _motionController.onOrientationChanged = ^(UIDeviceOrientation newOrientation) { + if (weakSelf.captureConnection.isVideoOrientationSupported) { + [weakSelf.captureConnection setVideoOrientation:AVCaptureVideoOrientationPortrait]; + } + }; + if (enablePhysicalButton) { [_physicalButtonController startListening]; } diff --git a/ios/camerawesome/Sources/camerawesome/Controllers/Motion/MotionController.m b/ios/camerawesome/Sources/camerawesome/Controllers/Motion/MotionController.m index 3dc83122..a90df1a6 100644 --- a/ios/camerawesome/Sources/camerawesome/Controllers/Motion/MotionController.m +++ b/ios/camerawesome/Sources/camerawesome/Controllers/Motion/MotionController.m @@ -34,7 +34,12 @@ - (void)startMotionDetection { } if (self->_deviceOrientation != newOrientation) { self->_deviceOrientation = newOrientation; - + + // Notify camera controller to update capture connection orientation + if (self->_onOrientationChanged) { + self->_onOrientationChanged(newOrientation); + } + NSString *orientationString; switch (newOrientation) { case UIDeviceOrientationLandscapeLeft: diff --git a/ios/camerawesome/Sources/camerawesome/include/MotionController.h b/ios/camerawesome/Sources/camerawesome/include/MotionController.h index d7b81e13..e74075d0 100644 --- a/ios/camerawesome/Sources/camerawesome/include/MotionController.h +++ b/ios/camerawesome/Sources/camerawesome/include/MotionController.h @@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic) FlutterEventSink orientationEventSink; @property(readonly, nonatomic) UIDeviceOrientation deviceOrientation; @property(readonly, nonatomic) CMMotionManager *motionManager; +@property(nonatomic, copy, nullable) void (^onOrientationChanged)(UIDeviceOrientation newOrientation); - (instancetype)init; - (void)startMotionDetection; diff --git a/lib/src/widgets/camera_awesome_builder.dart b/lib/src/widgets/camera_awesome_builder.dart index e614f4da..110d4a85 100644 --- a/lib/src/widgets/camera_awesome_builder.dart +++ b/lib/src/widgets/camera_awesome_builder.dart @@ -280,8 +280,7 @@ class CameraAwesomeBuilder extends StatefulWidget { Alignment previewAlignment = Alignment.center, PictureInPictureConfigBuilder? pictureInPictureConfigBuilder, }) : this._( - sensorConfig: sensorConfig ?? - SensorConfig.single(sensor: Sensor.position(SensorPosition.back)), + sensorConfig: sensorConfig ?? SensorConfig.single(sensor: Sensor.position(SensorPosition.back)), enablePhysicalButton: false, progressIndicator: progressIndicator, builder: builder, @@ -315,8 +314,7 @@ class CameraAwesomeBuilder extends StatefulWidget { required OnImageForAnalysis onImageForAnalysis, AnalysisConfig? imageAnalysisConfig, }) : this._( - sensorConfig: sensorConfig ?? - SensorConfig.single(sensor: Sensor.position(SensorPosition.back)), + sensorConfig: sensorConfig ?? SensorConfig.single(sensor: Sensor.position(SensorPosition.back)), enablePhysicalButton: false, progressIndicator: progressIndicator, builder: builder, @@ -342,8 +340,7 @@ class CameraAwesomeBuilder extends StatefulWidget { } } -class _CameraWidgetBuilder extends State - with WidgetsBindingObserver { +class _CameraWidgetBuilder extends State with WidgetsBindingObserver { late CameraContext _cameraContext; final _cameraPreviewKey = GlobalKey(); StreamSubscription? _captureStateListener; @@ -394,15 +391,11 @@ class _CameraWidgetBuilder extends State widget.sensorConfig, enablePhysicalButton: widget.enablePhysicalButton, filter: widget.defaultFilter ?? AwesomeFilter.None, - initialCaptureMode: widget.saveConfig?.initialCaptureMode ?? - (widget.showPreview - ? CaptureMode.preview - : CaptureMode.analysis_only), + initialCaptureMode: widget.saveConfig?.initialCaptureMode ?? (widget.showPreview ? CaptureMode.preview : CaptureMode.analysis_only), saveConfig: widget.saveConfig, onImageForAnalysis: widget.onImageForAnalysis, analysisConfig: widget.imageAnalysisConfig, - exifPreferences: widget.saveConfig?.exifPreferences ?? - ExifPreferences(saveGPSLocation: false), + exifPreferences: widget.saveConfig?.exifPreferences ?? ExifPreferences(saveGPSLocation: false), availableFilters: widget.availableFilters, ); @@ -423,9 +416,7 @@ class _CameraWidgetBuilder extends State child: StreamBuilder( stream: _cameraContext.state$, builder: (context, snapshot) { - if (!snapshot.hasData || - snapshot.data!.captureMode == null || - snapshot.requireData is PreparingCameraState) { + if (!snapshot.hasData || snapshot.data!.captureMode == null || snapshot.requireData is PreparingCameraState) { return widget.progressIndicator ?? const Center( child: CircularProgressIndicator.adaptive(), @@ -446,8 +437,7 @@ class _CameraWidgetBuilder extends State state: snapshot.requireData, padding: widget.previewPadding, alignment: widget.previewAlignment, - onPreviewTap: widget.onPreviewTapBuilder - ?.call(snapshot.requireData) ?? + onPreviewTap: widget.onPreviewTapBuilder?.call(snapshot.requireData) ?? OnPreviewTap( onTap: ( position, @@ -455,26 +445,22 @@ class _CameraWidgetBuilder extends State pixelPreviewSize, ) { snapshot.requireData.when( - onPhotoMode: (photoState) => - photoState.focusOnPoint( + onPhotoMode: (photoState) => photoState.focusOnPoint( flutterPosition: position, pixelPreviewSize: pixelPreviewSize, flutterPreviewSize: flutterPreviewSize, ), - onVideoMode: (videoState) => - videoState.focusOnPoint( + onVideoMode: (videoState) => videoState.focusOnPoint( flutterPosition: position, pixelPreviewSize: pixelPreviewSize, flutterPreviewSize: flutterPreviewSize, ), - onVideoRecordingMode: (videoRecState) => - videoRecState.focusOnPoint( + onVideoRecordingMode: (videoRecState) => videoRecState.focusOnPoint( flutterPosition: position, pixelPreviewSize: pixelPreviewSize, flutterPreviewSize: flutterPreviewSize, ), - onPreviewMode: (previewState) => - previewState.focusOnPoint( + onPreviewMode: (previewState) => previewState.focusOnPoint( flutterPosition: position, pixelPreviewSize: pixelPreviewSize, flutterPreviewSize: flutterPreviewSize, @@ -482,18 +468,15 @@ class _CameraWidgetBuilder extends State ); }, ), - onPreviewScale: widget.onPreviewScaleBuilder - ?.call(snapshot.requireData) ?? + onPreviewScale: widget.onPreviewScaleBuilder?.call(snapshot.requireData) ?? OnPreviewScale( onScale: (scale) { - snapshot.requireData.sensorConfig - .setZoom(scale); + snapshot.requireData.sensorConfig.setZoom(scale); }, ), interfaceBuilder: widget.builder, previewDecoratorBuilder: widget.previewDecoratorBuilder, - pictureInPictureConfigBuilder: - widget.pictureInPictureConfigBuilder, + pictureInPictureConfigBuilder: widget.pictureInPictureConfigBuilder, ), ), ], diff --git a/lib/src/widgets/preview/awesome_camera_preview.dart b/lib/src/widgets/preview/awesome_camera_preview.dart index 0fac6711..c4d273a6 100644 --- a/lib/src/widgets/preview/awesome_camera_preview.dart +++ b/lib/src/widgets/preview/awesome_camera_preview.dart @@ -153,6 +153,11 @@ class AwesomeCameraPreviewState extends State { ); } + // Don't rotate the camera preview texture when the device rotates — + // keep it stable like the native iOS Camera app. + const quarterTurns = 0; + final effectivePreviewSize = _previewSize!; + return Container( color: Colors.black, child: LayoutBuilder( @@ -163,7 +168,7 @@ class AwesomeCameraPreviewState extends State { child: AnimatedPreviewFit( alignment: widget.alignment, previewFit: widget.previewFit, - previewSize: _previewSize!, + previewSize: effectivePreviewSize, previewPadding: widget.padding, constraints: constraints, sensor: widget.state.sensorConfig.sensors.first, @@ -192,13 +197,16 @@ class AwesomeCameraPreviewState extends State { //FIX performances stream: widget.state.filter$, builder: (context, snapshot) { + final texture = quarterTurns != 0 + ? RotatedBox(quarterTurns: quarterTurns, child: _textures.first) + : _textures.first; return snapshot.hasData && snapshot.data != AwesomeFilter.None ? ColorFiltered( colorFilter: snapshot.data!.preview, - child: _textures.first, + child: texture, ) - : _textures.first; + : texture; }, ), ), diff --git a/lib/src/widgets/preview/awesome_preview_fit.dart b/lib/src/widgets/preview/awesome_preview_fit.dart index 27319b50..0c087f0a 100644 --- a/lib/src/widgets/preview/awesome_preview_fit.dart +++ b/lib/src/widgets/preview/awesome_preview_fit.dart @@ -152,6 +152,7 @@ class PreviewFitWidget extends StatelessWidget { return Align( alignment: alignment, child: SizedBox( + width: previewSize.width * scale, height: previewSize.height * scale, child: Padding( padding: previewPadding ?? EdgeInsets.zero,