import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:smtias_capture/services/raw_capture_service.dart'; /// 画像一覧の取得・キャッシュ・削除操作を管理する. class GalleryProvider extends ChangeNotifier { /// MediaStore 通知に使うサービスはコンストラクタで差し替え可能(省略時は既定実装). GalleryProvider({RawCaptureService? rawCaptureService}) : _rawCaptureService = rawCaptureService ?? RawCaptureService(); static const _basePath = '/storage/emulated/0/Pictures/MiniTIAS'; final RawCaptureService _rawCaptureService; List _images = []; bool _isLoading = false; List get images => _images; bool get isLoading => _isLoading; /// Pictures/MiniTIAS/ 内の PNG ファイルと QM プレビュー JPEG を取得し,新しい順に並べる. Future loadImages() async { _isLoading = true; notifyListeners(); try { final directory = Directory(_basePath); if (!await directory.exists()) { _images = []; } else { final files = await directory .list() .where((entity) { final lower = entity.path.toLowerCase(); return entity is File && (lower.endsWith('.png') || lower.endsWith('.preview.jpg')); }) .cast() .toList(); // 更新日時の新しい順にソート files.sort((a, b) { final aStat = a.statSync(); final bStat = b.statSync(); return bStat.modified.compareTo(aStat.modified); }); _images = files; } } catch (e) { debugPrint('画像一覧の取得に失敗: $e'); _images = []; } _isLoading = false; notifyListeners(); } /// 指定したファイルを削除し,一覧から除去する. /// /// QM プレビュー (*.preview.jpg) を削除する場合,同じ baseName の /// .dng ファイルと .meta.json ファイルも連動して削除する. /// .png ファイルは単独削除(後方互換). Future deleteImage(File file) async { final path = file.path; final lower = path.toLowerCase(); // 削除対象ファイル一覧を確定 final filesToDelete = [file]; if (lower.endsWith('.preview.jpg')) { final base = path.substring(0, path.length - '.preview.jpg'.length); filesToDelete.add(File('$base.dng')); filesToDelete.add(File('$base.meta.json')); } try { final deletedPaths = []; for (final f in filesToDelete) { if (await f.exists()) { await f.delete(); deletedPaths.add(f.path); } } // 元ファイルが消えたら成功とみなす if (!await file.exists()) { _images.removeWhere((f) => f.path == file.path); notifyListeners(); // 物理削除したファイルを MediaStore に再スキャンさせ,削除済みエントリ // (MTP や他アプリから見える幽霊エントリ)を索引から除去する. await _notifyMediaStoreDeleted(deletedPaths); return true; } return false; } catch (e) { debugPrint('画像の削除に失敗: $e'); return false; } } /// 物理削除済みのパスを MediaStore に再スキャンさせ,索引から除去する. /// /// 存在しなくなったパスへの `scanFile` は,MediaStore の該当エントリを削除する /// (Android の挙動).通知の失敗は削除自体の成否に影響させない(握りつぶす). Future _notifyMediaStoreDeleted(List deletedPaths) async { for (final path in deletedPaths) { try { await _rawCaptureService.scanFile(path); } catch (e) { debugPrint('MediaStore 更新の通知に失敗 ($path): $e'); } } } }