# 画面設計書 27-レビュー投稿画面

## 概要

本ドキュメントは、Bagisto eコマースモバイルアプリにおけるレビュー投稿画面の設計仕様を記述したものです。商品に対するレビュー（評価、タイトル、コメント、画像）を投稿する機能を提供します。

### 本画面の処理概要

本画面は、商品に対するレビューを投稿するためのフォーム画面です。星評価、タイトル、コメントの入力と、画像の添付機能を提供します。

**業務上の目的・背景**：商品レビューは、他のユーザーの購買決定に大きな影響を与える重要なコンテンツです。質の高いレビューを効率的に収集することで、商品の信頼性向上とコンバージョン率の改善に寄与します。画像添付機能により、視覚的な情報も提供可能です。

**画面へのアクセス方法**：商品詳細画面から「レビューを投稿」ボタンをタップしてアクセスします。

**主要な操作・処理内容**：
1. 商品画像と商品名を表示する
2. 星評価（1-5）を選択する
3. レビュータイトルを入力する
4. レビューコメントを入力する
5. 画像を添付する（複数可）
6. 投稿ボタンでレビューを送信する

**画面遷移**：商品詳細画面から本画面に遷移します。投稿成功後は前画面（商品詳細画面）に戻ります。

**権限による表示制御**：ログイン済みユーザーのみアクセス可能です。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 59 | レビュー投稿 | 主機能 | 商品へのレビュー・評価投稿処理 |

## 画面種別

登録

## URL/ルーティング

ルート名: `addReviewScreen`（routes定義による）

## 入出力項目

### 入力パラメータ

| 項目名 | 入出力 | 型 | 必須 | 説明 |
|--------|--------|------|------|------|
| imageUrl | 入力 | String | 任意 | 商品画像URL |
| productId | 入力 | String | 必須 | 商品ID |
| productName | 入力 | String | 任意 | 商品名 |

### フォーム入力項目

| 項目名 | 入出力 | 型 | 必須 | バリデーション | 説明 |
|--------|--------|------|------|---------------|------|
| rating | 入力 | int | 必須 | 1以上 | 星評価（1-5） |
| title | 入力 | String | 必須 | 空白チェック | レビュータイトル |
| comment | 入力 | String | 必須 | 空白チェック | レビューコメント |
| attachments | 入力 | List<MultipartFile> | 任意 | - | 添付画像リスト |

## 表示項目

| 項目名 | データ型 | 説明 |
|--------|---------|------|
| 商品画像 | Image | レビュー対象商品の画像 |
| 商品名 | String | レビュー対象商品の名称 |
| 評価ラベル | String | 「評価」ラベル |
| 星評価バー | RatingBar | 1-5の星選択UI |
| タイトル入力 | TextField | レビュータイトル入力フィールド |
| コメント入力 | TextField | マルチライン（8行）のコメント入力 |
| 画像追加ビュー | Widget | 添付画像の表示と追加ボタン |
| 投稿ボタン | MaterialButton | レビュー投稿ボタン |

## イベント仕様

### 1-画面初期化イベント

画面初期化時（initState）にAddReviewBlocを取得します。

処理フロー：
1. context.read<AddReviewBloc>()でBlocインスタンスを取得
2. 入力フィールドは空の状態で初期化
3. ratingは0で初期化

### 2-星評価選択イベント

RatingBarで星をタップした際の処理です。

処理フロー：
1. onRatingUpdateコールバックが呼び出される
2. ratingにint変換した値を設定

### 3-画像選択イベント

画像追加ボタンタップ時の処理です。

処理フロー：
1. AddImageViewウィジェット内でImagePickerEventを発行
2. 画像ピッカーで画像を選択
3. ImagePickerStateが発行される
4. pickedImagesリストに追加
5. 各画像をhttp.MultipartFile.fromPathで変換してimagesリストに追加

### 4-画像削除イベント

添付画像の削除ボタンタップ時の処理です。

処理フロー：
1. ImagePickerEvent(isDelete: true, deleteImage: image)を発行
2. pickedImagesリストから削除
3. imagesリストから対応するMultipartFileを削除

### 5-投稿ボタン押下イベント

投稿ボタン押下時の処理です。

処理フロー：
1. _reviewFormKey.currentState.validate()でフォームバリデーション
2. ratingが0より大きいかチェック
3. 処理中ダイアログを表示
4. AddReviewFetchEventを発行（productId, rating, title, comment, attachments）
5. 成功時：成功メッセージ表示、3秒後に前画面へ戻る
6. 失敗時：エラーメッセージを表示
7. ratingが0の場合：警告メッセージを表示

## データベース更新仕様

### 操作別データベース影響一覧

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| レビュー投稿 | reviews | INSERT | 新規レビューレコードを作成 |
| 画像添付 | review_images | INSERT | レビュー画像を保存 |

### テーブル別更新項目詳細

#### reviews

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | product_id | 引数のproductId | 対象商品ID |
| INSERT | customer_id | ログインユーザーID | 投稿者ID |
| INSERT | rating | フォーム入力値 | 星評価（1-5） |
| INSERT | title | フォーム入力値 | レビュータイトル |
| INSERT | comment | フォーム入力値 | レビューコメント |
| INSERT | status | pending/approved | 承認状態 |

#### review_images

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | review_id | 作成されたレビューID | 親レビューID |
| INSERT | image_path | アップロードされた画像パス | 画像ファイルパス |

## メッセージ仕様

| メッセージ種別 | メッセージキー | 表示内容 | 表示条件 |
|--------------|--------------|---------|---------|
| タイトル | StringConstants.addaReview | レビューを追加 | AppBarタイトル |
| ラベル | StringConstants.rating | 評価 | 評価セクションラベル |
| ラベル | StringConstants.titleLabel | タイトル | タイトルフィールドラベル |
| ラベル | StringConstants.commentLabel | コメント | コメントフィールドラベル |
| プレースホルダー | StringConstants.titleHint | タイトルを入力 | タイトルフィールド |
| プレースホルダー | StringConstants.commentHint | コメントを入力 | コメントフィールド |
| ボタン | StringConstants.submitReview | レビューを投稿 | 投稿ボタンラベル |
| 処理中 | StringConstants.processWaitingMsg | 処理中... | 処理中ダイアログ |
| 警告 | StringConstants.pleaseAddRating | 評価を追加してください | 評価未選択時 |
| 成功 | addReviewModel.message | 投稿成功 | 投稿成功時 |
| エラー | StringConstants.failed | 失敗しました | 投稿失敗時 |

## 例外処理

| 例外状況 | 処理内容 | ユーザーへの表示 |
|---------|---------|----------------|
| バリデーションエラー | フォームが送信されない | 各フィールドにエラー表示 |
| 評価未選択 | 投稿処理を実行しない | 警告通知を表示 |
| API通信エラー | AddReviewFetchState.failが発行される | エラー通知を表示 |
| 画像アップロード失敗 | エラーメッセージを表示 | エラー通知を表示 |

## 備考

- 星評価はRatingBar.builderを使用（flutter_rating_barパッケージ）
- 半分の星は許可されていない（allowHalfRating: false）
- コメントフィールドは8行のマルチラインテキスト
- 画像はhttp.MultipartFileとしてAPIに送信
- 画像のContent-Typeは'image/png'で固定
- 投稿成功後は3秒後に自動的に前画面に戻る
- DioMediaTypeを使用してContent-Typeを設定

---

## コードリーディングガイド

本画面を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、レビュー投稿のデータ構造を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | add_review_model.dart | `lib/screens/add_review/data_model/add_review_model.dart` | AddReviewModel、Reviewの構造を理解 |

**読解のコツ**: AddReviewModelはBaseModelを継承し、Reviewオブジェクトを持ちます。投稿結果としてレビューIDが返されます。

#### Step 2: エントリーポイントを理解する

処理の起点となるファイル・関数を特定します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | add_review.dart | `lib/screens/add_review/view/add_review.dart` | AddReviewクラスがエントリーポイント |

**主要処理フロー**:
1. **42-45行目**: initStateでAddReviewBloc取得
2. **48-59行目**: buildメソッドでScaffold、AppBar構築
3. **62-84行目**: BlocConsumerで状態に応じた処理
4. **127-269行目**: _reviewFormでフォームUI構築
5. **161-176行目**: RatingBar.builderで星評価UI
6. **272-336行目**: _onPressSubmitButtonで投稿処理

#### Step 3: ウィジェット層を理解する

画面を構成するサブウィジェットを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | add_image_view.dart | `lib/screens/add_review/view/widget/add_image_view.dart` | 画像選択・表示ウィジェット |

#### Step 4: Bloc層を理解する

状態管理と非同期処理の流れを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | add_review_bloc.dart | `lib/screens/add_review/bloc/add_review_bloc.dart` | イベント処理とステート発行 |
| 4-2 | add_review_event.dart | `lib/screens/add_review/bloc/add_review_event.dart` | 各イベントの定義 |
| 4-3 | add_review_state.dart | `lib/screens/add_review/bloc/add_review_state.dart` | 各ステートの定義 |
| 4-4 | add_review_repository.dart | `lib/screens/add_review/bloc/add_review_repository.dart` | API呼び出しの実装 |

**主要処理フロー**:
- **22-36行目** (add_review_bloc.dart): AddReviewFetchEventの処理
- **37-46行目**: ImagePickerEventの処理

### プログラム呼び出し階層図

```
AddReview (add_review.dart)
    │
    ├─ initState()
    │      └─ AddReviewBloc取得
    │
    ├─ build()
    │      └─ _addReviewBloc() → BlocConsumer
    │             └─ _reviewForm()
    │                    ├─ ImageView (商品画像)
    │                    ├─ Text (商品名)
    │                    ├─ RatingBar.builder (星評価)
    │                    ├─ TextField (タイトル)
    │                    ├─ TextFormField (コメント)
    │                    ├─ AddImageView (画像添付)
    │                    │      └─ ImagePickerEvent
    │                    └─ MaterialButton (投稿)
    │                           └─ _onPressSubmitButton()
    │
    ├─ _onPressSubmitButton()
    │      ├─ フォームバリデーション
    │      ├─ 評価チェック
    │      ├─ ダイアログ表示
    │      └─ AddReviewBloc.add(AddReviewFetchEvent)
    │
    └─ AddReviewBloc (add_review_bloc.dart)
           │
           ├─ AddReviewFetchEvent
           │      └─ AddReviewRepository.callAddReviewApi()
           │
           └─ ImagePickerEvent
                  └─ pickedFile追加/削除
```

### データフロー図

```
[入力]                        [処理]                              [出力]

imageUrl, productId,    ───▶ AddReview.build()                ───▶ 画面表示
productName                      │
                                 ▼
                         _reviewForm()
                                 │
                                 ▼
                         フォーム入力                          ───▶ ユーザー入力
                          ├─ 星評価
                          ├─ タイトル
                          ├─ コメント
                          └─ 画像添付

画像選択               ───▶ ImagePickerEvent                  ───▶ pickedImages更新
                                 │                                  │
                                 ▼                                  ▼
                         XFile取得                             画像プレビュー表示
                                 │
                                 ▼
                         MultipartFile変換                    ───▶ imagesリスト

投稿ボタン押下         ───▶ _onPressSubmitButton()            ───▶ 投稿結果
                                 │
                                 ▼
                    バリデーション & 評価チェック
                                 │
                                 ▼
                    AddReviewFetchEvent
                         ├─ productId
                         ├─ rating
                         ├─ title
                         ├─ comment
                         └─ attachments
                                 │
                                 ▼
                    AddReviewRepository
                    .callAddReviewApi()
                                 │
                                 ▼
                    AddReviewModel                           ───▶ 成功メッセージ
                                                                   │
                                                                   ▼
                                                              前画面に戻る
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| add_review.dart | `lib/screens/add_review/view/add_review.dart` | ソース | 画面本体のStatefulWidget |
| add_image_view.dart | `lib/screens/add_review/view/widget/add_image_view.dart` | ソース | 画像選択ウィジェット |
| add_review_bloc.dart | `lib/screens/add_review/bloc/add_review_bloc.dart` | ソース | 状態管理Bloc |
| add_review_event.dart | `lib/screens/add_review/bloc/add_review_event.dart` | ソース | Blocイベント定義 |
| add_review_state.dart | `lib/screens/add_review/bloc/add_review_state.dart` | ソース | Blocステート定義 |
| add_review_repository.dart | `lib/screens/add_review/bloc/add_review_repository.dart` | ソース | API通信リポジトリ |
| add_review_model.dart | `lib/screens/add_review/data_model/add_review_model.dart` | ソース | レビューデータモデル |
| index.dart | `lib/screens/add_review/utils/index.dart` | ソース | 共通インポートファイル |
