# 画面設計書 13-CreateReadingList画面

## 概要

本ドキュメントは、Wikipedia iOSアプリにおける新規リーディングリスト作成画面（CreateReadingListViewController）の設計仕様を定義するものです。

### 本画面の処理概要

新規リーディングリスト作成画面は、ユーザーが新しいリーディングリストを作成するためのモーダル画面です。リスト名と説明を入力し、オプションで記事を同時に追加することができます。また、共有リーディングリストのインポート機能もこの画面で処理されます。

**業務上の目的・背景**：ユーザーがWikipedia記事を整理するためには、テーマやトピックごとにリーディングリストを作成する必要があります。本画面は、シンプルなフォーム入力でリストを作成できるインターフェースを提供し、記事を保存する際のワークフローを効率化します。共有リンクからのインポート機能により、他ユーザーが作成したリストを簡単に取り込むことも可能です。

**画面へのアクセス方法**：ReadingLists画面の「Create new list」ボタン、記事保存時の「Add to reading list」アクションシートから「Create new list」を選択、または共有リーディングリストのリンクを開くことでアクセスします。

**主要な操作・処理内容**：
1. リスト名の入力（必須）
2. リスト説明の入力（任意）
3. 「Create reading list」ボタンで作成を実行
4. 共有リンクからのインポート時は、ページIDをデコードして記事を取得
5. 作成時に指定された記事を自動的にリストに追加
6. moveFromReadingList指定時は、元リストから記事を移動

**画面遷移**：
- 遷移元: ReadingLists画面、AddArticlesToReadingList画面、共有リンク
- 遷移先: なし（モーダルを閉じて呼び出し元に戻る）

**権限による表示制御**：特になし。すべてのユーザーがリーディングリストを作成可能。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 17 | リスト新規作成 | 主機能 | 新しいリーディングリストの作成 |

## 画面種別

登録

## URL/ルーティング

iOSネイティブアプリのため、URLルーティングは適用されません。ただし、共有リーディングリストのインポートはURLスキーム（Universal Link）経由で起動されます。

## 入出力項目

| 項目名 | 型 | 入力/出力 | 必須 | 説明 |
|--------|-----|---------|------|------|
| theme | Theme | 入力 | 必須 | 表示テーマ |
| articles | [WMFArticle] | 入力 | 任意 | 作成時に追加する記事 |
| moveFromReadingList | ReadingList | 入力 | 任意 | 移動元のリスト（記事移動時） |
| encodedPageIds | String | 入力 | 任意 | 共有リンクのエンコード済みページID |
| dataStore | MWKDataStore | 入力 | 任意 | データストア（インポートモード時必須） |

## 表示項目

| 項目名 | 表示形式 | 説明 |
|--------|---------|------|
| リスト名ラベル | テキスト | "Reading list name" |
| リスト名入力フィールド | テキストフィールド | プレースホルダー: "reading list title" |
| 説明ラベル | テキスト | "Description" |
| 説明入力フィールド | テキストフィールド | プレースホルダー: "optional short description" |
| 作成ボタン | ボタン | "Create reading list" |
| エラーラベル | テキスト（赤） | 同名リスト存在時のエラー表示 |
| ローディングビュー | スピナー | インポートモード時のローディング表示 |

## イベント仕様

### 1-作成ボタンタップ

リーディングリストを作成する処理。

- リスト名が空でないことを確認
- delegate.createReadingListViewController(_:didCreateReadingListWith:description:articles:)を呼び出し
- delegateがreadingListsController.createReadingList()を実行
- moveFromReadingList指定時は、元リストから記事を削除

### 2-キャンセルボタンタップ

モーダルを閉じる処理。

- navigationController?.dismiss(animated:)を呼び出し
- インポートモードの場合、ReadingListsFunnel.shared.logCancelImport()でログ記録

### 3-テキストフィールド入力

入力値の検証とボタン状態の更新。

- リスト名が空白のみの場合、作成ボタンを無効化
- 同名リストエラー表示中に入力があった場合、エラーを非表示
- Returnキーで次のフィールドまたは作成ボタンにフォーカス移動

### 4-共有リストインポート（インポートモード時）

エンコードされたページIDから記事を取得する処理。

- Base64デコードしてImportedReadingListを取得
- PageIDToURLFetcherでページURLを取得
- ArticleFetcherで記事サマリーを取得
- 取得完了後、リスト名に"My Reading List"をデフォルト設定

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| リスト作成 | ReadingList | INSERT | 新しいリーディングリストエンティティを作成 |
| 記事追加 | ReadingListEntry | INSERT | 指定記事のエントリを作成 |
| 記事移動 | ReadingListEntry | DELETE | 元リストからエントリを削除 |

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

#### ReadingList

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | canonicalName | テキストフィールドの入力値（トリミング済み） | リスト名（必須） |
| INSERT | readingListDescription | テキストフィールドの入力値（トリミング済み） | 説明（任意） |
| INSERT | createdDate | 現在日時 | 作成日時 |
| INSERT | isDefault | NO | デフォルトリストではない |

#### ReadingListEntry

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | list | 作成されたReadingList | リストへの参照 |
| INSERT | articleKey | 各記事のキー | 記事の識別子 |
| DELETE | - | moveFromReadingListの記事 | 移動元から削除 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|---------|
| reading-list-create-new-list-reading-list-name | ラベル | "Reading list name" | 常時表示 |
| reading-list-create-new-list-description | ラベル | "Description" | 常時表示 |
| reading-list-new-list-name-placeholder | プレースホルダー | "reading list title" | リスト名フィールド |
| reading-list-new-list-description-placeholder | プレースホルダー | "optional short description" | 説明フィールド |
| reading-list-create-new-list-button-title | ボタン | "Create reading list" | 作成ボタン |
| import-shared-reading-list-title | タイトル | "Import shared reading list" | インポートモード時 |
| import-shared-reading-list-default-title | デフォルト値 | "My Reading List" | インポート時のデフォルトリスト名 |
| ReadingListError.listExistsWithTheSameName | エラー | ローカライズされたエラー | 同名リスト存在時 |

## 例外処理

| 例外状況 | 処理内容 |
|---------|---------|
| 同名リスト存在 | handleReadingListNameError()でエラーラベルを表示、作成ボタンを無効化 |
| インポートデータデコード失敗 | ImportReadingListError.failureDecodingPayloadをアラート表示 |
| ページURL取得失敗 | ImportReadingListError.failureFetchingPageURLsをアラート表示 |
| 記事オブジェクト取得失敗 | ImportReadingListError.failureFetchingArticleObjectsをアラート表示 |
| データストア未設定 | ImportReadingListError.missingDataStoreをアラート表示 |

## 備考

- WMFScrollViewControllerを継承（キーボード対応のスクロールビュー）
- WMFAuthButtonを使用（テーマ対応のボタン）
- ThemeableTextFieldを使用（テーマ対応のテキストフィールド）
- CreateReadingListDelegateプロトコルでリスト作成を通知
- isInImportingModeでインポートモードを判定

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | CreateReadingListViewController.swift | `Wikipedia/Code/CreateReadingListViewController.swift` | ImportedReadingList構造体（5-9行目） |
| 1-2 | ImportReadingListError | `Wikipedia/Code/CreateReadingListViewController.swift` | エラー定義（11-17行目） |

**読解のコツ**: ImportedReadingListはJSON形式でBase64エンコードされた共有リストデータをデコードするための構造体。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | CreateReadingListViewController.swift | `Wikipedia/Code/CreateReadingListViewController.swift` | init()（72-79行目）、viewDidLoad()（85-105行目） |

**主要処理フロー**:
1. **72-79行目**: 初期化、プロパティ設定
2. **85-105行目**: viewDidLoad - UI設定、テキストフィールドデリゲート、インポートモードチェック
3. **167-198行目**: setupForImportingReadingList - インポート処理

#### Step 3: フォーム処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | CreateReadingListViewController.swift | `Wikipedia/Code/CreateReadingListViewController.swift` | UITextFieldDelegateメソッド（212-246行目） |

**主要処理フロー**:
- **222-227行目**: textFieldDidChange - 入力値の検証とボタン状態更新
- **229-238行目**: textFieldShouldReturn - フォーカス移動
- **240-246行目**: textFieldShouldClear - クリア時の処理

#### Step 4: インポート処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | CreateReadingListViewController.swift | `Wikipedia/Code/CreateReadingListViewController.swift` | articlesFromEncodedPageIds()（277-301行目） |

**主要処理フロー**:
- **277-301行目**: エンコードされたページIDから記事を取得
- **303-310行目**: decodedReadingListFromEncodedPageIds - Base64デコード
- **312-367行目**: pageURLsFromImportedReadingList - ページURLを取得
- **369-401行目**: articleObjectsFromArticleURLs - 記事オブジェクトを取得

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

```
CreateReadingListViewController
    │
    ├─ init()
    │      └─ プロパティ初期化
    │
    ├─ viewDidLoad()
    │      ├─ テキストフィールド設定
    │      ├─ ボタン設定
    │      └─ [インポートモード] setupForImportingReadingList()
    │                              └─ articlesFromEncodedPageIds()
    │                                     ├─ decodedReadingListFromEncodedPageIds()
    │                                     ├─ pageURLsFromImportedReadingList()
    │                                     │      └─ PageIDToURLFetcher.fetchPageURLs()
    │                                     └─ articleObjectsFromArticleURLs()
    │                                            └─ ArticleFetcher.fetchArticleSummaryResponsesForArticles()
    │
    ├─ createReadingListButtonPressed()
    │      └─ delegate?.createReadingListViewController(_:didCreateReadingListWith:...)
    │             └─ [ReadingListsViewController] readingListsController.createReadingList()
    │
    └─ closeButtonTapped()
           └─ navigationController?.dismiss()
```

### データフロー図

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

テキストフィールド          CreateReadingListViewController   CoreData
(リスト名/説明)        ───▶ createReadingListButtonPressed() ───▶ ReadingList (INSERT)
                             └─ delegate?.createReadingList...

共有リンク                  articlesFromEncodedPageIds()      [WMFArticle]
(encodedPageIds)       ───▶ ├─ Base64デコード              ───▶ リスト作成時に追加
                             ├─ PageIDToURLFetcher
                             └─ ArticleFetcher
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| CreateReadingListViewController.swift | `Wikipedia/Code/CreateReadingListViewController.swift` | ソース | メインViewController |
| CreateReadingListViewController.xib | `Wikipedia/Code/CreateReadingListViewController.xib` | XIB | レイアウト定義 |
| WMFScrollViewController.swift | `Wikipedia/Code/WMFScrollViewController.swift` | ソース | 基底クラス（キーボード対応） |
| ThemeableTextField.swift | `Wikipedia/Code/ThemeableTextField.swift` | ソース | テーマ対応テキストフィールド |
| WMFAuthButton.swift | `Wikipedia/Code/WMFAuthButton.swift` | ソース | テーマ対応ボタン |
| PageIDToURLFetcher.swift | `Wikipedia/Code/PageIDToURLFetcher.swift` | ソース | ページIDからURLを取得 |
| ArticleFetcher.swift | `Wikipedia/Code/ArticleFetcher.swift` | ソース | 記事データを取得 |
| ReadingListsController.swift | `Wikipedia/Code/ReadingListsController.swift` | ソース | リスト作成ロジック |
