import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../core/log/app_logger.dart'; import '../../core/theme/app_colors.dart'; import '../../core/theme/app_spacing.dart'; /// 应用内拍照:不跳出系统相机 Activity,避免与 Flutter Surface / 截屏防护等叠加导致黑屏卡死。 class InAppCameraPage extends StatefulWidget { const InAppCameraPage({super.key}); static final _log = AppLogger('InAppCamera'); @override State createState() => _InAppCameraPageState(); } class _InAppCameraPageState extends State { CameraController? _controller; bool _initializing = true; bool _capturing = false; String? _error; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _prepareCamera()); } Future _prepareCamera() async { final status = await Permission.camera.request(); if (!status.isGranted) { if (mounted) { setState(() { _initializing = false; _error = 'Camera permission is required'; }); } return; } try { final cameras = await availableCameras(); if (cameras.isEmpty) { if (mounted) { setState(() { _initializing = false; _error = 'No camera available'; }); } return; } final back = cameras.where((c) => c.lensDirection == CameraLensDirection.back); final selected = back.isEmpty ? cameras.first : back.first; final c = CameraController( selected, ResolutionPreset.medium, enableAudio: false, ); await c.initialize(); if (!mounted) { await c.dispose(); return; } setState(() { _controller = c; _initializing = false; }); } catch (e, st) { InAppCameraPage._log.e('Camera init failed', e, st); if (mounted) { setState(() { _initializing = false; _error = 'Could not open camera'; }); } } } @override void dispose() { _controller?.dispose(); super.dispose(); } Future _onShutter() async { final c = _controller; if (c == null || !c.value.isInitialized || _capturing) return; setState(() => _capturing = true); try { final shot = await c.takePicture(); if (!mounted) return; Navigator.of(context, rootNavigator: true).pop(shot.path); } catch (e, st) { InAppCameraPage._log.e('takePicture failed', e, st); if (mounted) setState(() => _capturing = false); } } void _onClose() { Navigator.of(context, rootNavigator: true).pop(null); } @override Widget build(BuildContext context) { final c = _controller; final showPreview = !_initializing && _error == null && c != null && c.value.isInitialized; return Scaffold( backgroundColor: Colors.black, body: Stack( fit: StackFit.expand, children: [ if (showPreview) Positioned.fill( child: ColoredBox( color: Colors.black, // 勿再包一层自定义 aspect 的 SizedBox/FittedBox:[CameraPreview] 已在竖屏下使用 // 1/value.aspectRatio + Android [RotatedBox],外层乱算比例会把它压扁。 child: Center( child: CameraPreview(c), ), ), ), SafeArea( child: Stack( fit: StackFit.expand, children: [ if (_initializing) const Center( child: CircularProgressIndicator(color: AppColors.primary), ) else if (_error != null) Center( child: Padding( padding: const EdgeInsets.all(AppSpacing.lg), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( LucideIcons.camera_off, size: 48, color: AppColors.surface.withValues(alpha: 0.6), ), const SizedBox(height: AppSpacing.md), Text( _error!, textAlign: TextAlign.center, style: TextStyle( color: AppColors.surface.withValues(alpha: 0.85), fontSize: 15, ), ), const SizedBox(height: AppSpacing.lg), TextButton( onPressed: _onClose, child: const Text('Close'), ), ], ), ), ), if (showPreview) Positioned( left: 0, right: 0, bottom: 24, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( onPressed: _capturing ? null : _onClose, icon: Icon( LucideIcons.x, color: AppColors.surface.withValues(alpha: 0.9), size: 28, ), ), GestureDetector( onTap: _capturing ? null : _onShutter, child: Container( width: 72, height: 72, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: AppColors.surface, width: 4, ), color: AppColors.surface.withValues(alpha: 0.2), ), child: _capturing ? const Padding( padding: EdgeInsets.all(20), child: CircularProgressIndicator( strokeWidth: 2, color: AppColors.surface, ), ) : null, ), ), const SizedBox(width: 48), ], ), ), ], ), ), ], ), ); } }