# 画面設計書 11-商品比較画面

## 概要

本ドキュメントは、Bagisto eコマースモバイルアプリにおける商品比較画面の設計仕様を定義するものです。商品比較画面は、ユーザーが複数の商品を並べて比較検討できる機能を提供します。

### 本画面の処理概要

商品比較画面は、ユーザーが購入検討中の複数商品を横並びで比較し、最適な商品を選択するための意思決定を支援する画面です。

**業務上の目的・背景**：eコマースにおいて、ユーザーが複数の類似商品から最適な選択を行うことは購買決定において重要です。商品比較機能は、各商品の仕様・価格・特徴を一目で比較できるようにすることで、ユーザーの購買体験を向上させ、購入意思決定を促進します。また、比較リストに追加した商品を管理し、不要な商品を削除したり、気に入った商品をカートやウィッシュリストに移動させる機能を提供します。

**画面へのアクセス方法**：
- 商品詳細画面の「比較リストに追加」ボタンから商品を追加後、比較画面へ遷移
- ホーム画面やカテゴリ商品一覧画面から商品を比較リストに追加後、比較画面へ遷移
- ナビゲーションドロワーの「比較」メニューから直接アクセス

**主要な操作・処理内容**：
1. 比較リストに追加された商品の一覧表示（横スクロール形式）
2. 商品の属性情報を縦に並べて比較表示
3. 個別商品を比較リストから削除
4. 全商品を一括で比較リストから削除（FloatingActionButton）
5. 比較画面から商品をカートに追加
6. 比較画面から商品をウィッシュリストに追加/削除
7. 商品画像タップで商品詳細画面へ遷移
8. ページネーションによる追加データの読み込み（無限スクロール）

**画面遷移**：
- 遷移元：商品詳細画面、ホーム画面、カテゴリ商品一覧画面、サブカテゴリ画面、ナビゲーションドロワー
- 遷移先：商品詳細画面、カート画面

**権限による表示制御**：ログイン状態に関わらず比較リストは閲覧可能ですが、ウィッシュリスト追加機能はログインユーザーのみ利用可能です。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 32 | 商品比較一覧表示 | 主機能 | 比較リストに追加された商品の比較表示 |
| 34 | 比較リストから削除 | 補助機能 | 比較リストから個別商品を削除 |
| 35 | 比較リスト全削除 | 補助機能 | 比較リストの全商品一括削除 |
| 20 | カートに追加 | API連携 | 比較画面から商品をカートに追加 |
| 28 | ウィッシュリストに追加 | API連携 | 比較画面から商品をウィッシュリストに追加 |

## 画面種別

一覧（比較表示）

## URL/ルーティング

```dart
const String compareScreen = 'compareScreen';
```

ルート定義：`Navigator.pushNamed(context, compareScreen)`

## 入出力項目

| 項目名 | 入力/出力 | 型 | 必須 | 説明 |
|--------|----------|-----|------|------|
| - | - | - | - | 本画面は入力パラメータなしで遷移可能 |

## 表示項目

| No | 項目名 | 型 | 表示条件 | 説明 |
|----|--------|-----|----------|------|
| 1 | AppBar | Widget | 常時 | タイトル「比較」、カートアイコン（バッジ付き） |
| 2 | 商品画像 | Image | 商品あり | 各商品のサムネイル画像 |
| 3 | 商品名 | Text | 商品あり | 商品の名称 |
| 4 | 価格 | Text | 商品あり | 商品価格（フォーマット済み） |
| 5 | 削除ボタン | IconButton | 商品あり | 個別商品の削除ボタン |
| 6 | カート追加ボタン | Button | 商品あり | カートへの追加ボタン |
| 7 | ウィッシュリストボタン | IconButton | 商品あり | ウィッシュリストへの追加/削除 |
| 8 | 商品属性一覧 | Table | 商品あり | 商品の各種属性を比較表示 |
| 9 | 全削除ボタン | FloatingActionButton | 商品1件以上 | 比較リスト全削除ボタン |
| 10 | 空状態表示 | EmptyDataView | 商品0件 | 比較リストが空の場合の表示 |
| 11 | ローディング | Loader | データ読み込み中 | 処理中のローディング表示 |

## イベント仕様

### 1-比較リストデータ取得

**トリガー**：画面初期表示時、スクロール最下部到達時

**処理フロー**：
1. `CompareScreenBloc`に`CompareScreenFetchEvent`を発行
2. `CompareScreenRepository.callCompareApi(page, limit)`でAPIを呼び出し
3. 取得したデータを`CompareProductsData`モデルに変換
4. ページネーション処理（page++、hasMore判定）
5. `CompareScreenFetchDataState`を発行しUIを更新

### 2-個別商品削除

**トリガー**：商品カードの削除ボタン押下

**処理フロー**：
1. `OnClickCompareLoaderEvent`でローダー表示
2. `RemoveFromCompareListEvent(productId)`を発行
3. `CompareScreenRepository.removeFromCompareList(productId)`でAPIを呼び出し
4. 成功時：`RemoveFromCompareState.success`を発行、成功通知表示
5. ページをリセットして比較リストを再取得

### 3-全商品削除

**トリガー**：FloatingActionButton（ゴミ箱アイコン）押下

**処理フロー**：
1. `OnClickCompareLoaderEvent`でローダー表示
2. `RemoveAllCompareListEvent`を発行
3. `CompareScreenRepository.removeAllCompareProducts()`でAPIを呼び出し
4. 成功時：`RemoveAllCompareProductState.success`を発行、成功通知表示
5. ページをリセットして比較リストを再取得（空になる）

### 4-カート追加

**トリガー**：カート追加ボタン押下

**処理フロー**：
1. `AddToCartCompareEvent(productId, quantity)`を発行
2. `CompareScreenRepository.callAddToCartAPi(productId, quantity)`でAPIを呼び出し
3. 成功時：`AddToCartCompareState.success`を発行
4. グローバルカート数を更新（`GlobalData.cartCountController.sink.add`）
5. 成功通知「カートに追加しました」を表示

### 5-ウィッシュリスト追加/削除

**トリガー**：ウィッシュリストボタン押下

**処理フロー**：
1. 商品の`isInWishlist`状態を確認
2. `isInWishlist == false`の場合：`AddToWishlistCompareEvent`を発行
3. `isInWishlist == true`の場合：`FetchDeleteWishlistItemEvent`を発行
4. API呼び出し後、商品の`isInWishlist`状態をトグル
5. 成功/失敗通知を表示

### 6-カート画面遷移

**トリガー**：AppBarのカートアイコン押下

**処理フロー**：
1. `Navigator.pushNamed(context, cartScreen)`を実行
2. カート画面へ遷移

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 比較リスト取得 | compare_items | SELECT | ユーザーの比較リスト商品を取得 |
| 個別削除 | compare_items | DELETE | 指定商品を比較リストから削除 |
| 全削除 | compare_items | DELETE | ユーザーの比較リスト全商品を削除 |
| カート追加 | cart_items | INSERT/UPDATE | 商品をカートに追加 |
| ウィッシュリスト追加 | wishlist_items | INSERT | 商品をウィッシュリストに追加 |
| ウィッシュリスト削除 | wishlist_items | DELETE | 商品をウィッシュリストから削除 |

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

#### compare_items

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | product_id, product | customer_idで絞り込み | ページネーション対応 |
| DELETE | - | product_idで特定 | 個別削除 |
| DELETE | - | customer_idで絞り込み | 全削除 |

#### cart_items

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | product_id, quantity | 商品ID、数量 | 既存の場合は数量加算 |

## メッセージ仕様

| No | メッセージID | メッセージ内容 | 表示タイミング | 種別 |
|----|------------|---------------|--------------|------|
| 1 | success | 成功 | 各操作成功時 | 成功 |
| 2 | failed | 失敗 | 各操作失敗時 | エラー |
| 3 | addToCart | カートに追加しました | カート追加成功時 | 成功 |
| 4 | deletedSuccess | 削除しました | 削除成功時 | 成功 |
| 5 | somethingWrong | 問題が発生しました | 予期せぬエラー時 | エラー |
| 6 | processWaitingMsg | 処理中です... | データ読み込み中 | 情報 |

## 例外処理

| No | 例外状況 | 対応処理 |
|----|---------|---------|
| 1 | 比較リストが空 | `EmptyDataView`を表示 |
| 2 | API通信エラー | エラーメッセージを表示、リトライ可能 |
| 3 | ネットワーク未接続 | エラー通知を表示 |
| 4 | データ取得失敗 | `ErrorMessage.errorMsg`を表示 |
| 5 | カート追加失敗（在庫切れ等） | APIからのエラーメッセージを表示 |

## 備考

- 比較リストはページネーション対応（1ページ10件、無限スクロール）
- 商品画像はキャッシュ機能を利用
- 横スクロールで複数商品を比較可能
- カートアイコンのバッジ数はリアルタイムで更新
- BLoCパターンによる状態管理を採用

---

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

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

### 推奨読解順序

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

まず、比較画面で扱うデータモデルの構造を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | compare_product_model.dart | `lib/screens/compare/data_model/compare_product_model.dart` | CompareProductsData、CompareProductsクラスの構造を理解 |
| 1-2 | product_details_model.dart | `lib/screens/product_screen/data_model/product_details_model.dart` | Productクラスの構造（比較商品に含まれる） |

**読解のコツ**: `@JsonSerializable()`アノテーションはJSON変換用。`part`ディレクティブで生成ファイル（.g.dart）を参照。

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

処理の起点となる画面ウィジェットを特定します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | compare_screen.dart | `lib/screens/compare/view/compare_screen.dart` | CompareScreenクラス、initStateでのBloc初期化 |

**主要処理フロー**:
1. **行34-39**: initStateでBlocを取得し、`CompareScreenFetchEvent`を発行
2. **行42-47**: ScrollControllerでスクロール監視、最下部で追加データ取得
3. **行62-110**: buildメソッドでScaffold構築（AppBar、body、FloatingActionButton）
4. **行129-202**: BlocConsumerでイベント処理とUI更新

#### Step 3: 状態管理（BLoC）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | compare_screen_bloc.dart | `lib/screens/compare/bloc/compare_screen_bloc.dart` | イベント処理、ページネーション管理 |
| 3-2 | compare_screen_event.dart | `lib/screens/compare/bloc/compare_screen_event.dart` | 各イベントクラスの定義 |
| 3-3 | compare_screen_state.dart | `lib/screens/compare/bloc/compare_screen_state.dart` | 各状態クラスの定義 |

**主要処理フロー**:
- **行27-62**: CompareScreenFetchEvent処理（ページネーション、データ取得）
- **行63-79**: RemoveFromCompareListEvent処理（個別削除）
- **行80-96**: AddToCartCompareEvent処理（カート追加）
- **行142-156**: RemoveAllCompareListEvent処理（全削除）

#### Step 4: API通信（Repository）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | compare_screen_repository.dart | `lib/screens/compare/bloc/compare_screen_repository.dart` | API呼び出しの実装 |

#### Step 5: UIコンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | compare_view.dart | `lib/screens/compare/view/widget/compare_view.dart` | 比較表示のメインビュー |
| 5-2 | compare_list.dart | `lib/screens/compare/view/widget/compare_list.dart` | 商品リストの表示 |
| 5-3 | compare_loader_view.dart | `lib/screens/compare/view/widget/compare_loader_view.dart` | ローディング表示 |

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

```
CompareScreen (compare_screen.dart)
    │
    ├─ CompareScreenBloc (compare_screen_bloc.dart)
    │      │
    │      ├─ CompareScreenFetchEvent
    │      │      └─ CompareScreenRepository.callCompareApi()
    │      │
    │      ├─ RemoveFromCompareListEvent
    │      │      └─ CompareScreenRepository.removeFromCompareList()
    │      │
    │      ├─ AddToCartCompareEvent
    │      │      └─ CompareScreenRepository.callAddToCartAPi()
    │      │
    │      ├─ AddToWishlistCompareEvent
    │      │      └─ CompareScreenRepository.callWishListAddDeleteItem()
    │      │
    │      └─ RemoveAllCompareListEvent
    │             └─ CompareScreenRepository.removeAllCompareProducts()
    │
    └─ CompareView (compare_view.dart)
           └─ CompareList (compare_list.dart)
```

### データフロー図

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

ユーザー操作 ─────▶ CompareScreenBloc ─────▶ UI更新
   │                     │
   │                     ├─ CompareScreenRepository
   │                     │         │
   │                     │         └─ GraphQL API
   │                     │
   │                     └─ State発行
   │                            │
   └───────────────────────────▶ BlocConsumer
                                      │
                                      └─ Widget再描画
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| compare_screen.dart | `lib/screens/compare/view/compare_screen.dart` | ソース | メイン画面ウィジェット |
| compare_screen_bloc.dart | `lib/screens/compare/bloc/compare_screen_bloc.dart` | ソース | 状態管理BLoC |
| compare_screen_event.dart | `lib/screens/compare/bloc/compare_screen_event.dart` | ソース | イベント定義 |
| compare_screen_state.dart | `lib/screens/compare/bloc/compare_screen_state.dart` | ソース | 状態定義 |
| compare_screen_repository.dart | `lib/screens/compare/bloc/compare_screen_repository.dart` | ソース | API通信 |
| compare_product_model.dart | `lib/screens/compare/data_model/compare_product_model.dart` | ソース | データモデル |
| compare_view.dart | `lib/screens/compare/view/widget/compare_view.dart` | ソース | 比較表示ビュー |
| compare_list.dart | `lib/screens/compare/view/widget/compare_list.dart` | ソース | 商品リスト |
| compare_loader_view.dart | `lib/screens/compare/view/widget/compare_loader_view.dart` | ソース | ローダービュー |
| index.dart | `lib/screens/compare/utils/index.dart` | ソース | エクスポート集約 |
| route_constants.dart | `lib/utils/route_constants.dart` | 設定 | ルート定数定義 |
