# 画面機能仕様書 (Screen & Function Specification)

本ドキュメントでは，MiniTIAS アプリの画面設計，画面遷移，カメラ制御，ファイル管理，およびアーキテクチャを定義する．

## 画面一覧 (Screen List)

| ID | 画面名 | 概要 |
| --- | --- | --- |
| S-01 | 撮影画面 | インカメラのライブプレビューを表示し，撮影を行う |
| S-02 | 一覧画面 | 撮影済み画像のサムネイル一覧を表示し，確認・削除を行う |

※ 初期バージョンでは 2 画面構成とする．

## 画面遷移 (Screen Transition)

```text
[アプリ起動]
    │
    ▼
┌──────────┐       BottomNavigationBar       ┌──────────┐
│  S-01    │ ◄──────────────────────────────► │  S-02    │
│ 撮影画面  │         タブ切り替え              │ 一覧画面  │
└──────────┘                                  └──────────┘
                                                   │
                                                   │ サムネイルタップ
                                                   ▼
                                              ┌──────────┐
                                              │ 詳細表示   │
                                              │(インライン) │
                                              └──────────┘
```

- アプリ起動時のデフォルト画面は **撮影画面（S-01）** とする
- 画面切り替えには `BottomNavigationBar` を使用する（タブ 2 つ: 撮影 / 一覧）
- 画像の詳細表示は一覧画面内の `Stack` オーバーレイで実装し，独立画面・ダイアログとしない

## 撮影画面 (S-01: Capture Screen)

### レイアウト

アプリ全体を `app.dart` の `MaterialApp` ごと `Transform.rotate(angle: pi)` で 180° 回転する．これにより画面・ダイアログ・スナックバー等すべての UI が自動的に回転されるため，個々のウィジェットで回転を意識する必要がない．以下は**コード上の配置順**（回転前）を示す．操作者が逆さに置いた端末を見ると，上下が反転して表示される．

```text
操作者の視点（回転後）            コード上の配置（回転前）
┌─────────────────────┐      ┌─────────────────────┐
│ （黒い空白）          │      │  BottomNavBar        │
│  アタッチメントで隠れる │      │  明るさスライダー      │
├─────────────────────┤      │                     │
│                     │      │  カメラプレビュー      │
│  カメラプレビュー      │      │  （中央クロップ）      │
│  （中央クロップ）      │      │                     │
│                     │      ├─────────────────────┤
│  明るさスライダー      │      │ （黒い空白）          │
│  BottomNavBar        │      │  アタッチメントで隠れる │
└─────────────────────┘      └─────────────────────┘
```

※ アタッチメント（SmTIAS）により画面の約 1/3 が隠れるため，その領域は黒い空白とし，UI 要素は見える領域に配置する．

### UI 要素

| 要素 | 仕様 |
| --- | --- |
| カメラプレビュー | インカメラのライブ映像．鏡像（左右反転）＋上下反転で表示．画面いっぱいに拡大し中央をクロップ |
| シャッターボタン | 丸型ボタン（画面下部中央）．タップで撮影実行．連続撮影可 |
| タイマーボタン | 画面左下に配置．ON（黄色）/ OFF（グレー）を切り替える．ON 時はシャッター押下後 3 秒のカウントダウン表示の後に撮影 |
| 明るさスライダー | ナビバーの直下に配置．画面の輝度を手動で調整する（デフォルト: 0.8） |
| BottomNavigationBar | 撮影タブ（アクティブ）/ 一覧タブ．背景色は黒系で統一．保存中はタップ無効 |

### 動作仕様

- **起動時**: カメラの初期化を行い，プレビューを開始する
- **即時撮影**: タイマー OFF 時，シャッターボタンタップで即座に撮影する
- **タイマー撮影**: タイマー ON 時，シャッターボタンタップで 3, 2, 1 のカウントダウン後に撮影する（手ブレ・振動防止用）
- **連続撮影**: 前回の保存完了を待ってから次の撮影が可能
- **保存中表示**: カメラの画像ストリーム（`startImageStream`）から最新フレームを保持し，撮影開始時に Android ネイティブの `YuvImage.compressToJpeg()` で JPEG 変換して表示する（オーバーレイなし・瞬時）
- **フィードバック**: 撮影成功時にスナックバーで「保存しました: [ファイル名]」を表示する
- **エラー時**: カメラ初期化失敗やストレージ書き込み失敗時は，エラーメッセージをスナックバーで表示する

## 一覧画面 (S-02: Gallery Screen)

### レイアウト

```text
┌─────────────────────────┐
│     BottomNavBar         │
├─────────────────────────┤
│ ┌─────┐ ┌─────┐ ┌─────┐│
│ │     │ │     │ │     ││
│ │ img │ │ img │ │ img ││
│ │     │ │     │ │     ││
│ └─────┘ └─────┘ └─────┘│
│ ┌─────┐ ┌─────┐ ┌─────┐│
│ │     │ │     │ │     ││
│ │ img │ │ img │ │ img ││
│ │     │ │     │ │     ││
│ └─────┘ └─────┘ └─────┘│
│          ...             │
├─────────────────────────┤
│   [画面上部エリア]        │  ← 逆さ配置のため上部が操作側
└─────────────────────────┘
```

※ 撮影画面と同様に UI 全体を 180° 回転して表示する．

### UI 要素

| 要素 | 仕様 |
| --- | --- |
| サムネイルグリッド | 3 列のグリッド表示．新しい画像が先頭（降順） |
| サムネイル | 正方形クロップ．タップで拡大表示 |
| ファイル名ラベル | 各サムネイル下部にファイル名を表示 |
| BottomNavigationBar | 撮影タブ / 一覧タブ（アクティブ） |

### 詳細表示（インラインオーバーレイ）

| 要素 | 仕様 |
| --- | --- |
| 画像 | 一覧画面エリアを全面に覆うオーバーレイで表示．カメラプレビューと同様に上下をクロップして全面表示（`BoxFit.cover`）．ピンチ操作でズーム可能 |
| ファイル名 | オーバーレイ上部に半透明背景とともに表示 |
| 削除ボタン | オーバーレイ下部に半透明背景とともに配置．タップで削除確認ダイアログを表示 |
| 閉じるボタン | ヘッダー右端の × ボタンで閉じる |

### 動作仕様

- **画面表示時**: `Pictures/MiniTIAS/` ディレクトリ内の PNG ファイルを走査し，一覧を構築する
- **リフレッシュ**: 撮影画面から戻った際に一覧を再取得する
- **詳細表示**: サムネイルタップで詳細表示オーバーレイを開く．× ボタンで閉じる
- **削除**: 詳細表示の削除ボタンから確認ダイアログで「削除」を選択すると，ファイルをストレージから削除し，一覧から除去する
- **空状態**: 画像が 0 件の場合は「撮影した画像がありません」のメッセージを表示する

## カメラ制御仕様 (Camera Control)

### プレビュー設定（フレーミング用）

| 項目 | 値 |
| --- | --- |
| 使用カメラ | フロントカメラ（インカメラ） |
| 解像度 | `ResolutionPreset.max` |
| 制御方式 | `camera` パッケージ（オート任せ） |
| フラッシュ | OFF（LED ライトはアタッチメント側で制御） |

プレビューは見やすさ重視．撮影時は別の Camera2 セッションが走る．

### 撮影設定（定量モード．Camera2 API マニュアル制御）

| 項目 | 値 |
| --- | --- |
| 使用カメラ | フロントカメラ（インカメラ） |
| 解像度 | 3264×2448（センサ最大） |
| 画像フォーマット | `RAW_SENSOR`（10-bit BGGR Bayer）．`DngCreator` で DNG ファイル出力 |
| `CONTROL_MODE` | OFF（マニュアル全制御） |
| `CONTROL_AE_MODE` | OFF |
| `SENSOR_EXPOSURE_TIME` | 8,333,333 ns（1/120 s）固定（アタッチメント + LED 環境で線形領域中央狙い．TECH_04 目標 mean G = 400〜600 達成済み） |
| `SENSOR_SENSITIVITY` | 40（センサ最低 ISO）固定 |
| `SENSOR_FRAME_DURATION` | 33,333,333 ns（30fps 相当） |
| `BLACK_LEVEL_LOCK` | true |
| `CONTROL_AWB_MODE` | OFF |
| `COLOR_CORRECTION_GAINS` | (1.0, 1.0, 1.0, 1.0) |
| `COLOR_CORRECTION_TRANSFORM` | 単位行列 |
| `NOISE_REDUCTION_MODE` | OFF |
| `EDGE_MODE` | OFF |
| `TONEMAP_MODE` | `CONTRAST_CURVE` 線形 `[(0,0),(1,1)]` |
| `SHADING_MODE` | HIGH_QUALITY（LSC マップ実値取得のため．画像は LSC 補正済み．PC 側で逆適用可） |
| `STATISTICS_LENS_SHADING_MAP_MODE` | ON |
| `CONTROL_AF_MODE` | OFF（AQUOS sense3 は固定焦点） |
| `FLASH_MODE` | OFF |

### 既存 PNG 撮影パス（無効化済み・コードは温存）

旧 YUV→BT.601→PNG 経路は `RawCapturePlugin.captureFullResolutionPng` として残存．現状の `takePicture` フローからは呼ばれない．差分比較やフォールバック用に保持．

### プレビュー表示

- `camera` パッケージの `CameraPreview` ウィジェットを使用する
- UI 全体を `Transform.rotate(angle: pi)` で 180° 回転する
- カメラ映像に `Matrix4.diagonal3Values(-scale, -scale, 1.0)` を適用する
  - X 軸反転: 鏡像表示（操作者が自然に見える向き）
  - Y 軸反転: 端末が逆さのため上下を補正
- プレビューは画面いっぱいに拡大し，はみ出す部分はクロップする（`ClipRect` + スケール計算）

### 撮影フロー（定量モード）

```text
シャッターボタンタップ
    │
    ▼
プレビュー停止（CameraController.dispose()）
    │
    ▼
baseName 生成（MiniTIAS_QM_YYYYMMDD_HHmmss．重複時 _1, _2 ...）
    │
    ▼
Camera2 API でフロントカメラを開く（RAW_SENSOR，3264×2448）
    │
    ▼
上記マニュアル制御パラメータを CaptureRequest に適用
    │
    ▼
連続 3 枚キャプチャ（設定反映のため最初の 2 枚は破棄）
    │
    ▼
3 枚目の Image + TotalCaptureResult を取得
    │
    ▼
DngCreator で DNG ファイルを直接書き込み（FileOutputStream）
  → Pictures/MiniTIAS/{baseName}.dng（約 16 MB）
    │
    ▼
LSC マップ取得（CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP）
    │
    ▼
メタデータ Map 構築（settings / actual / sensorCharacteristics / lscMap / image_statistics / hot_pixel_map）
    │
    ▼
Dart 側で JSON 保存
  → Pictures/MiniTIAS/{baseName}.meta.json
    │
    ▼
[並行] シャッター押下時に取得済みのライブプレビュースナップショット JPEG を保存
  → Pictures/MiniTIAS/{baseName}.preview.jpg
  ※ スナップショット取得失敗時は preview.jpg を保存しない（撮影全体は失敗扱いにしない）
    │
    ▼
MediaStore 通知（DNG / meta.json / preview.jpg すべて scanFile）
    │
    ▼
Camera2 を閉じ，プレビューを再開
    │
    ▼
スナックバーで保存完了を通知
```

### ライフサイクル管理

- `WidgetsBindingObserver` を使用し，アプリのライフサイクルを監視する
- **バックグラウンド遷移時**（`paused`）: カメラリソースを解放する（`CameraController.dispose()`）
- **フォアグラウンド復帰時**（`resumed`）: カメラを再初期化する
- 画面遷移でカメラを使用しない画面に移動した場合もカメラリソースを解放する

## ファイル管理仕様 (File Management)

### 保存先

- パス: `Pictures/MiniTIAS/`（Android 共有ストレージ）
- Android 10 以上では MediaStore API を使用する（`saver_gallery` パッケージで抽象化）
- ディレクトリが存在しない場合は自動作成する

### ファイル命名規則

撮影 1 回で 3 ファイルを生成する（baseName は共通．重複時 `_1`, `_2` ...）：

| 種別 | ファイル名 | 概要 |
| --- | --- | --- |
| RAW 画像 | `MiniTIAS_QM_YYYYMMDD_HHmmss.dng` | 解析の本体（10-bit BGGR Bayer，約 16 MB） |
| メタデータ | `MiniTIAS_QM_YYYYMMDD_HHmmss.meta.json` | 撮影設定 + 実適用値 + LSC マップ + センサ特性（約 24 KB） |
| プレビュー | `MiniTIAS_QM_YYYYMMDD_HHmmss.preview.jpg` | アプリ内表示用の縮小プレビュー．シャッター瞬間のライブプレビュー（自動 ISP 処理済み YUV→JPEG）をそのまま保存．解像度はプレビューストリーム依存（おそらく 1280×720 程度） |

- タイムスタンプはデバイスのローカル時刻を使用する
- 旧通常モードの `MiniTIAS_YYYYMMDD_HHmmss.png` は引き続き一覧に表示可能（後方互換）．新規撮影は QM 形式で行われる

### 重複回避ロジック

```text
ファイル名候補 = "MiniTIAS_{timestamp}.png"

IF ファイル名候補が存在しない:
    RETURN ファイル名候補

suffix = 1
WHILE "MiniTIAS_{timestamp}_{suffix}.png" が存在する:
    suffix += 1

RETURN "MiniTIAS_{timestamp}_{suffix}.png"
```

### 削除

- 一覧画面の詳細表示オーバーレイから削除を実行する
- 削除前に確認ダイアログを表示する
  - `.preview.jpg` の場合：「この画像を削除しますか？(DNG ファイルとメタデータも一緒に削除されます)」
  - `.png`（旧通常モード）の場合：「この画像を削除しますか？」
- `.preview.jpg` 削除時は同 baseName の `.dng` と `.meta.json` も**連動削除**する
- ファイルシステムから物理削除する（ゴミ箱機能なし）
- 削除後，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        # サムネイルグリッド
└── 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)

### 画面輝度

- アプリ起動時に画面の輝度をデフォルト値（0.8）に設定する
- ユーザーが明るさスライダーで任意に調整できる
- アプリ終了時にシステムの輝度設定に復帰する
- アタッチメント装着時に光センサーで画面が暗くなることへの対策

### パフォーマンス

- 撮影から保存完了まで 2 秒以内を目標とする
- 一覧画面のサムネイル読み込みは非同期で行い，画面表示をブロックしない

### データ保全

- 撮影データは端末ローカルにのみ保存する（クラウド同期なし）
- PNG（非圧縮）形式で保存し，画質劣化を防ぐ

### 対応端末

- 試験端末: AQUOS sense3（SH-02M，Android 9）
- minSdkVersion: 21（Android 5.0）
- targetSdkVersion: Flutter デフォルト（最新安定版に追従）
