# 画面設計書 32-CMSコンテンツ画面

## 概要

本ドキュメントは、Bagisto eコマースモバイルアプリにおける「CMSコンテンツ画面」の画面設計書です。本画面は、CMS（Content Management System）で管理されたコンテンツ（利用規約、プライバシーポリシー等）をHTMLレンダリングで表示する機能を提供します。

### 本画面の処理概要

本画面は、管理画面で設定されたCMSページのHTMLコンテンツを取得し、モバイルアプリ内でレンダリング表示します。各CMSページはID、タイトル、ロケール別の翻訳コンテンツを持ち、現在のアプリロケールに応じた適切な言語でコンテンツが表示されます。

**業務上の目的・背景**：ECサイトでは法的に必要な文書（利用規約、プライバシーポリシー、特定商取引法に基づく表記など）や、会社情報、よくある質問などの静的コンテンツを管理する必要があります。これらのコンテンツをバックエンドのCMSで一元管理することで、アプリの再リリースなしにコンテンツの更新が可能となり、運用効率が向上します。

**画面へのアクセス方法**：サイドドロワーメニューのCMS項目一覧（利用規約、プライバシーポリシー等）をタップすることでアクセスします。遷移時にページのID、タイトル、インデックスが引数として渡されます。ルーティングは`cmsScreen`という名前で定義されています。

**主要な操作・処理内容**：
1. 画面表示時にCMSページIDを指定してGraphQL APIをコール
2. サーバーからCMSページデータ（HTMLコンテンツ含む）を取得
3. 現在のアプリロケール（GlobalData.locale）に一致する翻訳を検索
4. HTMLコンテンツをHtmlWidgetでレンダリング表示
5. データ取得中はスケルトンローダーを表示
6. データ取得失敗時は空データビューを表示

**画面遷移**：本画面からの直接的な画面遷移はありません。AppBarの戻るボタンで前の画面に戻ることができます。HTMLコンテンツ内のリンクをタップした場合は、外部ブラウザまたはWebView画面が開く可能性があります。

**権限による表示制御**：本画面はログイン状態に関わらずアクセス可能です。CMSページの公開設定はサーバー側で管理されており、非公開ページは取得できません。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 61 | CMSページ表示 | 主機能 | CMS管理されたコンテンツページ（利用規約、プライバシーポリシー等）の表示 |

## 画面種別

詳細（コンテンツ表示画面）

## URL/ルーティング

- ルート名: `cmsScreen`
- ルート定数: `const String cmsScreen = "cmsScreen";`
- ナビゲーション: `Navigator.pushNamed(context, cmsScreen, arguments: CmsDataContent(...))`
- 引数: `CmsDataContent`オブジェクト（title, id, index）

## 入出力項目

| 項目名 | 項目ID | 入力/出力 | データ型 | 必須 | バリデーション | 備考 |
|--------|--------|----------|----------|------|----------------|------|
| ページタイトル | title | 入力（引数） | String? | 任意 | なし | AppBarに表示 |
| ページID | id | 入力（引数） | int? | 必須 | なし | API呼び出しに使用 |
| インデックス | index | 入力（引数） | int? | 任意 | なし | 画面識別用 |

## 表示項目

| 項目名 | 表示位置 | データソース | 表示形式 | 備考 |
|--------|----------|-------------|----------|------|
| 画面タイトル | AppBar | widget.title | テキスト | 引数で渡されたタイトル |
| HTMLコンテンツ | 本文エリア | CmsPage.translations.htmlContent | HTML | HtmlWidgetでレンダリング |
| スケルトンローダー | 本文エリア | - | アニメーション | データ取得中に表示 |
| 空データビュー | 本文エリア | - | EmptyDataView | データ取得失敗時に表示 |

## イベント仕様

### 1-画面初期化（initState）

画面表示時に自動的に以下の処理が実行されます：

1. CmsBlocを`context.read<CmsBloc>()`で取得
2. `FetchCmsDataEvent`イベントをBlocに送信（ページIDを引数として渡す）
3. Blocがリポジトリ経由でGraphQL APIを呼び出し
4. 取得結果に応じて状態が更新され、UIが再描画される

### 2-データ取得成功時の表示処理

- BlocConsumerのbuilderでFetchCmsDataStateを監視
- `CmsStatus.success`の場合、`_cmsContent()`メソッドでHTMLを表示
- 現在のロケールに一致する翻訳を`firstWhereOrNull`で検索
- `HtmlWidget`を使用してHTMLコンテンツをレンダリング

### 3-データ取得失敗時の表示処理

- `CmsStatus.fail`の場合、`EmptyDataView()`ウィジェットを表示
- ユーザーにコンテンツが利用できないことを通知

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 画面表示 | cms_pages（サーバー側） | SELECT | CMSページデータの取得（読み取りのみ） |

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

本画面はデータの読み取りのみを行い、データベースへの更新操作はありません。

#### GraphQL Query: cmsPage

| 操作 | 項目（パラメータ） | 取得条件 | 備考 |
|-----|-----------------|---------|------|
| SELECT | id | 引数で指定されたID | CMSページ識別子 |
| SELECT | translations | 全ロケールの翻訳 | 配列形式 |
| SELECT | translations.id | - | 翻訳レコードID |
| SELECT | translations.name | - | ページ名 |
| SELECT | translations.htmlContent | - | HTMLコンテンツ本体 |
| SELECT | translations.locale | - | 言語コード（en, jaなど） |

## メッセージ仕様

| メッセージID | メッセージ種別 | 表示条件 | メッセージ内容 | 表示方法 |
|-------------|---------------|----------|---------------|----------|
| MSG001 | エラー | データ取得失敗時 | エラーメッセージ（StringConstants.somethingWrong） | EmptyDataView |

## 例外処理

| 例外状況 | 対応処理 | ユーザーへの表示 |
|---------|---------|-----------------|
| ネットワークエラー | catch句でエラーキャッチ | EmptyDataViewを表示 |
| CMSページが見つからない | FetchCmsDataState.fail | EmptyDataViewを表示 |
| 該当ロケールの翻訳がない | firstWhereOrNull がnull返却 | 空のHTMLコンテンツを表示 |
| APIエラー応答 | FetchCmsDataState.fail | EmptyDataViewを表示 |

## 備考

- HtmlWidgetパッケージを使用してHTMLコンテンツをFlutterウィジェットに変換
- 多言語対応：GlobalData.localeに基づいて適切な翻訳を選択
- CmsPageデータモデルはjson_annotationで生成（cms_details.g.dart）
- データ取得中はSkeletonLoaderでローディング状態を視覚的に表示
- SafeAreaでノッチ等のシステムUI領域を考慮した表示

---

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

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

### 推奨読解順序

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

まず、CMSページのデータモデルとAPI応答の構造を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | cms_details.dart | `lib/screens/cms_screen/data_model/cms_details.dart` | CmsPageクラスとTranslationsクラスの構造を確認 |
| 1-2 | cms_event.dart | `lib/screens/cms_screen/bloc/cms_event.dart` | FetchCmsDataEventのパラメータ（id）を確認 |
| 1-3 | cms_state.dart | `lib/screens/cms_screen/bloc/cms_state.dart` | CmsStatusの状態（success, fail）とFetchCmsDataStateを確認 |

**読解のコツ**: CmsPageモデルのtranslationsは配列形式で、各要素がロケール別のコンテンツを保持します。htmlContentがHTMLコンテンツの本体です。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | cms_view.dart | `lib/screens/cms_screen/cms_view.dart` | ウィジェット構造、initState、BlocConsumerの使い方 |

**主要処理フロー**:
1. **行24-28**: initStateでCmsBlocを取得し、FetchCmsDataEventを送信
2. **行43-49**: BlocConsumerでCmsBaseStateを監視
3. **行53-75**: buildContainerで状態に応じたUIを構築
4. **行77-90**: _cmsContentでHTMLコンテンツをレンダリング

#### Step 3: ビジネスロジック層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | cms_bloc.dart | `lib/screens/cms_screen/bloc/cms_bloc.dart` | イベント受信からリポジトリ呼び出し、状態更新の流れ |

**主要処理フロー**:
- **行20-32**: FetchCmsDataEventを受信し、repository.callCmsDataを呼び出し
- **行24-26**: 成功時はFetchCmsDataState.successを発行
- **行28-29**: 失敗時はFetchCmsDataState.failを発行

#### Step 4: データアクセス層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | cms_repository.dart | `lib/screens/cms_screen/bloc/cms_repository.dart` | リポジトリインターフェースと実装 |
| 4-2 | api_client.dart | `lib/services/api_client.dart` | getCmsPageDetailsメソッドの実装（行430付近） |
| 4-3 | mutation_query.dart | `lib/services/mutation_query.dart` | getCmsPageDetails GraphQLクエリの定義（行272付近） |

**主要処理フロー**:
- **cms_repository.dart 行18-28**: ApiClient().getCmsPageDetailsを呼び出し
- **api_client.dart 行430-442**: GraphQL queryを実行しCmsPageを返却
- **mutation_query.dart 行272-286**: cmsPage queryのGraphQLクエリ定義

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

```
CmsContent (cms_view.dart)
    │
    ├─ initState() [行24-28]
    │      └─ CmsBloc.add(FetchCmsDataEvent)
    │
    └─ BlocConsumer<CmsBloc, CmsBaseState>
           │
           └─ CmsBloc (cms_bloc.dart)
                  │
                  └─ mapEventToState() [行20-32]
                         │
                         └─ CmsRepository.callCmsData()
                                │
                                └─ CmsRepositoryImp (cms_repository.dart)
                                       │
                                       └─ ApiClient().getCmsPageDetails() [行21]
                                              │
                                              └─ mutation.getCmsPageDetails() [GraphQL Query]
```

### データフロー図

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

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ 画面引数         │    │ FetchCmsDataEvent│    │ GraphQL API     │
│ - title         │───▶│ Bloc送信         │───▶│ cmsPage Query   │
│ - id            │    └──────────────────┘    └─────────────────┘
│ - index         │              │                      │
└─────────────────┘              │                      ▼
                                 │              ┌─────────────────┐
                                 │              │ CmsPage         │
                                 │              │ - id            │
                                 │              │ - translations  │
                                 │              └─────────────────┘
                                 │                      │
                                 ▼                      ▼
                       ┌──────────────────┐    ┌─────────────────┐
                       │ ロケールマッチング │    │ HtmlWidget      │
                       │ GlobalData.locale│───▶│ HTML表示        │
                       └──────────────────┘    └─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| cms_view.dart | `lib/screens/cms_screen/cms_view.dart` | View | CMSコンテンツ表示UI |
| cms_bloc.dart | `lib/screens/cms_screen/bloc/cms_bloc.dart` | Bloc | 状態管理とビジネスロジック |
| cms_event.dart | `lib/screens/cms_screen/bloc/cms_event.dart` | Event | Blocイベント定義 |
| cms_state.dart | `lib/screens/cms_screen/bloc/cms_state.dart` | State | Bloc状態定義 |
| cms_repository.dart | `lib/screens/cms_screen/bloc/cms_repository.dart` | Repository | データアクセス層 |
| cms_details.dart | `lib/screens/cms_screen/data_model/cms_details.dart` | Model | CmsPageデータモデル |
| cms_model.dart | `lib/screens/cms_screen/data_model/cms_model.dart` | Model | CMSデータ一覧モデル |
| api_client.dart | `lib/services/api_client.dart` | Service | APIクライアント（行430付近） |
| mutation_query.dart | `lib/services/mutation_query.dart` | Query | GraphQLクエリ定義（行272付近） |
| app_navigation.dart | `lib/utils/app_navigation.dart` | Router | 画面ルーティング定義（行177-184） |
| route_constants.dart | `lib/utils/route_constants.dart` | Constants | ルート名定数（行25） |
| index.dart | `lib/screens/cms_screen/utils/index.dart` | Barrel | エクスポートファイル |
