本ドキュメントでは,MiniTIAS アプリの画面設計,画面遷移,カメラ制御,ファイル管理,およびアーキテクチャを定義する.
| ID | 画面名 | 概要 |
| S-01 | 撮影画面 | インカメラのライブプレビューを表示し,撮影を行う |
| S-02 | 一覧画面 | 撮影済み画像のサムネイル一覧を表示し,確認・削除を行う |
※ 初期バージョンでは 2 画面構成とする.
[アプリ起動]
│
▼
┌──────────┐ BottomNavigationBar ┌──────────┐
│ S-01 │ ◄──────────────────────────────► │ S-02 │
│ 撮影画面 │ タブ切り替え │ 一覧画面 │
└──────────┘ └──────────┘
│
│ サムネイルタップ
▼
┌──────────┐
│ 拡大表示 │
│ (ダイアログ) │
└──────────┘
- アプリ起動時のデフォルト画面は 撮影画面(S-01) とする
- 画面切り替えには
BottomNavigationBar を使用する(タブ 2 つ: 撮影 / 一覧)
- 画像の拡大表示はダイアログ(
showDialog)で実装し,独立画面としない
UI 全体を Transform.rotate(angle: pi) で 180° 回転する.以下はコード上の配置順(回転前)を示す.操作者が逆さに置いた端末を見ると,上下が反転して表示される.
操作者の視点(回転後) コード上の配置(回転前)
┌─────────────────────┐ ┌─────────────────────┐
│ (黒い空白) │ │ BottomNavBar │
│ アタッチメントで隠れる │ │ 明るさスライダー │
├─────────────────────┤ │ │
│ │ │ カメラプレビュー │
│ カメラプレビュー │ │ (中央クロップ) │
│ (中央クロップ) │ │ │
│ │ ├─────────────────────┤
│ 明るさスライダー │ │ (黒い空白) │
│ BottomNavBar │ │ アタッチメントで隠れる │
└─────────────────────┘ └─────────────────────┘
※ アタッチメント(SmTIAS)により画面の約 1/3 が隠れるため,その領域は黒い空白とし,UI 要素は見える領域に配置する.
| 要素 | 仕様 |
| カメラプレビュー | インカメラのライブ映像.鏡像(左右反転)+上下反転で表示.画面いっぱいに拡大し中央をクロップ |
| シャッターボタン | 丸型ボタン.タップで撮影実行.連続タップ可(連写対応).※ Step 3 で実装 |
| 明るさスライダー | ナビバーの直下に配置.画面の輝度を手動で調整する(デフォルト: 0.8) |
| BottomNavigationBar | 撮影タブ(アクティブ)/ 一覧タブ.背景色は黒系で統一 |
- 起動時: カメラの初期化を行い,プレビューを開始する
- 撮影: シャッターボタンタップで静止画をキャプチャし,PNG 形式で保存する
- 連続撮影: 前回の保存完了を待たずに次の撮影が可能.撮影中はシャッターボタンの操作を受け付ける
- フィードバック: 撮影成功時にスナックバーで「保存しました: ファイル名」を表示する
- エラー時: カメラ初期化失敗やストレージ書き込み失敗時は,エラーメッセージをスナックバーで表示する
┌─────────────────────────┐
│ BottomNavBar │
├─────────────────────────┤
│ ┌─────┐ ┌─────┐ ┌─────┐│
│ │ │ │ │ │ ││
│ │ img │ │ img │ │ img ││
│ │ │ │ │ │ ││
│ └─────┘ └─────┘ └─────┘│
│ ┌─────┐ ┌─────┐ ┌─────┐│
│ │ │ │ │ │ ││
│ │ img │ │ img │ │ img ││
│ │ │ │ │ │ ││
│ └─────┘ └─────┘ └─────┘│
│ ... │
├─────────────────────────┤
│ [画面上部エリア] │ ← 逆さ配置のため上部が操作側
└─────────────────────────┘
※ 撮影画面と同様に UI 全体を 180° 回転して表示する.
| 要素 | 仕様 |
| サムネイルグリッド | 3 列のグリッド表示.新しい画像が先頭(降順) |
| サムネイル | 正方形クロップ.タップで拡大表示 |
| ファイル名ラベル | 各サムネイル下部にファイル名を表示 |
| BottomNavigationBar | 撮影タブ / 一覧タブ(アクティブ) |
| 要素 | 仕様 |
| 画像 | 元画像をフル解像度で表示.ピンチ操作でズーム可能 |
| ファイル名 | ダイアログ上部にファイル名を表示 |
| 削除ボタン | ダイアログ内に配置.タップで削除確認ダイアログを表示 |
| 閉じるボタン | ダイアログ外タップまたは閉じるボタンで閉じる |
- 画面表示時:
Pictures/MiniTIAS/ ディレクトリ内の PNG ファイルを走査し,一覧を構築する
- リフレッシュ: 撮影画面から戻った際に一覧を再取得する
- 削除: 削除確認ダイアログで「削除」を選択すると,ファイルをストレージから削除し,一覧から除去する
- 空状態: 画像が 0 件の場合は「撮影した画像がありません」のメッセージを表示する
| 項目 | 値 |
| 使用カメラ | フロントカメラ(インカメラ) |
| 解像度 | ResolutionPreset.max(カメラが対応する最大解像度) |
| フォーカス | オートフォーカス(デフォルト動作) |
| フラッシュ | OFF(LED ライトはアタッチメント側で制御) |
| 画像フォーマット | Camera2 API で YUV_420_888 生データを取得し,PNG に直接変換して保存(JPEG 非経由) |
camera パッケージの CameraPreview ウィジェットを使用する
- UI 全体を
Transform.rotate(angle: pi) で 180° 回転する
- カメラ映像に
Matrix4.diagonal3Values(-scale, -scale, 1.0) を適用する
- X 軸反転: 鏡像表示(操作者が自然に見える向き)
- Y 軸反転: 端末が逆さのため上下を補正
- プレビューは画面いっぱいに拡大し,はみ出す部分はクロップする(
ClipRect + スケール計算)
シャッターボタンタップ
│
▼
プレビュー停止(CameraController.dispose())
│
▼
Camera2 API でフロントカメラを開く(YUV_420_888,フル解像度)
│
▼
AE/AF 安定のため 1 秒間プレビューフレームを流す
│
▼
STILL_CAPTURE リクエストで YUV フレームを 1 枚取得
│
▼
YUV → RGB 変換(BT.601 係数,isolate で実行)
│
▼
PNG エンコード(image パッケージ,ロスレス)
│
▼
ファイル名生成(命名規則に従う)
│
▼
Pictures/MiniTIAS/ に保存
│
▼
MediaStore 通知(MediaScannerConnection でスキャン)
│
▼
Camera2 を閉じ,プレビューを再開
│
▼
スナックバーで保存完了を通知
WidgetsBindingObserver を使用し,アプリのライフサイクルを監視する
- バックグラウンド遷移時(
paused): カメラリソースを解放する(CameraController.dispose())
- フォアグラウンド復帰時(
resumed): カメラを再初期化する
- 画面遷移でカメラを使用しない画面に移動した場合もカメラリソースを解放する
- パス:
Pictures/MiniTIAS/(Android 共有ストレージ)
- Android 10 以上では MediaStore API を使用する(
saver_gallery パッケージで抽象化)
- ディレクトリが存在しない場合は自動作成する
- 基本形式:
MiniTIAS_YYYYMMDD_HHmmss.png
- 同秒重複時:
MiniTIAS_YYYYMMDD_HHmmss_1.png,MiniTIAS_YYYYMMDD_HHmmss_2.png,...
- タイムスタンプはデバイスのローカル時刻を使用する
ファイル名候補 = "MiniTIAS_{timestamp}.png"
IF ファイル名候補が存在しない:
RETURN ファイル名候補
suffix = 1
WHILE "MiniTIAS_{timestamp}_{suffix}.png" が存在する:
suffix += 1
RETURN "MiniTIAS_{timestamp}_{suffix}.png"
- 一覧画面の拡大表示ダイアログから削除を実行する
- 削除前に確認ダイアログを表示する(「この画像を削除しますか?」)
- ファイルシステムから物理削除する(ゴミ箱機能なし)
- 削除後,MediaStore からも当該エントリを除去する
| パーミッション | 用途 | 対象 API レベル |
CAMERA | カメラプレビュー・撮影 | 全バージョン |
WRITE_EXTERNAL_STORAGE | 画像保存 | API 28 以下 |
READ_EXTERNAL_STORAGE | 画像一覧取得 | API 28 以下 |
READ_MEDIA_IMAGES | 画像一覧取得 | API 33 以上 |
※ API 29〜32 では Scoped Storage により,アプリが MediaStore 経由で保存したファイルは追加権限なしでアクセス可能.
アプリ起動
│
▼
カメラ権限を確認
│
├── 許可済み → カメラプレビュー開始
│
└── 未許可 → 権限リクエストダイアログ表示
│
├── 許可 → カメラプレビュー開始
│
└── 拒否 → 「カメラの権限が必要です」メッセージ表示
+ 設定画面への誘導ボタン
- ストレージ権限は撮影時・一覧表示時にそれぞれ確認する
permission_handler パッケージを使用する
provider パッケージを使用する
- 画面ごとに ChangeNotifier を作成し,ビジネスロジックを UI から分離する
| Provider | 責務 |
CameraProvider | カメラの初期化・プレビュー制御・撮影実行・ライフサイクル管理 |
GalleryProvider | 画像一覧の取得・キャッシュ・削除操作 |
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 # パーミッション管理
┌─────────────────────────────────┐
│ Screens(画面) │ UI 層: ウィジェット構築のみ
├─────────────────────────────────┤
│ Widgets(部品) │ 再利用可能な UI コンポーネント
├─────────────────────────────────┤
│ Providers(状態管理) │ ビジネスロジック・状態保持
├─────────────────────────────────┤
│ Services(サービス) │ 外部リソースとのインタフェース
└─────────────────────────────────┘
- Screens は Provider を
context.watch / context.read で参照し,直接 Service を呼ばない
- Providers は Service を呼び出し,結果を状態として保持する
- Services はプラットフォーム API やファイルシステムとの橋渡しを行う
- アプリ起動時に画面の輝度をデフォルト値(0.8)に設定する
- ユーザーが明るさスライダーで任意に調整できる
- アプリ終了時にシステムの輝度設定に復帰する
- アタッチメント装着時に光センサーで画面が暗くなることへの対策
- 撮影から保存完了まで 2 秒以内を目標とする
- 一覧画面のサムネイル読み込みは非同期で行い,画面表示をブロックしない
- 撮影データは端末ローカルにのみ保存する(クラウド同期なし)
- PNG(非圧縮)形式で保存し,画質劣化を防ぐ
- 試験端末: AQUOS sense3(SH-02M,Android 9)
- minSdkVersion: 21(Android 5.0)
- targetSdkVersion: Flutter デフォルト(最新安定版に追従)