diff --git a/CLAUDE.md b/CLAUDE.md index f432105..f1102c8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,15 +1,27 @@ -# プロジェクト名 +# MiniTIAS - +千葉大学 TIAS(舌画像撮影・解析システム)のスマートフォン版.Flutter で開発し,初期は撮影・保存に特化する. ## 開発進捗 -現在の進捗: GUIDE_01 に従いプロジェクト立ち上げ中 +現在の進捗: 仕様設計・規約整備・開発計画 完了.次は Step 1(プロジェクト基盤)の実装. ※ ステップ完了時にここを更新すること. ## 必須ルール(コード実装時) - +### コーディング規約(GUIDE_05 準拠) + +- Effective Dart に準拠.プロジェクト固有ルールは GUIDE_05 を参照 +- `dart format` + `dart analyze` を必ず実行してからコミットする +- 1 ファイル 1 クラス.命名は Provider / Service / Screen の接尾辞ルールに従う +- エラー処理: Service → Provider → Screen の順で伝播し,画面でユーザーに通知 + +### コミット前チェック(GUIDE_07 準拠) + +1. `dart analyze` — 警告・エラー 0 件 +2. `dart format .` — 差分なし +3. `flutter test` — 全テスト通過 +4. ドキュメント更新(仕様変更がある場合) ### Git 運用(GUIDE_04 準拠) @@ -36,16 +48,21 @@ - ドキュメント作成規約: docs/01_GUIDE/GUIDE_02_ドキュメント作成ガイド.md - ファイル命名規則: docs/01_GUIDE/GUIDE_03_ファイル命名規則.md - Git 運用ルール: docs/01_GUIDE/GUIDE_04_Git運用ルール.md -- ※ コーディング規約,テスト方針等はプロジェクト立ち上げ時に作成する(GUIDE_01 参照) +- コーディング規約: docs/01_GUIDE/GUIDE_05_コーディング規約.md +- テスト方針: docs/01_GUIDE/GUIDE_06_テスト方針.md +- 実装完了フロー: docs/01_GUIDE/GUIDE_07_実装完了フロー.md ### 02_ENV(環境) - +- 技術スタック: docs/02_ENV/ENV_01_技術スタック.md +- 環境構築手順: docs/02_ENV/ENV_02_環境構築手順.md +- 管理者用環境構築手順: docs/02_ENV/ENV_03_管理者用環境構築手順.md ### 03_PLAN(計画) - +- 要件定義書: docs/03_PLAN/PLAN_01_要件定義書.md +- 開発ステップ: docs/03_PLAN/PLAN_02_開発ステップ.md ### 04_SPEC(仕様・設計) - +- 画面機能仕様書: docs/04_SPEC/SPEC_01_画面機能仕様書.md diff --git "a/docs/01_GUIDE/GUIDE_05_\343\202\263\343\203\274\343\203\207\343\202\243\343\203\263\343\202\260\350\246\217\347\264\204.md" "b/docs/01_GUIDE/GUIDE_05_\343\202\263\343\203\274\343\203\207\343\202\243\343\203\263\343\202\260\350\246\217\347\264\204.md" new file mode 100644 index 0000000..47a2c96 --- /dev/null +++ "b/docs/01_GUIDE/GUIDE_05_\343\202\263\343\203\274\343\203\207\343\202\243\343\203\263\343\202\260\350\246\217\347\264\204.md" @@ -0,0 +1,125 @@ +# コーディング規約 (Coding Standards) + +本ドキュメントでは,MiniTIAS プロジェクトの Dart / Flutter コーディング規約を定義する. +基本方針として [Effective Dart](https://dart.dev/effective-dart) に準拠し,プロジェクト固有のルールのみ本書で定める. + +## フォーマット (Formatting) + +- `dart format` のデフォルト設定に従う(行幅 80 文字) +- VS Code の「保存時フォーマット」を有効にする(`.vscode/settings.json` で設定済み) +- 手動でのフォーマット調整は行わない + +## 命名規則 (Naming Conventions) + +Effective Dart の命名規則に従う.プロジェクト固有の補足は以下の通り. + +| 対象 | 規則 | 例 | +| --- | --- | --- | +| ファイル名 | snake_case | `camera_provider.dart` | +| クラス名 | UpperCamelCase | `CameraProvider` | +| 変数・関数 | lowerCamelCase | `takePhoto()` | +| 定数 | lowerCamelCase | `defaultImageQuality` | +| プライベート | 先頭に `_` | `_controller` | +| Provider | `〇〇Provider` | `CameraProvider` | +| Service | `〇〇Service` | `FileService` | +| Screen(画面) | `〇〇Screen` | `CaptureScreen` | +| Widget(部品) | 機能を表す名前 | `ShutterButton` | + +## import 順序 (Import Order) + +以下の順序で記述し,各グループ間は空行で区切る. + +```dart +// 1. Dart SDK +import 'dart:async'; +import 'dart:io'; + +// 2. Flutter SDK +import 'package:flutter/material.dart'; + +// 3. 外部パッケージ(pub.dev) +import 'package:camera/camera.dart'; +import 'package:provider/provider.dart'; + +// 4. プロジェクト内 +import 'package:mini_tias/providers/camera_provider.dart'; +import 'package:mini_tias/services/file_service.dart'; +``` + +※ `dart fix --apply` および `dart format` で自動整列されるため,手動調整は不要. + +## ディレクトリ構造 (Directory Structure) + +[SPEC_01_画面機能仕様書](../04_SPEC/SPEC_01_画面機能仕様書.md) のアーキテクチャセクションで定義したディレクトリ構成に従う. + +```text +lib/ +├── main.dart # エントリポイント,Provider 登録 +├── app.dart # MaterialApp,テーマ +├── providers/ # 状態管理(ChangeNotifier) +├── screens/ # 画面(Scaffold 単位) +├── widgets/ # 再利用可能な UI 部品 +└── services/ # 外部リソースとの橋渡し +``` + +### 配置ルール + +- **1 ファイル 1 クラス** を原則とする.ファイル名はクラス名の snake_case 版 +- **screens/**: 各画面に対応する 1 ファイル.画面固有のロジックは対応する Provider に置く +- **widgets/**: 複数画面で再利用する UI 部品,または画面ファイルが大きくなった場合に切り出す部品 +- **providers/**: 画面または機能ごとに 1 ファイル.ChangeNotifier を継承する +- **services/**: プラットフォーム API やファイルシステムへのアクセスを担う + +## 型の使い方 (Type Usage) + +- ローカル変数は型推論(`var` / `final`)を活用する +- 関数の引数・戻り値には明示的に型を記述する +- `dynamic` は避け,`Object?` を使用する +- `final` をデフォルトとし,再代入が必要な場合のみ `var` を使用する + +## エラーハンドリング (Error Handling) + +### 基本方針 + +- **Service 層**: 例外をキャッチし,呼び出し元に結果を返す.想定外の例外はそのまま上位に伝播させる +- **Provider 層**: Service の結果を受け取り,状態(エラーメッセージ等)として保持する +- **Screen 層**: Provider の状態を参照し,ユーザーにフィードバックを表示する(スナックバー等) + +### 想定するエラー + +| エラー | 発生箇所 | 対処 | +| --- | --- | --- | +| カメラ初期化失敗 | CameraProvider | エラーメッセージを画面に表示 | +| パーミッション拒否 | PermissionService | 設定画面への誘導を表示 | +| ストレージ書き込み失敗 | FileService | スナックバーでエラー通知 | +| ファイル削除失敗 | FileService | スナックバーでエラー通知 | + +### ログ出力 + +- 開発中は `debugPrint()` を使用する(リリースビルドでは自動的に無効化される) +- `print()` は使用しない + +## ドキュメント (Documentation) + +### コード内コメント + +- **DartDoc コメント(`///`)**: 公開 API(public なクラス・メソッド)に記述する +- **実装コメント(`//`)**: ロジックが自明でない箇所にのみ記述する +- 「何をしているか」ではなく「なぜそうしているか」を書く + +### DartDoc の書き方 + +```dart +/// 撮影画像を PNG 形式で共有ストレージに保存する. +/// +/// [imageBytes] は JPEG エンコード済みの画像データ. +/// 保存先は `Pictures/MiniTIAS/` ディレクトリ. +/// 同秒の重複ファイルが存在する場合は連番サフィックスを付与する. +Future saveImage(Uint8List imageBytes) async { + // ... +} +``` + +- 1 行目は動詞で始める簡潔な要約(句点で終わる) +- 詳細が必要な場合は空行を挟んで続ける +- パラメータの説明は `[paramName]` 記法を使用する diff --git "a/docs/01_GUIDE/GUIDE_06_\343\203\206\343\202\271\343\203\210\346\226\271\351\207\235.md" "b/docs/01_GUIDE/GUIDE_06_\343\203\206\343\202\271\343\203\210\346\226\271\351\207\235.md" new file mode 100644 index 0000000..ba62567 --- /dev/null +++ "b/docs/01_GUIDE/GUIDE_06_\343\203\206\343\202\271\343\203\210\346\226\271\351\207\235.md" @@ -0,0 +1,92 @@ +# テスト方針 (Testing Strategy) + +本ドキュメントでは,MiniTIAS プロジェクトのテスト方針を定義する. + +## 基本方針 (Principles) + +- 初期フェーズではカメラ・ストレージ等のハードウェア依存が大きいため,**実機での手動確認を主体** とする +- ハードウェアに依存しないロジック(ファイル命名,重複回避等)には **Unit テスト** を書く +- テストは `flutter test` で実行できる状態を維持する + +## テストの種類と対象 (Test Types) + +| 種類 | 対象 | 実施方法 | +| --- | --- | --- | +| Unit テスト | Service 層のロジック(ファイル命名,重複回避等) | `flutter test` | +| Widget テスト | 画面の基本的な UI 構成(ボタンの存在等) | `flutter test` | +| 手動確認 | カメラプレビュー,撮影,保存,削除の E2E 動作 | 実機(AQUOS sense3) | + +※ Integration テスト(`flutter drive`)は初期フェーズでは導入しない. + +## Unit テスト + +### 対象 + +- `FileService` — ファイル名生成ロジック,重複回避ロジック +- `PermissionService` — パーミッション状態の判定ロジック(モック使用) + +### ファイル配置 + +```text +test/ +├── services/ +│ ├── file_service_test.dart +│ └── permission_service_test.dart +└── providers/ + └── gallery_provider_test.dart +``` + +### 命名規則 + +- テストファイル名: `{対象ファイル名}_test.dart` +- テストグループ: `group('クラス名',)` でクラス単位にまとめる +- テスト名: 日本語で「〜の場合,〜する」形式 + +```dart +group('FileService', () { + test('同秒のファイルが存在しない場合,サフィックスなしのファイル名を返す', () { + // ... + }); + + test('同秒のファイルが存在する場合,連番サフィックスを付与する', () { + // ... + }); +}); +``` + +## Widget テスト + +### 対象 + +- 画面に必要な UI 要素が存在すること(シャッターボタン,ナビゲーションバー等) +- ボタンタップ時に Provider のメソッドが呼ばれること(モック使用) + +### 方針 + +- カメラプレビューはモック化する(`camera` パッケージのウィジェットは Widget テストで動作しない) +- Provider は `ChangeNotifierProvider.value` でモックを注入する + +## 手動確認 (Manual Testing) + +### 確認項目 + +実装完了時に以下を実機で確認する. + +- [ ] カメラプレビューが正しく表示される(180° 回転) +- [ ] シャッターボタンで撮影できる +- [ ] 撮影画像が `Pictures/MiniTIAS/` に PNG で保存される +- [ ] ファイル名が命名規則に従っている +- [ ] 連続撮影が正常に動作する +- [ ] 一覧画面にサムネイルが表示される +- [ ] サムネイルタップで拡大表示される +- [ ] 画像の削除ができる +- [ ] パーミッション拒否時に適切なメッセージが表示される +- [ ] アプリをバックグラウンド→復帰してもカメラが正常に動作する + +## テスト実行タイミング (When to Test) + +| タイミング | 実施内容 | +| --- | --- | +| 機能実装完了時 | 対応する Unit / Widget テストを作成・実行 | +| コミット前 | `flutter test` を実行し全テストが通ることを確認 | +| PR 作成前 | 手動確認項目のうち関連するものを実機で確認 | diff --git "a/docs/01_GUIDE/GUIDE_07_\345\256\237\350\243\205\345\256\214\344\272\206\343\203\225\343\203\255\343\203\274.md" "b/docs/01_GUIDE/GUIDE_07_\345\256\237\350\243\205\345\256\214\344\272\206\343\203\225\343\203\255\343\203\274.md" new file mode 100644 index 0000000..3642189 --- /dev/null +++ "b/docs/01_GUIDE/GUIDE_07_\345\256\237\350\243\205\345\256\214\344\272\206\343\203\225\343\203\255\343\203\274.md" @@ -0,0 +1,79 @@ +# 実装完了フロー (Implementation Completion Flow) + +本ドキュメントでは,機能実装の完了からコミット・PR 作成までの手順を定義する. +リファクタリングの判断基準もここで定める. + +## コミット前チェックリスト (Pre-commit Checklist) + +機能実装が完了したら,以下を順番に実施する. + +### 1. 静的解析 + +```bash +dart analyze +``` + +- 警告・エラーが 0 件であることを確認する +- `// ignore:` で抑制する場合は理由をコメントに記述する + +### 2. フォーマット + +```bash +dart format . +``` + +- 差分がないことを確認する(VS Code の保存時フォーマットが有効なら通常は差分なし) + +### 3. テスト実行 + +```bash +flutter test +``` + +- 全テストが通ることを確認する +- 新しいロジックを追加した場合はテストも追加する([GUIDE_06](GUIDE_06_テスト方針.md) 参照) + +### 4. ドキュメント確認 + +- 仕様変更がある場合は `docs/` のドキュメントを先に更新する +- 新しいファイルを追加した場合は CLAUDE.md の参照パスを更新する + +### 5. コミット・PR + +- [GUIDE_04](GUIDE_04_Git運用ルール.md) に従ってコミット・push・PR 作成を行う + +## 実装完了の基準 (Definition of Done) + +以下をすべて満たした場合に「実装完了」とする. + +- [ ] 要求された機能が動作する +- [ ] `dart analyze` で警告・エラーが 0 件 +- [ ] `dart format` で差分がない +- [ ] `flutter test` で全テストが通る +- [ ] 新しいロジックに対するテストが追加されている +- [ ] 関連ドキュメントが更新されている + +## リファクタリング方針 (Refactoring Policy) + +### 実施タイミング + +- **機能実装中**: 実装対象のコードに限り,必要に応じてリファクタリングする +- **機能実装後**: 動作確認が完了してからリファクタリングを行う(動くコードを壊さない) +- **独立した作業として**: 機能追加と同じコミットに混ぜない.`[clean]` タグで別コミットにする + +### 判断基準 + +以下のいずれかに該当する場合にリファクタリングを検討する. + +| 基準 | 例 | +| --- | --- | +| 同じコードが 3 箇所以上に重複している | 共通ロジックのメソッド抽出 | +| 1 ファイルが 300 行を超えている | クラスやウィジェットの分割 | +| 1 メソッドが 50 行を超えている | メソッドの分割 | +| クラスの責務が 2 つ以上混在している | クラスの分割 | + +### やらないこと + +- 動作に問題のないコードの「予防的」リファクタリング +- 将来の要件を見越した過度な抽象化 +- 機能追加と同一コミットでのリファクタリング diff --git "a/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" "b/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" new file mode 100644 index 0000000..45634d9 --- /dev/null +++ "b/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" @@ -0,0 +1,149 @@ +# 開発ステップ (Development Steps) + +[SPEC_01_画面機能仕様書](../04_SPEC/SPEC_01_画面機能仕様書.md) の仕様をもとに,実装の順序とステップを定義する. +各ステップは 1 つのブランチ・PR に対応させる. + +## ステップ一覧 (Overview) + +| # | ステップ | 概要 | 依存 | +| --- | --- | --- | --- | +| 1 | プロジェクト基盤 | アプリ骨格,テーマ,画面遷移,Provider 登録 | — | +| 2 | カメラプレビュー | インカメラのライブプレビュー表示(180° 回転) | 1 | +| 3 | 撮影・保存 | シャッターボタン,PNG 保存,ファイル命名 | 2 | +| 4 | 一覧・削除 | サムネイルグリッド,拡大表示,画像削除 | 3 | +| 5 | 仕上げ | パーミッション UX 改善,エラーハンドリング,手動テスト | 4 | + +## Step 1: プロジェクト基盤 (App Foundation) + +### 目的 + +アプリの骨格を構築し,画面遷移が動作する状態にする. + +### 実装内容 + +- `main.dart` — `runApp()` で Provider を登録し,アプリを起動する +- `app.dart` — `MaterialApp` の定義,テーマ設定 +- `screens/capture_screen.dart` — 撮影画面の空のスキャフォールド +- `screens/gallery_screen.dart` — 一覧画面の空のスキャフォールド +- `BottomNavigationBar` による 2 タブ切り替え +- UI 全体の 180° 回転(`Transform.rotate`) + +### 完了基準 + +- アプリが起動し,2 つのタブで画面を切り替えられる +- UI が 180° 回転して表示される +- `dart analyze` / `dart format` がクリーン + +### ブランチ名 + +`feature/app-foundation` + +## Step 2: カメラプレビュー (Camera Preview) + +### 目的 + +インカメラのライブプレビューを撮影画面に表示する. + +### 実装内容 + +- `services/permission_service.dart` — カメラ権限の確認・要求 +- `providers/camera_provider.dart` — カメラの初期化・プレビュー制御・ライフサイクル管理 +- `widgets/camera_preview.dart` — `CameraPreview` ウィジェット +- `screens/capture_screen.dart` — カメラプレビューの組み込み +- パーミッション拒否時のエラー表示(基本的なメッセージのみ) + +### 完了基準 + +- 撮影画面でインカメラのライブプレビューが表示される +- アプリをバックグラウンド→復帰してもカメラが正常に動作する +- カメラ権限の拒否時にメッセージが表示される + +### ブランチ名 + +`feature/camera-preview` + +## Step 3: 撮影・保存 (Capture & Save) + +### 目的 + +シャッターボタンで撮影し,PNG 形式でストレージに保存する. + +### 実装内容 + +- `services/file_service.dart` — PNG 保存,ファイル名生成,重複回避ロジック +- `widgets/shutter_button.dart` — シャッターボタン UI +- `providers/camera_provider.dart` — 撮影実行メソッドの追加 +- `screens/capture_screen.dart` — シャッターボタンの組み込み,撮影成功時のスナックバー +- ストレージ権限の確認・要求 +- MediaStore への通知 + +### テスト + +- `FileService` のファイル名生成ロジックの Unit テスト +- `FileService` の重複回避ロジックの Unit テスト + +### 完了基準 + +- シャッターボタンタップで撮影できる +- `Pictures/MiniTIAS/` に `MiniTIAS_YYYYMMDD_HHmmss.png` 形式で保存される +- 連続撮影で同秒の場合,連番サフィックスが付与される +- 撮影成功時にスナックバーが表示される + +### ブランチ名 + +`feature/capture-save` + +## Step 4: 一覧・削除 (Gallery & Delete) + +### 目的 + +撮影済み画像の一覧表示,拡大確認,削除機能を実装する. + +### 実装内容 + +- `providers/gallery_provider.dart` — 画像一覧の取得・キャッシュ・削除操作 +- `widgets/image_grid.dart` — 3 列サムネイルグリッド +- `widgets/image_detail_dialog.dart` — 拡大表示ダイアログ(ピンチズーム,削除ボタン) +- `screens/gallery_screen.dart` — グリッド・ダイアログの組み込み,空状態メッセージ + +### テスト + +- `GalleryProvider` の一覧取得・削除ロジックの Unit テスト + +### 完了基準 + +- 一覧画面に撮影済み画像がサムネイルで表示される(新しい順) +- サムネイルタップで拡大表示ダイアログが開く +- ダイアログ内の削除ボタンで画像を削除できる(確認ダイアログあり) +- 画像 0 件時に空状態メッセージが表示される + +### ブランチ名 + +`feature/gallery-delete` + +## Step 5: 仕上げ (Polish) + +### 目的 + +パーミッション UX の改善,エラーハンドリングの強化,全体の動作確認を行う. + +### 実装内容 + +- パーミッション拒否時の設定画面誘導ボタン +- 各種エラー時のスナックバー表示の統一 +- 撮影画面↔一覧画面切り替え時のデータリフレッシュ + +### テスト + +- 全 Unit / Widget テストの通過確認 +- [GUIDE_06](../01_GUIDE/GUIDE_06_テスト方針.md) の手動確認項目を実機で確認 + +### 完了基準 + +- GUIDE_06 の手動確認項目がすべて OK +- `dart analyze` / `flutter test` がクリーン +- 白板・舌模型の撮影ができ,PC から USB 経由で画像にアクセスできる + +### ブランチ名 + +`feature/polish-permissions` diff --git "a/docs/04_SPEC/SPEC_01_\347\224\273\351\235\242\346\251\237\350\203\275\344\273\225\346\247\230\346\233\270.md" "b/docs/04_SPEC/SPEC_01_\347\224\273\351\235\242\346\251\237\350\203\275\344\273\225\346\247\230\346\233\270.md" new file mode 100644 index 0000000..b68984e --- /dev/null +++ "b/docs/04_SPEC/SPEC_01_\347\224\273\351\235\242\346\251\237\350\203\275\344\273\225\346\247\230\346\233\270.md" @@ -0,0 +1,317 @@ +# 画面機能仕様書 (Screen & Function Specification) + +本ドキュメントでは,MiniTIAS アプリの画面設計,画面遷移,カメラ制御,ファイル管理,およびアーキテクチャを定義する. + +## 画面一覧 (Screen List) + +| ID | 画面名 | 概要 | +| --- | --- | --- | +| S-01 | 撮影画面 | インカメラのライブプレビューを表示し,撮影を行う | +| S-02 | 一覧画面 | 撮影済み画像のサムネイル一覧を表示し,確認・削除を行う | + +※ 初期バージョンでは 2 画面構成とする. + +## 画面遷移 (Screen Transition) + +```text +[アプリ起動] + │ + ▼ +┌──────────┐ BottomNavigationBar ┌──────────┐ +│ S-01 │ ◄──────────────────────────────► │ S-02 │ +│ 撮影画面 │ タブ切り替え │ 一覧画面 │ +└──────────┘ └──────────┘ + │ + │ サムネイルタップ + ▼ + ┌──────────┐ + │ 拡大表示 │ + │ (ダイアログ) │ + └──────────┘ +``` + +- アプリ起動時のデフォルト画面は **撮影画面(S-01)** とする +- 画面切り替えには `BottomNavigationBar` を使用する(タブ 2 つ: 撮影 / 一覧) +- 画像の拡大表示はダイアログ(`showDialog`)で実装し,独立画面としない + +## 撮影画面 (S-01: Capture Screen) + +### レイアウト + +```text +┌─────────────────────────┐ +│ BottomNavBar │ ← 画面下部(通常位置) +├─────────────────────────┤ +│ │ +│ │ +│ カメラプレビュー │ ← 上下反転表示(180°回転) +│ (画面全体) │ +│ │ +│ │ +├─────────────────────────┤ +│ [シャッターボタン] │ ← 画面上部(端末を逆さに置くため) +└─────────────────────────┘ +``` + +※ 端末を逆さに置いて使用するため,UI 全体を 180° 回転する.操作者から見ると通常の上下配置に見える. + +### UI 要素 + +| 要素 | 仕様 | +| --- | --- | +| カメラプレビュー | インカメラ(前面カメラ)のライブ映像.UI 全体を 180° 回転して表示 | +| シャッターボタン | 丸型ボタン.タップで撮影実行.連続タップ可(連写対応) | +| BottomNavigationBar | 撮影タブ(アクティブ)/ 一覧タブ | + +### 動作仕様 + +- **起動時**: カメラの初期化を行い,プレビューを開始する +- **撮影**: シャッターボタンタップで静止画をキャプチャし,PNG 形式で保存する +- **連続撮影**: 前回の保存完了を待たずに次の撮影が可能.撮影中はシャッターボタンの操作を受け付ける +- **フィードバック**: 撮影成功時にスナックバーで「保存しました: [ファイル名]」を表示する +- **エラー時**: カメラ初期化失敗やストレージ書き込み失敗時は,エラーメッセージをスナックバーで表示する + +## 一覧画面 (S-02: Gallery Screen) + +### レイアウト + +```text +┌─────────────────────────┐ +│ BottomNavBar │ +├─────────────────────────┤ +│ ┌─────┐ ┌─────┐ ┌─────┐│ +│ │ │ │ │ │ ││ +│ │ img │ │ img │ │ img ││ +│ │ │ │ │ │ ││ +│ └─────┘ └─────┘ └─────┘│ +│ ┌─────┐ ┌─────┐ ┌─────┐│ +│ │ │ │ │ │ ││ +│ │ img │ │ img │ │ img ││ +│ │ │ │ │ │ ││ +│ └─────┘ └─────┘ └─────┘│ +│ ... │ +├─────────────────────────┤ +│ [画面上部エリア] │ ← 逆さ配置のため上部が操作側 +└─────────────────────────┘ +``` + +※ 撮影画面と同様に UI 全体を 180° 回転して表示する. + +### UI 要素 + +| 要素 | 仕様 | +| --- | --- | +| サムネイルグリッド | 3 列のグリッド表示.新しい画像が先頭(降順) | +| サムネイル | 正方形クロップ.タップで拡大表示 | +| ファイル名ラベル | 各サムネイル下部にファイル名を表示 | +| BottomNavigationBar | 撮影タブ / 一覧タブ(アクティブ) | + +### 拡大表示ダイアログ + +| 要素 | 仕様 | +| --- | --- | +| 画像 | 元画像をフル解像度で表示.ピンチ操作でズーム可能 | +| ファイル名 | ダイアログ上部にファイル名を表示 | +| 削除ボタン | ダイアログ内に配置.タップで削除確認ダイアログを表示 | +| 閉じるボタン | ダイアログ外タップまたは閉じるボタンで閉じる | + +### 動作仕様 + +- **画面表示時**: `Pictures/MiniTIAS/` ディレクトリ内の PNG ファイルを走査し,一覧を構築する +- **リフレッシュ**: 撮影画面から戻った際に一覧を再取得する +- **削除**: 削除確認ダイアログで「削除」を選択すると,ファイルをストレージから削除し,一覧から除去する +- **空状態**: 画像が 0 件の場合は「撮影した画像がありません」のメッセージを表示する + +## カメラ制御仕様 (Camera Control) + +### カメラ設定 + +| 項目 | 値 | +| --- | --- | +| 使用カメラ | フロントカメラ(インカメラ) | +| 解像度 | `ResolutionPreset.max`(カメラが対応する最大解像度) | +| フォーカス | オートフォーカス(デフォルト動作) | +| フラッシュ | OFF(LED ライトはアタッチメント側で制御) | +| 画像フォーマット | カメラ出力は JPEG → アプリ内で PNG に変換して保存 | + +### プレビュー表示 + +- `camera` パッケージの `CameraPreview` ウィジェットを使用する +- UI 全体を `Transform.rotate(angle: pi)` で 180° 回転する +- プレビューのアスペクト比はカメラのネイティブ比率を維持する + +### 撮影フロー + +```text +シャッターボタンタップ + │ + ▼ +CameraController.takePicture() + │ + ▼ +一時ファイル(XFile)取得 + │ + ▼ +PNG エンコード(image パッケージ) + │ + ▼ +ファイル名生成(命名規則に従う) + │ + ▼ +Pictures/MiniTIAS/ に保存 + │ + ▼ +MediaStore 通知(ギャラリーアプリへの反映) + │ + ▼ +スナックバーで保存完了を通知 +``` + +### ライフサイクル管理 + +- `WidgetsBindingObserver` を使用し,アプリのライフサイクルを監視する +- **バックグラウンド遷移時**(`paused`): カメラリソースを解放する(`CameraController.dispose()`) +- **フォアグラウンド復帰時**(`resumed`): カメラを再初期化する +- 画面遷移でカメラを使用しない画面に移動した場合もカメラリソースを解放する + +## ファイル管理仕様 (File Management) + +### 保存先 + +- パス: `Pictures/MiniTIAS/`(Android 共有ストレージ) +- Android 10 以上では MediaStore API を使用する(`saver_gallery` パッケージで抽象化) +- ディレクトリが存在しない場合は自動作成する + +### ファイル命名規則 + +- 基本形式: `MiniTIAS_YYYYMMDD_HHmmss.png` +- 同秒重複時: `MiniTIAS_YYYYMMDD_HHmmss_1.png`,`MiniTIAS_YYYYMMDD_HHmmss_2.png`,... +- タイムスタンプはデバイスのローカル時刻を使用する + +### 重複回避ロジック + +```text +ファイル名候補 = "MiniTIAS_{timestamp}.png" + +IF ファイル名候補が存在しない: + RETURN ファイル名候補 + +suffix = 1 +WHILE "MiniTIAS_{timestamp}_{suffix}.png" が存在する: + suffix += 1 + +RETURN "MiniTIAS_{timestamp}_{suffix}.png" +``` + +### 削除 + +- 一覧画面の拡大表示ダイアログから削除を実行する +- 削除前に確認ダイアログを表示する(「この画像を削除しますか?」) +- ファイルシステムから物理削除する(ゴミ箱機能なし) +- 削除後,MediaStore からも当該エントリを除去する + +## パーミッション管理 (Permission Handling) + +### 必要なパーミッション + +| パーミッション | 用途 | 対象 API レベル | +| --- | --- | --- | +| `CAMERA` | カメラプレビュー・撮影 | 全バージョン | +| `WRITE_EXTERNAL_STORAGE` | 画像保存 | API 28 以下 | +| `READ_EXTERNAL_STORAGE` | 画像一覧取得 | API 28 以下 | +| `READ_MEDIA_IMAGES` | 画像一覧取得 | API 33 以上 | + +※ API 29〜32 では Scoped Storage により,アプリが MediaStore 経由で保存したファイルは追加権限なしでアクセス可能. + +### パーミッション要求フロー + +```text +アプリ起動 + │ + ▼ +カメラ権限を確認 + │ + ├── 許可済み → カメラプレビュー開始 + │ + └── 未許可 → 権限リクエストダイアログ表示 + │ + ├── 許可 → カメラプレビュー開始 + │ + └── 拒否 → 「カメラの権限が必要です」メッセージ表示 + + 設定画面への誘導ボタン +``` + +- ストレージ権限は撮影時・一覧表示時にそれぞれ確認する +- `permission_handler` パッケージを使用する + +## アーキテクチャ (Architecture) + +### 状態管理 + +- `provider` パッケージを使用する +- 画面ごとに ChangeNotifier を作成し,ビジネスロジックを UI から分離する + +### Provider 構成 + +| Provider | 責務 | +| --- | --- | +| `CameraProvider` | カメラの初期化・プレビュー制御・撮影実行・ライフサイクル管理 | +| `GalleryProvider` | 画像一覧の取得・キャッシュ・削除操作 | + +### ディレクトリ構成 + +```text +lib/ +├── main.dart # アプリエントリポイント,Provider 登録 +├── app.dart # MaterialApp 定義,テーマ設定 +├── providers/ +│ ├── camera_provider.dart # カメラ制御の状態管理 +│ └── gallery_provider.dart # 画像一覧の状態管理 +├── screens/ +│ ├── capture_screen.dart # 撮影画面 (S-01) +│ └── gallery_screen.dart # 一覧画面 (S-02) +├── widgets/ +│ ├── camera_preview.dart # カメラプレビューウィジェット +│ ├── shutter_button.dart # シャッターボタン +│ ├── image_grid.dart # サムネイルグリッド +│ └── image_detail_dialog.dart # 拡大表示ダイアログ +└── services/ + ├── file_service.dart # ファイル保存・命名・削除 + └── permission_service.dart # パーミッション管理 +``` + +### レイヤー構成 + +```text +┌─────────────────────────────────┐ +│ Screens(画面) │ UI 層: ウィジェット構築のみ +├─────────────────────────────────┤ +│ Widgets(部品) │ 再利用可能な UI コンポーネント +├─────────────────────────────────┤ +│ Providers(状態管理) │ ビジネスロジック・状態保持 +├─────────────────────────────────┤ +│ Services(サービス) │ 外部リソースとのインタフェース +└─────────────────────────────────┘ +``` + +- **Screens** は Provider を `context.watch` / `context.read` で参照し,直接 Service を呼ばない +- **Providers** は Service を呼び出し,結果を状態として保持する +- **Services** はプラットフォーム API やファイルシステムとの橋渡しを行う + +## 非機能要件 (Non-functional Requirements) + +### パフォーマンス + +- 撮影から保存完了まで 2 秒以内を目標とする +- 一覧画面のサムネイル読み込みは非同期で行い,画面表示をブロックしない + +### データ保全 + +- 撮影データは端末ローカルにのみ保存する(クラウド同期なし) +- PNG(非圧縮)形式で保存し,画質劣化を防ぐ + +### 対応端末 + +- 試験端末: AQUOS sense3(SH-02M,Android 9) +- minSdkVersion: 21(Android 5.0) +- targetSdkVersion: Flutter デフォルト(最新安定版に追従)