From aeda40d34f4b0989c17c992967c9c4a380ac24dc Mon Sep 17 00:00:00 2001 From: otsmr Date: Thu, 17 Jul 2025 19:53:37 +0200 Subject: [PATCH] fix camera freeze issue --- .gitignore | 1 + .../camera_preview.dart | 48 +++--- .../camera_preview_controller_view.dart | 150 +++++++++--------- lib/src/views/camera/camera_send_to_view.dart | 15 +- lib/src/views/home.view.dart | 15 +- 5 files changed, 117 insertions(+), 112 deletions(-) diff --git a/.gitignore b/.gitignore index 5b8dc46..0ed545b 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ app.*.map.json /android/app/release /android/app/.cxx/ android/.kotlin/ +devtools_options.yaml diff --git a/lib/src/views/camera/camera_preview_components/camera_preview.dart b/lib/src/views/camera/camera_preview_components/camera_preview.dart index ae25306..a59d5c7 100644 --- a/lib/src/views/camera/camera_preview_components/camera_preview.dart +++ b/lib/src/views/camera/camera_preview_components/camera_preview.dart @@ -1,41 +1,36 @@ import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:screenshot/screenshot.dart'; -import 'package:twonly/src/views/camera/camera_send_to_view.dart'; import 'package:twonly/src/views/components/media_view_sizing.dart'; -import 'package:twonly/src/views/home.view.dart'; -class HomeViewCameraPreview extends StatefulWidget { +class HomeViewCameraPreview extends StatelessWidget { const HomeViewCameraPreview({ + required this.controller, + required this.screenshotController, super.key, }); - @override - State createState() => _HomeViewCameraPreviewState(); -} + final CameraController? controller; + final ScreenshotController screenshotController; -class _HomeViewCameraPreviewState extends State { @override Widget build(BuildContext context) { - if (HomeViewState.cameraController == null || - !HomeViewState.cameraController!.value.isInitialized) { + if (controller == null || !controller!.value.isInitialized) { return Container(); } return Positioned.fill( child: MediaViewSizing( child: Screenshot( - controller: HomeViewState.screenshotController, + controller: screenshotController, child: AspectRatio( aspectRatio: 9 / 16, child: ClipRect( child: FittedBox( fit: BoxFit.cover, child: SizedBox( - width: - HomeViewState.cameraController!.value.previewSize!.height, - height: - HomeViewState.cameraController!.value.previewSize!.width, - child: CameraPreview(HomeViewState.cameraController!), + width: controller!.value.previewSize!.height, + height: controller!.value.previewSize!.width, + child: CameraPreview(controller!), ), ), ), @@ -46,37 +41,34 @@ class _HomeViewCameraPreviewState extends State { } } -class SendToCameraPreview extends StatefulWidget { +class SendToCameraPreview extends StatelessWidget { const SendToCameraPreview({ + required this.cameraController, + required this.screenshotController, super.key, }); - @override - State createState() => _SendToCameraPreviewState(); -} + final CameraController? cameraController; + final ScreenshotController screenshotController; -class _SendToCameraPreviewState extends State { @override Widget build(BuildContext context) { - if (CameraSendToViewState.cameraController == null || - !CameraSendToViewState.cameraController!.value.isInitialized) { + if (cameraController == null || !cameraController!.value.isInitialized) { return Container(); } return Positioned.fill( child: MediaViewSizing( child: Screenshot( - controller: CameraSendToViewState.screenshotController, + controller: screenshotController, child: AspectRatio( aspectRatio: 9 / 16, child: ClipRect( child: FittedBox( fit: BoxFit.cover, child: SizedBox( - width: CameraSendToViewState - .cameraController!.value.previewSize!.height, - height: CameraSendToViewState - .cameraController!.value.previewSize!.width, - child: CameraPreview(CameraSendToViewState.cameraController!), + width: cameraController!.value.previewSize!.height, + height: cameraController!.value.previewSize!.width, + child: CameraPreview(cameraController!), ), ), ), diff --git a/lib/src/views/camera/camera_preview_controller_view.dart b/lib/src/views/camera/camera_preview_controller_view.dart index 0c02967..c8d0243 100644 --- a/lib/src/views/camera/camera_preview_controller_view.dart +++ b/lib/src/views/camera/camera_preview_controller_view.dart @@ -17,7 +17,6 @@ import 'package:twonly/src/views/camera/camera_preview_components/permissions_vi import 'package:twonly/src/views/camera/camera_preview_components/send_to.dart'; import 'package:twonly/src/views/camera/camera_preview_components/video_recording_time.dart'; import 'package:twonly/src/views/camera/camera_preview_components/zoom_selector.dart'; -import 'package:twonly/src/views/camera/camera_send_to_view.dart'; import 'package:twonly/src/views/camera/image_editor/action_button.dart'; import 'package:twonly/src/views/camera/share_image_editor_view.dart'; import 'package:twonly/src/views/components/media_view_sizing.dart'; @@ -83,23 +82,21 @@ class SelectedCameraDetails { bool cameraLoaded = false; } -class CameraPreviewControllerView extends StatefulWidget { +class CameraPreviewControllerView extends StatelessWidget { const CameraPreviewControllerView({ + required this.cameraController, required this.selectCamera, - required this.isHomeView, + required this.selectedCameraDetails, + required this.screenshotController, super.key, this.sendTo, }); final Contact? sendTo; final void Function(int sCameraId, bool init, bool enableAudio) selectCamera; - final bool isHomeView; + final CameraController? cameraController; + final SelectedCameraDetails selectedCameraDetails; + final ScreenshotController screenshotController; - @override - State createState() => - _CameraPreviewControllerView(); -} - -class _CameraPreviewControllerView extends State { @override Widget build(BuildContext context) { return FutureBuilder( @@ -108,13 +105,16 @@ class _CameraPreviewControllerView extends State { if (snap.hasData) { if (snap.data!) { return CameraPreviewView( - sendTo: widget.sendTo, - selectCamera: widget.selectCamera, - isHomeView: widget.isHomeView, + sendTo: sendTo, + selectCamera: selectCamera, + cameraController: cameraController, + selectedCameraDetails: selectedCameraDetails, + screenshotController: screenshotController, ); } else { return PermissionHandlerView(onSuccess: () { - setState(() {}); + // setState(() {}); + selectCamera(0, true, false); }); } } else { @@ -126,14 +126,19 @@ class _CameraPreviewControllerView extends State { } class CameraPreviewView extends StatefulWidget { - const CameraPreviewView( - {required this.selectCamera, - required this.isHomeView, - super.key, - this.sendTo}); + const CameraPreviewView({ + required this.selectCamera, + required this.cameraController, + required this.selectedCameraDetails, + required this.screenshotController, + super.key, + this.sendTo, + }); final Contact? sendTo; - final bool isHomeView; final void Function(int sCameraId, bool init, bool enableAudio) selectCamera; + final CameraController? cameraController; + final SelectedCameraDetails selectedCameraDetails; + final ScreenshotController screenshotController; @override State createState() => _CameraPreviewViewState(); @@ -163,18 +168,6 @@ class _CameraPreviewViewState extends State { initAsync(); } - CameraController? get cameraController => widget.isHomeView - ? HomeViewState.cameraController - : CameraSendToViewState.cameraController; - - SelectedCameraDetails get selectedCameraDetails => widget.isHomeView - ? HomeViewState.selectedCameraDetails - : CameraSendToViewState.selectedCameraDetails; - - ScreenshotController get screenshotController => widget.isHomeView - ? HomeViewState.screenshotController - : CameraSendToViewState.screenshotController; - Future initAsync() async { hasAudioPermission = await Permission.microphone.isGranted; if (!mounted) return; @@ -200,15 +193,15 @@ class _CameraPreviewViewState extends State { } Future updateScaleFactor(double newScale) async { - if (selectedCameraDetails.scaleFactor == newScale || - cameraController == null) { + if (widget.selectedCameraDetails.scaleFactor == newScale || + widget.cameraController == null) { return; } - await cameraController?.setZoomLevel(newScale.clamp( - selectedCameraDetails.minAvailableZoom, - selectedCameraDetails.maxAvailableZoom)); + await widget.cameraController?.setZoomLevel(newScale.clamp( + widget.selectedCameraDetails.minAvailableZoom, + widget.selectedCameraDetails.maxAvailableZoom)); setState(() { - selectedCameraDetails.scaleFactor = newScale; + widget.selectedCameraDetails.scaleFactor = newScale; }); } @@ -240,26 +233,28 @@ class _CameraPreviewViewState extends State { setState(() { sharePreviewIsShown = true; }); - if (selectedCameraDetails.isFlashOn) { + if (widget.selectedCameraDetails.isFlashOn) { if (isFront) { setState(() { showSelfieFlash = true; }); } else { - await cameraController?.setFlashMode(FlashMode.torch); + await widget.cameraController?.setFlashMode(FlashMode.torch); } await Future.delayed(const Duration(milliseconds: 1000)); } - await cameraController?.pausePreview(); + await widget.cameraController?.pausePreview(); if (!mounted) return; - await cameraController?.setFlashMode( - selectedCameraDetails.isFlashOn ? FlashMode.always : FlashMode.off); + await widget.cameraController?.setFlashMode( + widget.selectedCameraDetails.isFlashOn + ? FlashMode.always + : FlashMode.off); if (!mounted) return; - imageBytes = screenshotController.capture( - pixelRatio: MediaQuery.of(context).devicePixelRatio); + imageBytes = widget.screenshotController + .capture(pixelRatio: MediaQuery.of(context).devicePixelRatio); if (await pushMediaEditor(imageBytes, null)) { return; @@ -305,26 +300,30 @@ class _CameraPreviewViewState extends State { } return true; } - widget.selectCamera(selectedCameraDetails.cameraId, false, false); + widget.selectCamera(widget.selectedCameraDetails.cameraId, false, false); return false; } bool get isFront => - cameraController?.description.lensDirection == CameraLensDirection.front; + widget.cameraController?.description.lensDirection == + CameraLensDirection.front; Future onPanUpdate(dynamic details) async { if (isFront || details == null) { return; } - if (cameraController == null) return; - if (!cameraController!.value.isInitialized) return; + if (widget.cameraController == null || + !widget.cameraController!.value.isInitialized) { + return; + } - selectedCameraDetails.scaleFactor = + widget.selectedCameraDetails.scaleFactor = // ignore: avoid_dynamic_calls (baseScaleFactor + (basePanY - (details.localPosition.dy as int)) / 30) - .clamp(1, selectedCameraDetails.maxAvailableZoom); + .clamp(1, widget.selectedCameraDetails.maxAvailableZoom); - await cameraController!.setZoomLevel(selectedCameraDetails.scaleFactor); + await widget.cameraController! + .setZoomLevel(widget.selectedCameraDetails.scaleFactor); if (mounted) { setState(() {}); } @@ -353,12 +352,13 @@ class _CameraPreviewViewState extends State { } Future startVideoRecording() async { - if (cameraController != null && cameraController!.value.isRecordingVideo) { + if (widget.cameraController != null && + widget.cameraController!.value.isRecordingVideo) { return; } if (hasAudioPermission && videoWithAudio) { widget.selectCamera( - selectedCameraDetails.cameraId, + widget.selectedCameraDetails.cameraId, false, await Permission.microphone.isGranted && videoWithAudio, ); @@ -369,7 +369,7 @@ class _CameraPreviewViewState extends State { }); try { - await cameraController?.startVideoRecording(); + await widget.cameraController?.startVideoRecording(); videoRecordingTimer = Timer.periodic(const Duration(milliseconds: 15), (timer) { setState(() { @@ -401,7 +401,8 @@ class _CameraPreviewViewState extends State { videoRecordingTimer?.cancel(); videoRecordingTimer = null; } - if (cameraController == null || !cameraController!.value.isRecordingVideo) { + if (widget.cameraController == null || + !widget.cameraController!.value.isRecordingVideo) { return; } @@ -412,7 +413,7 @@ class _CameraPreviewViewState extends State { sharePreviewIsShown = true; }); File? videoPathFile; - final videoPath = await cameraController?.stopVideoRecording(); + final videoPath = await widget.cameraController?.stopVideoRecording(); if (videoPath != null) { if (Platform.isAndroid) { // see https://github.com/flutter/flutter/issues/148335 @@ -422,7 +423,7 @@ class _CameraPreviewViewState extends State { videoPathFile = File(videoPath.path); } } - await cameraController?.pausePreview(); + await widget.cameraController?.pausePreview(); if (await pushMediaEditor(null, videoPathFile)) { return; } @@ -450,8 +451,8 @@ class _CameraPreviewViewState extends State { @override Widget build(BuildContext context) { - if (selectedCameraDetails.cameraId >= gCameras.length || - cameraController == null) { + if (widget.selectedCameraDetails.cameraId >= gCameras.length || + widget.cameraController == null) { return Container(); } return MediaViewSizing( @@ -462,14 +463,14 @@ class _CameraPreviewViewState extends State { } setState(() { basePanY = details.localPosition.dy; - baseScaleFactor = selectedCameraDetails.scaleFactor; + baseScaleFactor = widget.selectedCameraDetails.scaleFactor; }); }, onLongPressMoveUpdate: onPanUpdate, onLongPressStart: (details) { setState(() { basePanY = details.localPosition.dy; - baseScaleFactor = selectedCameraDetails.scaleFactor; + baseScaleFactor = widget.selectedCameraDetails.scaleFactor; }); // Get the position of the pointer final renderBox = @@ -523,28 +524,28 @@ class _CameraPreviewViewState extends State { tooltipText: context.lang.switchFrontAndBackCamera, onPressed: () async { widget.selectCamera( - (selectedCameraDetails.cameraId + 1) % 2, + (widget.selectedCameraDetails.cameraId + 1) % 2, false, false); }, ), ActionButton( - selectedCameraDetails.isFlashOn + widget.selectedCameraDetails.isFlashOn ? Icons.flash_on_rounded : Icons.flash_off_rounded, tooltipText: context.lang.toggleFlashLight, - color: selectedCameraDetails.isFlashOn + color: widget.selectedCameraDetails.isFlashOn ? Colors.white : Colors.white.withAlpha(160), onPressed: () async { - if (selectedCameraDetails.isFlashOn) { - await cameraController + if (widget.selectedCameraDetails.isFlashOn) { + await widget.cameraController ?.setFlashMode(FlashMode.off); - selectedCameraDetails.isFlashOn = false; + widget.selectedCameraDetails.isFlashOn = false; } else { - await cameraController + await widget.cameraController ?.setFlashMode(FlashMode.always); - selectedCameraDetails.isFlashOn = true; + widget.selectedCameraDetails.isFlashOn = true; } setState(() {}); }, @@ -602,17 +603,18 @@ class _CameraPreviewViewState extends State { alignment: Alignment.bottomCenter, child: Column( children: [ - if (cameraController!.value.isInitialized && - selectedCameraDetails.isZoomAble && + if (widget.cameraController!.value.isInitialized && + widget.selectedCameraDetails.isZoomAble && !isFront && !isVideoRecording) SizedBox( width: 120, child: CameraZoomButtons( key: widget.key, - scaleFactor: selectedCameraDetails.scaleFactor, + scaleFactor: + widget.selectedCameraDetails.scaleFactor, updateScaleFactor: updateScaleFactor, - controller: cameraController!, + controller: widget.cameraController!, ), ), const SizedBox(height: 30), diff --git a/lib/src/views/camera/camera_send_to_view.dart b/lib/src/views/camera/camera_send_to_view.dart index 3955c50..07231d5 100644 --- a/lib/src/views/camera/camera_send_to_view.dart +++ b/lib/src/views/camera/camera_send_to_view.dart @@ -13,9 +13,9 @@ class CameraSendToView extends StatefulWidget { } class CameraSendToViewState extends State { - static CameraController? cameraController; - static ScreenshotController screenshotController = ScreenshotController(); - static SelectedCameraDetails selectedCameraDetails = SelectedCameraDetails(); + CameraController? cameraController; + ScreenshotController screenshotController = ScreenshotController(); + SelectedCameraDetails selectedCameraDetails = SelectedCameraDetails(); @override void initState() { @@ -54,11 +54,16 @@ class CameraSendToViewState extends State { onDoubleTap: toggleSelectedCamera, child: Stack( children: [ - const SendToCameraPreview(), + SendToCameraPreview( + cameraController: cameraController, + screenshotController: screenshotController, + ), CameraPreviewControllerView( selectCamera: selectCamera, sendTo: widget.sendTo, - isHomeView: false, + cameraController: cameraController, + selectedCameraDetails: selectedCameraDetails, + screenshotController: screenshotController, ), ], ), diff --git a/lib/src/views/home.view.dart b/lib/src/views/home.view.dart index 6d58b05..38db6a1 100644 --- a/lib/src/views/home.view.dart +++ b/lib/src/views/home.view.dart @@ -55,9 +55,9 @@ class HomeViewState extends State { Timer? disableCameraTimer; bool initCameraStarted = true; - static CameraController? cameraController; - static ScreenshotController screenshotController = ScreenshotController(); - static SelectedCameraDetails selectedCameraDetails = SelectedCameraDetails(); + CameraController? cameraController; + ScreenshotController screenshotController = ScreenshotController(); + SelectedCameraDetails selectedCameraDetails = SelectedCameraDetails(); bool onPageView(ScrollNotification notification) { disableCameraTimer?.cancel(); @@ -145,7 +145,10 @@ class HomeViewState extends State { onDoubleTap: offsetRatio == 0 ? toggleSelectedCamera : null, child: Stack( children: [ - const HomeViewCameraPreview(), + HomeViewCameraPreview( + controller: cameraController, + screenshotController: screenshotController, + ), Shade( opacity: offsetRatio, ), @@ -177,8 +180,10 @@ class HomeViewState extends State { child: Opacity( opacity: 1 - (offsetRatio * 4) % 1, child: CameraPreviewControllerView( + cameraController: cameraController, + screenshotController: screenshotController, + selectedCameraDetails: selectedCameraDetails, selectCamera: selectCamera, - isHomeView: true, ), )), ],