import 'dart:typed_data'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:screenshot/screenshot.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/components/zoom_selector.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/components/image_editor/action_button.dart'; import 'package:twonly/src/components/media_view_sizing.dart'; import 'package:twonly/src/components/permissions_view.dart'; import 'package:twonly/src/views/camera_to_share/share_image_editor_view.dart'; class CameraPreviewViewPermission extends StatefulWidget { const CameraPreviewViewPermission({super.key}); @override State createState() => _CameraPreviewViewPermission(); } class _CameraPreviewViewPermission extends State { @override Widget build(BuildContext context) { return FutureBuilder( future: checkPermissions(), builder: (context, snap) { if (snap.hasData) { if (snap.data!) { return CameraPreviewView(); } else { return PermissionHandlerView(onSuccess: () { setState(() {}); }); } } else { return Container(); } }); } } class CameraPreviewView extends StatefulWidget { const CameraPreviewView({super.key}); @override State createState() => _CameraPreviewViewState(); } class _CameraPreviewViewState extends State { double scaleFactor = 1; bool sharePreviewIsShown = false; bool isFlashOn = false; bool showSelfieFlash = false; int cameraId = 0; bool isZoomAble = false; double basePanY = 0; double baseScaleFactor = 0; bool cameraLoaded = false; final GlobalKey navigatorKey = GlobalKey(); late CameraController controller; ScreenshotController screenshotController = ScreenshotController(); @override void initState() { super.initState(); selectCamera(0, init: true); FlutterVolumeController.addListener( (volume) { if (!cameraLoaded) { // there is a bug, this is called at the start return; } if (sharePreviewIsShown) return; if (controller.value.isInitialized) takePicture(); }, ); } @override void dispose() { FlutterVolumeController.removeListener(); if (cameraId < gCameras.length) { controller.dispose(); } super.dispose(); } void selectCamera(int sCameraId, {bool init = false}) { if (sCameraId >= gCameras.length) return; if (init) { for (; sCameraId < gCameras.length; sCameraId++) { if (gCameras[sCameraId].lensDirection == CameraLensDirection.back) { break; } } } setState(() { isZoomAble = false; }); controller = CameraController(gCameras[sCameraId], ResolutionPreset.high, enableAudio: false); controller.initialize().then((_) async { if (!mounted) { return; } controller.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off); isZoomAble = await controller.getMinZoomLevel() != await controller.getMaxZoomLevel(); setState(() { cameraLoaded = true; }); }).catchError((Object e) { if (e is CameraException) { switch (e.code) { case 'CameraAccessDenied': // Handle access errors here. break; default: // Handle other errors here. break; } } }); setState(() { cameraId = sCameraId; }); } Future updateScaleFactor(double newScale) async { if (scaleFactor == newScale) return; var minFactor = await controller.getMinZoomLevel(); var maxFactor = await controller.getMaxZoomLevel(); if (newScale < minFactor) { newScale = minFactor; } if (newScale > maxFactor) { newScale = maxFactor; } await controller.setZoomLevel(newScale); setState(() { scaleFactor = newScale; }); } Future takePicture() async { if (isFlashOn) { if (isFront) { setState(() { showSelfieFlash = true; }); } else { controller.setFlashMode(FlashMode.torch); } await Future.delayed(Duration(milliseconds: 1000)); } await controller.pausePreview(); if (!context.mounted) return; controller.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off); Future imageBytes = screenshotController.capture(pixelRatio: 1); setState(() { sharePreviewIsShown = true; }); await Navigator.push( context, PageRouteBuilder( opaque: false, pageBuilder: (context, a1, a2) => ShareImageEditorView(imageBytes: imageBytes), transitionsBuilder: (context, animation, secondaryAnimation, child) { return child; }, transitionDuration: Duration.zero, reverseTransitionDuration: Duration.zero, ), ); // does not work?? //await controller.resumePreview(); selectCamera(0); if (context.mounted) { setState(() { sharePreviewIsShown = false; }); } setState(() { showSelfieFlash = false; }); } bool get isFront => controller.description.lensDirection == CameraLensDirection.front; @override Widget build(BuildContext context) { if (cameraId >= gCameras.length) { return Center( child: Text("No camera found."), ); } return MediaViewSizing( Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(22), child: Stack( children: [ (controller.value.isInitialized) ? Positioned.fill( child: Screenshot( controller: screenshotController, child: AspectRatio( aspectRatio: 9 / 16, child: ClipRect( child: FittedBox( fit: BoxFit.cover, child: SizedBox( width: controller.value.previewSize!.height, height: controller.value.previewSize!.width, child: Transform( alignment: Alignment.center, transform: Matrix4.rotationY(isFront ? 3.14 : 0), child: CameraPreview(controller), ), ), ), ), ), ), ) : Container(), Positioned.fill( child: GestureDetector( onPanStart: (details) async { if (isFront) { return; } setState(() { basePanY = details.localPosition.dy; baseScaleFactor = scaleFactor; }); }, onPanUpdate: (details) async { if (isFront) { return; } var diff = basePanY - details.localPosition.dy; if (diff > 200) diff = 200; if (diff < -200) diff = -200; var tmp = (diff / 200 * (7 * 2)).toInt() / 2; tmp = baseScaleFactor + tmp; if (tmp < 1) tmp = 1; updateScaleFactor(tmp); }, onDoubleTap: () async { selectCamera((cameraId + 1) % 2); }, ), ), if (!sharePreviewIsShown) Positioned( right: 5, top: 0, child: Container( alignment: Alignment.bottomCenter, padding: const EdgeInsets.symmetric(vertical: 16), child: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ActionButton( FontAwesomeIcons.repeat, tooltipText: context.lang.switchFrontAndBackCamera, onPressed: () async { selectCamera((cameraId + 1) % 2); }, ), ActionButton( FontAwesomeIcons.boltLightning, tooltipText: context.lang.toggleFlashLight, color: isFlashOn ? const Color.fromARGB(255, 255, 230, 0) : const Color.fromARGB(158, 255, 255, 255), onPressed: () async { if (isFlashOn) { controller.setFlashMode(FlashMode.off); isFlashOn = false; } else { controller.setFlashMode(FlashMode.always); isFlashOn = true; } setState(() {}); }, ), ], ), ), ), ), if (!sharePreviewIsShown) Positioned( bottom: 30, left: 0, right: 0, child: Align( alignment: Alignment.bottomCenter, child: Column( children: [ if (controller.value.isInitialized && isZoomAble && !isFront) SizedBox( width: 120, child: CameraZoomButtons( key: widget.key, scaleFactor: scaleFactor, updateScaleFactor: updateScaleFactor, controller: controller, ), ), const SizedBox(height: 30), GestureDetector( onTap: () async { takePicture(); }, onLongPress: () async {}, child: Align( alignment: Alignment.center, child: Container( height: 100, width: 100, clipBehavior: Clip.antiAliasWithSaveLayer, padding: const EdgeInsets.all(2), decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( width: 7, color: Colors.white, ), ), ), ), ), ], ), ), ), ], ), ), if (showSelfieFlash) Positioned.fill( child: ClipRRect( borderRadius: BorderRadius.circular(22), child: Container( color: Colors.white, ), ), ), ], ), ); } }