import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'dart:async'; import 'media_preview_screen.dart'; class CameraScreen extends StatefulWidget { const CameraScreen({super.key}); @override State createState() => _CameraScreenState(); } enum FlashModeType { off, autoCapture, alwaysCapture, torch } class _CameraScreenState extends State { CameraController? _controller; List _cameras = []; int _cameraIndex = 0; bool _isRecording = false; FlashModeType _flashMode = FlashModeType.off; double _minZoom = 1.0; double _maxZoom = 1.0; double _currentZoom = 1.0; bool _showZoomSlider = false; Future? _initFuture; @override void initState() { super.initState(); _initFuture = _init(); } Future _init() async { _cameras = await availableCameras(); await _initCamera(); } Future _initCamera() async { final camera = _cameras[_cameraIndex]; final controller = CameraController( camera, ResolutionPreset.high, enableAudio: true, ); await controller.initialize(); _minZoom = await controller.getMinZoomLevel(); _maxZoom = await controller.getMaxZoomLevel(); _currentZoom = _minZoom; if (!mounted) return; setState(() { _controller = controller; }); } Future _switchCamera() async { if (_cameras.length < 2) return; await _controller?.dispose(); _cameraIndex = (_cameraIndex + 1) % _cameras.length; setState(() => _controller = null); await _initCamera(); } Future _cycleFlashMode() async { if (_controller == null) return; switch (_flashMode) { case FlashModeType.off: _flashMode = FlashModeType.autoCapture; await _controller!.setFlashMode(FlashMode.off); break; case FlashModeType.autoCapture: _flashMode = FlashModeType.alwaysCapture; await _controller!.setFlashMode(FlashMode.off); break; case FlashModeType.alwaysCapture: _flashMode = FlashModeType.torch; await _controller!.setFlashMode(FlashMode.torch); break; case FlashModeType.torch: _flashMode = FlashModeType.off; await _controller!.setFlashMode(FlashMode.off); break; } setState(() {}); } Future _takePhoto() async { if (_controller == null) return; bool usedTorch = false; if (_flashMode == FlashModeType.alwaysCapture) { await _controller!.setFlashMode(FlashMode.torch); usedTorch = true; await Future.delayed(const Duration(milliseconds: 120)); } if (_flashMode == FlashModeType.autoCapture) { await _controller!.setFlashMode(FlashMode.torch); usedTorch = true; await Future.delayed(const Duration(milliseconds: 120)); } final file = await _controller!.takePicture(); if (usedTorch) { await _controller!.setFlashMode(FlashMode.off); } WidgetsBinding.instance.addPostFrameCallback((_) async { final result = await Navigator.push( context, MaterialPageRoute( builder: (_) => MediaPreviewScreen(path: file.path, isVideo: false), ), ); if (result == true && mounted) { Navigator.pop(context, (file, 'image')); } }); } bool usedTorch = false; Future _startVideo() async { if (_controller == null || _isRecording) return; if (_flashMode == FlashModeType.alwaysCapture) { await _controller!.setFlashMode(FlashMode.torch); usedTorch = true; await Future.delayed(const Duration(milliseconds: 120)); } if (_flashMode == FlashModeType.autoCapture) { await _controller!.setFlashMode(FlashMode.torch); usedTorch = true; await Future.delayed(const Duration(milliseconds: 120)); } await _controller!.startVideoRecording(); setState(() => _isRecording = true); } Future _stopVideo() async { if (_controller == null || !_isRecording) return; if (usedTorch) { await _controller!.setFlashMode(FlashMode.off); } final file = await _controller!.stopVideoRecording(); setState(() => _isRecording = false); final result = await Navigator.push( context, MaterialPageRoute( builder: (_) => MediaPreviewScreen(path: file.path, isVideo: true), ), ); if (result == true && mounted) { Navigator.pop(context, (file, 'video')); } } Future _setZoom(double zoom) async { if (_controller == null) return; final clamped = zoom.clamp(_minZoom, _maxZoom); await _controller!.setZoomLevel(clamped); setState(() { _currentZoom = clamped; }); } @override void dispose() { _controller?.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, body: FutureBuilder( future: _initFuture, builder: (context, snapshot) { if (_controller == null || !_controller!.value.isInitialized) { return const Center(child: CircularProgressIndicator()); } return Stack( children: [ // 📷 Camera preview (full screen, Telegram style crop) Positioned.fill( child: FittedBox( fit: BoxFit.cover, child: SizedBox( width: _controller!.value.previewSize!.height, height: _controller!.value.previewSize!.width, child: GestureDetector( onScaleStart: (_) { setState(() { _showZoomSlider = true; }); }, onScaleUpdate: (details) { final zoom = (_currentZoom * details.scale).clamp( _minZoom, _maxZoom, ); _setZoom(zoom); }, child: CameraPreview(_controller!), ), ), ), ), // 🌑 top gradient (Telegram feel) Positioned( top: 0, left: 0, right: 0, height: 120, child: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.black87, Colors.transparent], ), ), ), ), // 🔘 top controls Positioned( top: 50, left: 20, right: 20, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Flash (left) IconButton( onPressed: _cycleFlashMode, icon: Icon(switch (_flashMode) { FlashModeType.off => Icons.flash_off, FlashModeType.autoCapture => Icons.flash_auto, FlashModeType.alwaysCapture => Icons.flash_on, FlashModeType.torch => Icons.highlight, }, color: Colors.white), ), // Camera switch (right) IconButton( onPressed: _switchCamera, icon: const Icon(Icons.cameraswitch, color: Colors.white), ), ], ), ), // 🔘 capture button (center bottom) Positioned( bottom: 90, left: 0, right: 0, child: Column( children: [ GestureDetector( onTap: _takePhoto, onLongPressStart: (_) => _startVideo(), onLongPressEnd: (_) => _stopVideo(), child: AnimatedContainer( duration: const Duration(milliseconds: 150), width: _isRecording ? 80 : 72, height: _isRecording ? 80 : 72, decoration: BoxDecoration( shape: BoxShape.circle, color: _isRecording ? Colors.red : Colors.white, border: Border.all(color: Colors.white, width: 4), ), ), ), const SizedBox(height: 16), const Text( "Нажмите для фото, удерживайте для съемки", style: TextStyle(color: Colors.white70, fontSize: 13), ), ], ), ), // 🔴 recording indicator if (_isRecording) const Positioned( top: 50, left: 0, right: 0, child: Center( child: Text( "REC", style: TextStyle( color: Colors.red, fontWeight: FontWeight.bold, fontSize: 14, ), ), ), ), if (_showZoomSlider) Positioned( bottom: 200, left: 20, right: 20, child: Center( child: Container( child: Row( children: [ GestureDetector( onTap: () { final newZoom = (_currentZoom - 0.5).clamp( _minZoom, _maxZoom, ); _setZoom(newZoom); }, child: const Text( '−', style: TextStyle( color: Colors.white, fontSize: 18, ), ), ), const SizedBox(width: 8), Expanded( child: SliderTheme( data: SliderTheme.of(context).copyWith( trackHeight: 2, activeTrackColor: Colors.white, inactiveTrackColor: Colors.white24, thumbColor: Colors.white, overlayColor: Colors.white24, thumbShape: const RoundSliderThumbShape( enabledThumbRadius: 6, ), ), child: Slider( value: _currentZoom, min: _minZoom, max: _maxZoom, onChanged: (value) { _setZoom(value); }, ), ), ), const SizedBox(width: 8), GestureDetector( onTap: () { final newZoom = (_currentZoom + 0.5).clamp( _minZoom, _maxZoom, ); _setZoom(newZoom); }, child: const Text( '+', style: TextStyle( color: Colors.white, fontSize: 18, ), ), ), ], ), ), ), ), ], ); }, ), ); } }