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<String, dynamic>? _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<void> 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<void> _stopImageStream() async {
if (!_imageStreamActive || _controller == null) return;
try {
await _controller!.stopImageStream();
} catch (_) {
// 既に停止済みの場合等は無視
}
_imageStreamActive = false;
}
/// 最新のプレビューフレームを JPEG として取得する(オーバーレイなし).
Future<Uint8List?> 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<void> 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 キャプチャ
final yuvData = await _rawCaptureService.captureFullResolutionYuv();
// PNG に変換して保存
final savedPath = await _fileService.saveImageFromYuvData(yuvData);
// MediaStore に登録(PC から MTP で見えるようにする)
await _rawCaptureService.scanFile(savedPath);
_lastSavedFileName = savedPath.split('/').last;
_errorMessage = null;
} catch (e) {
_errorMessage = '撮影に失敗しました: $e';
_lastSavedFileName = null;
}
_isSaving = false;
notifyListeners();
// プレビューを再開
await initialize();
}
/// カメラリソースを解放する.
void disposeCamera() {
_stopImageStream();
_latestFrame = null;
_controller?.dispose();
_controller = null;
_isInitialized = false;
notifyListeners();
}
/// アプリの設定画面を開く(権限が永久拒否された場合).
Future<void> openSettings() async {
await _permissionService.openSettings();
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
}