From 1f2ece4dadd56124bb3a794dfdab897e457991cf Mon Sep 17 00:00:00 2001 From: Alex Li Date: Wed, 26 Jun 2024 09:36:19 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B8=20Improve=20the=20overall=20experi?= =?UTF-8?q?ences=20(#258)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 9 +++ README-ZH.md | 2 +- README.md | 2 +- example/lib/main.dart | 17 ++++ example/pubspec.yaml | 2 +- lib/src/constants/config.dart | 2 +- lib/src/states/camera_picker_state.dart | 78 +++++++++---------- .../states/camera_picker_viewer_state.dart | 6 +- pubspec.yaml | 2 +- 9 files changed, 70 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8fef20..f99b40d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ that can be found in the LICENSE file. --> See the [Migration Guide](guides/migration_guide.md) for breaking changes between versions. +## 4.3.1 + +### Improvements + +- Downgrades the default resolution preset from `max` to `ultraHigh`. +- Improves pinch zooming experiences. +- Do not wait for focus mode and exposure mode to reset. +- Updates the capture actions section size to compatible with more cases. + ## 4.3.0+1 ### Fixes diff --git a/README-ZH.md b/README-ZH.md index b83ef7d..d7fef4e 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -195,7 +195,7 @@ final AssetEntity? entity = await CameraPicker.pickFromCamera( | minimumRecordingDuration | `Duration` | 录制视频最短时长 | `const Duration(seconds: 1)` | | theme | `ThemeData?` | 选择器的主题 | `CameraPicker.themeData(wechatThemeColor)` | | textDelegate | `CameraPickerTextDelegate?` | 控制部件中的文字实现 | `CameraPickerTextDelegate` | -| resolutionPreset | `ResolutionPreset` | 相机的分辨率预设 | `ResolutionPreset.max` | +| resolutionPreset | `ResolutionPreset` | 相机的分辨率预设 | `ResolutionPreset.ultraHigh` | | cameraQuarterTurns | `int` | 摄像机视图顺时针旋转次数,每次 90 度 | `0` | | imageFormatGroup | `ImageFormatGroup` | 输出图像的格式描述 | `ImageFormatGroup.unknown` | | preferredLensDirection | `CameraLensDirection` | 首次使用相机时首选的镜头方向 | `CameraLensDirection.back` | diff --git a/README.md b/README.md index 0896918..bcf7e6d 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ Fields in `CameraPickerConfig`: | minimumRecordingDuration | `Duration` | The minimum duration of the video recording process. | `const Duration(seconds: 1)` | | theme | `ThemeData?` | Theme data for the picker. | `CameraPicker.themeData(wechatThemeColor)` | | textDelegate | `CameraPickerTextDelegate?` | Text delegate that controls text in widgets. | `CameraPickerTextDelegate` | -| resolutionPreset | `ResolutionPreset` | Present resolution for the camera. | `ResolutionPreset.max` | +| resolutionPreset | `ResolutionPreset` | Present resolution for the camera. | `ResolutionPreset.ultraHigh` | | cameraQuarterTurns | `int` | The number of clockwise quarter turns the camera view should be rotated. | `0` | | imageFormatGroup | `ImageFormatGroup` | Describes the output of the raw image format. | `ImageFormatGroup.unknown` | | preferredLensDirection | `CameraLensDirection` | Which lens direction is preferred when first using the camera. | `CameraLensDirection.back` | diff --git a/example/lib/main.dart b/example/lib/main.dart index 4aa6980..9a928b0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -12,6 +12,10 @@ import 'pages/splash_page.dart'; const Color themeColor = Color(0xff00bc56); +/// The mock size is used for integration tests. +/// Changing this requires at least a hot-restart. +const Size? mockSize = null; + String? packageVersion; void main() { @@ -45,6 +49,19 @@ class MyApp extends StatelessWidget { home: const SplashPage(), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, + builder: (context, child) { + if (mockSize == null) { + return child!; + } + final mq = MediaQuery.of(context).copyWith(size: mockSize); + return MediaQuery( + data: mq, + child: Align( + alignment: Alignment.topCenter, + child: SizedBox.fromSize(size: mockSize, child: child), + ), + ); + }, ); } } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index eec4d0c..5f597dc 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.3.0+36 +version: 4.3.1+37 publish_to: none environment: diff --git a/lib/src/constants/config.dart b/lib/src/constants/config.dart index dfdcd45..9095e18 100644 --- a/lib/src/constants/config.dart +++ b/lib/src/constants/config.dart @@ -31,7 +31,7 @@ final class CameraPickerConfig { this.theme, this.textDelegate, this.cameraQuarterTurns = 0, - this.resolutionPreset = ResolutionPreset.max, + this.resolutionPreset = ResolutionPreset.ultraHigh, this.imageFormatGroup = ImageFormatGroup.unknown, this.preferredLensDirection = CameraLensDirection.back, this.preferredFlashMode = FlashMode.off, diff --git a/lib/src/states/camera_picker_state.dart b/lib/src/states/camera_picker_state.dart index 6896eea..e42f636 100644 --- a/lib/src/states/camera_picker_state.dart +++ b/lib/src/states/camera_picker_state.dart @@ -232,6 +232,9 @@ class CameraPickerState extends State /// The locked capture orientation of the current camera instance. DeviceOrientation? lockedCaptureOrientation; + /// The calculated capture actions section height. + double? lastCaptureActionsEffectiveHeight; + @override void initState() { super.initState(); @@ -684,10 +687,10 @@ class CameraPickerState extends State /// 处理双指缩放更新 Future handleScaleUpdate(ScaleUpdateDetails details) async { // When there are not exactly two fingers on screen don't scale - if (pointers != 2) { + if (innerController == null || pointers != 2) { return; } - zoom(details.scale); + zoom(details.scale * 2 - 1); } void restartExposurePointDisplayTimer() { @@ -926,17 +929,16 @@ class CameraPickerState extends State Navigator.of(context).pop(entity); return; } - await Future.wait(>[ + wrapControllerMethod( + 'setFocusMode', + () => controller.setFocusMode(FocusMode.auto), + ); + if (previousExposureMode != ExposureMode.locked) { wrapControllerMethod( - 'setFocusMode', - () => controller.setFocusMode(FocusMode.auto), - ), - if (previousExposureMode != ExposureMode.locked) - wrapControllerMethod( - 'setExposureMode', - () => controller.setExposureMode(previousExposureMode), - ), - ]); + 'setExposureMode', + () => controller.setExposureMode(previousExposureMode), + ); + } await controller.resumePreview(); } catch (e, s) { handleErrorWithHandler(e, s, pickerConfig.onError); @@ -1283,8 +1285,9 @@ class CameraPickerState extends State return AnimatedOpacity( duration: recordDetectDuration, opacity: controller?.value.isRecordingVideo ?? false ? 0 : 1, - child: Padding( - padding: const EdgeInsets.all(20), + child: Container( + height: 48.0, + alignment: Alignment.center, child: Text( tips, style: const TextStyle(fontSize: 15), @@ -1304,13 +1307,15 @@ class CameraPickerState extends State required BoxConstraints constraints, CameraController? controller, }) { - const fallbackSize = 184.0; + const fallbackSize = 150.0; final previewSize = controller?.value.previewSize; final orientation = controller?.value.deviceOrientation ?? MediaQuery.orientationOf(context); final isPortrait = orientation.toString().contains('portrait'); double effectiveSize; - if (previewSize != null) { + if (controller == null || pickerConfig.enableScaledPreview) { + effectiveSize = lastCaptureActionsEffectiveHeight ?? fallbackSize; + } else if (previewSize != null) { Size constraintSize = Size(constraints.maxWidth, constraints.maxHeight); if (isPortrait && constraintSize.aspectRatio > 1 || !isPortrait && constraintSize.aspectRatio < 1) { @@ -1323,9 +1328,11 @@ class CameraPickerState extends State effectiveSize = constraintSize.width - constraintSize.height * previewSize.aspectRatio; } + } else if (lastCaptureActionsEffectiveHeight != null) { + effectiveSize = lastCaptureActionsEffectiveHeight!; } else { // Fallback to a reasonable height. - effectiveSize = 184.0; + effectiveSize = fallbackSize; } if (effectiveSize <= 0) { realDebugPrint( @@ -1334,11 +1341,14 @@ class CameraPickerState extends State 'orientation: $orientation', ); effectiveSize = fallbackSize; + } else if (effectiveSize < fallbackSize) { + effectiveSize = fallbackSize; } - - return SizedBox( + lastCaptureActionsEffectiveHeight = effectiveSize; + return Container( width: isPortrait ? null : effectiveSize, height: isPortrait ? effectiveSize : null, + padding: EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom), child: Flex( direction: isPortrait ? Axis.horizontal : Axis.vertical, verticalDirection: orientation == DeviceOrientation.landscapeLeft @@ -1670,8 +1680,8 @@ class CameraPickerState extends State image: true, onTap: () { // Focus on the center point when using semantics tap. - final Size size = MediaQuery.of(context).size; - final TapUpDetails details = TapUpDetails( + final size = MediaQuery.sizeOf(context); + final details = TapUpDetails( kind: PointerDeviceKind.touch, globalPosition: Offset(size.width / 2, size.height / 2), ); @@ -1752,22 +1762,6 @@ class CameraPickerState extends State preview = Stack( children: [ preview, - Positioned.fill( - child: ExcludeSemantics( - child: RotatedBox( - quarterTurns: cameraQuarterTurns, - child: Align( - alignment: { - DeviceOrientation.portraitUp: Alignment.bottomCenter, - DeviceOrientation.portraitDown: Alignment.topCenter, - DeviceOrientation.landscapeLeft: Alignment.centerRight, - DeviceOrientation.landscapeRight: Alignment.centerLeft, - }[cameraValue.deviceOrientation]!, - child: buildCaptureTips(innerController), - ), - ), - ), - ), if (pickerConfig.enableSetExposure) buildExposureDetector(context, constraints), buildFocusingPoint( @@ -1827,9 +1821,10 @@ class CameraPickerState extends State BoxConstraints constraints, DeviceOrientation? deviceOrientation, ) { - final orientation = deviceOrientation ?? MediaQuery.of(context).orientation; + final orientation = deviceOrientation ?? MediaQuery.orientationOf(context); final isPortrait = orientation.toString().contains('portrait'); return SafeArea( + bottom: false, child: Flex( direction: isPortrait ? Axis.vertical : Axis.horizontal, textDirection: orientation == DeviceOrientation.landscapeRight @@ -1844,8 +1839,7 @@ class CameraPickerState extends State child: buildSettingActions(context), ), const Spacer(), - if (enableScaledPreview) - ExcludeSemantics(child: buildCaptureTips(innerController)), + ExcludeSemantics(child: buildCaptureTips(innerController)), Semantics( sortKey: const OrdinalSortKey(2), hidden: innerController == null, @@ -1907,8 +1901,8 @@ class CameraPickerState extends State image: true, onTap: () { // Focus on the center point when using semantics tap. - final Size size = MediaQuery.of(context).size; - final TapUpDetails details = TapUpDetails( + final size = MediaQuery.sizeOf(context); + final details = TapUpDetails( kind: PointerDeviceKind.touch, globalPosition: Offset(size.width / 2, size.height / 2), ); diff --git a/lib/src/states/camera_picker_viewer_state.dart b/lib/src/states/camera_picker_viewer_state.dart index 30d73f4..0787c0a 100644 --- a/lib/src/states/camera_picker_viewer_state.dart +++ b/lib/src/states/camera_picker_viewer_state.dart @@ -150,18 +150,18 @@ class CameraPickerViewerState extends State { try { final PermissionState ps = await PhotoManager.requestPermissionExtend(); if (ps == PermissionState.authorized || ps == PermissionState.limited) { + final filePath = previewFile.path; switch (widget.viewType) { case CameraPickerViewType.image: - final String filePath = previewFile.path; entity = await PhotoManager.editor.saveImageWithPath( filePath, - title: path.basename(previewFile.path), + title: path.basename(filePath), ); break; case CameraPickerViewType.video: entity = await PhotoManager.editor.saveVideo( previewFile, - title: path.basename(previewFile.path), + title: path.basename(filePath), ); break; } diff --git a/pubspec.yaml b/pubspec.yaml index 9819749..0cc05c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: wechat_camera_picker -version: 4.3.0+1 +version: 4.3.1 description: | A camera picker for Flutter projects based on WeChat's UI, which is also a separate runnable extension to the