import 'package:camera/camera.dart'; import 'package:flutter/foundation.dart'; import 'package:mini_tias/services/file_service.dart'; import 'package:mini_tias/services/permission_service.dart'; import 'package:mini_tias/services/raw_capture_service.dart'; /// カメラの初期化・プレビュー制御・撮影実行・ライフサイクルを管理する. class CameraProvider extends ChangeNotifier { final PermissionService _permissionService = PermissionService(); final FileService _fileService = FileService(); final RawCaptureService _rawCaptureService = RawCaptureService(); CameraController? _controller; bool _isInitialized = false; String? _errorMessage; bool _permissionDenied = false; bool _permissionPermanentlyDenied = false; String? _lastSavedFileName; bool _isSaving = false; bool _imageStreamActive = false; Map? _latestFrame; CameraController? get controller => _controller; bool get isInitialized => _isInitialized; String? get errorMessage => _errorMessage; bool get permissionDenied => _permissionDenied; bool get permissionPermanentlyDenied => _permissionPermanentlyDenied; String? get lastSavedFileName => _lastSavedFileName; bool get isSaving => _isSaving; /// カメラを初期化する. Future initialize() async { final granted = await _permissionService.requestCamera(); if (!granted) { _permissionDenied = true; _permissionPermanentlyDenied = await _permissionService .isCameraPermanentlyDenied(); _errorMessage = 'カメラの権限が必要です'; notifyListeners(); return; } _permissionDenied = false; _permissionPermanentlyDenied = false; try { final cameras = await availableCameras(); final frontCamera = cameras.firstWhere( (c) => c.lensDirection == CameraLensDirection.front, ); _controller = CameraController( frontCamera, ResolutionPreset.max, enableAudio: false, ); await _controller!.initialize(); await _controller!.setFlashMode(FlashMode.off); _isInitialized = true; _errorMessage = null; _startImageStream(); } catch (e) { _errorMessage = 'カメラの初期化に失敗しました: $e'; _isInitialized = false; } notifyListeners(); } /// 画像ストリームを開始して最新フレームを保持する. void _startImageStream() { if (_controller == null || _imageStreamActive) return; try { _controller!.startImageStream((CameraImage image) { _latestFrame = { 'width': image.width, 'height': image.height, 'yPlane': Uint8List.fromList(image.planes[0].bytes), 'uPlane': Uint8List.fromList(image.planes[1].bytes), 'vPlane': Uint8List.fromList(image.planes[2].bytes), 'yRowStride': image.planes[0].bytesPerRow, 'uvRowStride': image.planes[1].bytesPerRow, 'uvPixelStride': image.planes[1].bytesPerPixel ?? 1, }; }); _imageStreamActive = true; } catch (e) { debugPrint('画像ストリーム開始失敗: $e'); } } /// 画像ストリームを停止する. Future _stopImageStream() async { if (!_imageStreamActive || _controller == null) return; try { await _controller!.stopImageStream(); } catch (_) { // 既に停止済みの場合等は無視 } _imageStreamActive = false; } /// 最新のプレビューフレームを JPEG として取得する(オーバーレイなし). Future capturePreviewSnapshot() async { if (_latestFrame == null) return null; final frame = _latestFrame!; return _rawCaptureService.convertYuvToJpeg( width: frame['width'] as int, height: frame['height'] as int, yPlane: frame['yPlane'] as Uint8List, uPlane: frame['uPlane'] as Uint8List, vPlane: frame['vPlane'] as Uint8List, yRowStride: frame['yRowStride'] as int, uvRowStride: frame['uvRowStride'] as int, uvPixelStride: frame['uvPixelStride'] as int, rotation: 90, mirror: true, ); } /// Camera2 API でフル解像度 YUV キャプチャし,PNG で保存する. Future takePicture() async { if (!_isInitialized || _isSaving) return; _isSaving = true; _isInitialized = false; notifyListeners(); try { // ストレージ権限の確認 final storageGranted = await _permissionService.requestStorage(); if (!storageGranted) { _errorMessage = 'ストレージの権限が必要です'; _isSaving = false; notifyListeners(); return; } // 画像ストリームを停止してカメラを解放 await _stopImageStream(); await _controller?.dispose(); _controller = null; // カメラが完全に解放されるまで待つ await Future.delayed(const Duration(milliseconds: 500)); // Camera2 API でフル解像度キャプチャ(ネイティブで YUV→PNG 変換) final pngBytes = await _rawCaptureService.captureFullResolutionPng(); // PNG バイト列をファイルに保存 final savedPath = await _fileService.saveImage(pngBytes); // MediaStore に登録(PC から MTP で見えるようにする) await _rawCaptureService.scanFile(savedPath); _lastSavedFileName = savedPath.split('/').last; _errorMessage = null; } catch (e) { _errorMessage = '撮影に失敗しました: $e'; _lastSavedFileName = null; } _isSaving = false; notifyListeners(); // プレビューを再開 await initialize(); } /// カメラリソースを解放する. Future disposeCamera() async { await _stopImageStream(); _latestFrame = null; _controller?.dispose(); _controller = null; _isInitialized = false; notifyListeners(); } /// アプリの設定画面を開く(権限が永久拒否された場合). Future openSettings() async { await _permissionService.openSettings(); } @override void dispose() { _controller?.dispose(); super.dispose(); } }