Newer
Older
MiniTias / lib / providers / camera_provider.dart
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';

/// カメラの初期化・プレビュー制御・撮影実行・ライフサイクルを管理する.
class CameraProvider extends ChangeNotifier {
  final PermissionService _permissionService = PermissionService();
  final FileService _fileService = FileService();

  CameraController? _controller;
  bool _isInitialized = false;
  String? _errorMessage;
  bool _permissionDenied = false;
  bool _permissionPermanentlyDenied = false;
  String? _lastSavedFileName;
  CameraImage? _latestFrame;
  bool _isSaving = false;

  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,
        imageFormatGroup: ImageFormatGroup.yuv420,
      );

      await _controller!.initialize();
      await _controller!.setFlashMode(FlashMode.off);

      // 画像ストリームを開始し,最新フレームを保持する
      await _controller!.startImageStream((image) {
        _latestFrame = image;
      });

      _isInitialized = true;
      _errorMessage = null;
    } catch (e) {
      _errorMessage = 'カメラの初期化に失敗しました: $e';
      _isInitialized = false;
    }

    notifyListeners();
  }

  /// 最新フレームを PNG 形式で保存する.
  ///
  /// YUV 生データから直接 PNG に変換するため画質劣化がない.
  Future<void> takePicture() async {
    if (_latestFrame == null || _isSaving) return;

    _isSaving = true;
    notifyListeners();

    try {
      // ストレージ権限の確認
      final storageGranted = await _permissionService.requestStorage();
      if (!storageGranted) {
        _errorMessage = 'ストレージの権限が必要です';
        _isSaving = false;
        notifyListeners();
        return;
      }

      final savedPath = await _fileService.saveImageFromYuv(_latestFrame!);

      _lastSavedFileName = savedPath.split('/').last;
      _errorMessage = null;
    } catch (e) {
      _errorMessage = '撮影に失敗しました: $e';
      _lastSavedFileName = null;
    }

    _isSaving = false;
    notifyListeners();
  }

  /// カメラリソースを解放する.
  void disposeCamera() {
    _latestFrame = null;
    _controller?.dispose();
    _controller = null;
    _isInitialized = false;
    notifyListeners();
  }

  /// アプリの設定画面を開く(権限が永久拒否された場合).
  Future<void> openSettings() async {
    await _permissionService.openSettings();
  }

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }
}