# 画面設計書 83-ReadingListEntryCollection画面

## 概要

本ドキュメントは、Wikipedia iOSアプリにおけるReadingListEntryCollection画面の設計仕様を定義するものである。この画面は、特定のリーディングリストに含まれる記事エントリをコレクションビュー形式で表示・管理するコンポーネントである。

### 本画面の処理概要

ReadingListEntryCollection画面は、選択されたリーディングリストの記事エントリ（ReadingListEntry）をNSFetchedResultsControllerを使用して動的に取得・表示する画面である。ユーザーはリスト内の記事を閲覧、検索、ソート、削除、他リストへの移動などの操作を行うことができる。

**業務上の目的・背景**：ユーザーが保存した記事を効率的に管理・閲覧するためのインターフェースを提供する。リーディングリスト機能はWikipediaの記事をオフラインで読むための重要な機能であり、本画面はその中核となるリスト内記事の一覧表示と操作を担当する。記事のバッチ編集（複数選択して移動・削除）や検索・ソート機能により、大量の保存記事を効率的に管理できる。

**画面へのアクセス方法**：ReadingListDetail画面の子ViewControllerとしてコンテナ内に埋め込まれて表示される。直接的なナビゲーション遷移ではなく、親画面がこのViewControllerをインスタンス化して管理する。

**主要な操作・処理内容**：
1. リーディングリスト内の記事エントリ一覧表示
2. 記事のタップによる詳細画面への遷移
3. スワイプによる個別記事の共有・削除
4. バッチ編集モードでの複数記事の一括操作（追加、移動、削除）
5. 検索バーによるリスト内記事の絞り込み
6. ソート機能（タイトル順、追加日順）
7. プルリフレッシュによるリーディングリストの同期
8. 失敗した記事ダウンロードの再試行

**画面遷移**：
- 遷移元：ReadingListDetail画面（親コンテナ）
- 遷移先：Article画面（記事詳細）、AddArticlesToReadingList画面（バッチ追加/移動）

**権限による表示制御**：ログイン状態によりリーディングリストの同期が有効/無効となる。デフォルトリストでは一部の編集機能が制限される。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 15 | リーディングリスト管理 | 補助機能 | リスト内記事の表示 |
| 14 | 記事保存 | 主機能 | 記事の保存・削除管理 |
| 16 | リーディングリスト同期 | API連携 | プルリフレッシュによる同期 |

## 画面種別

一覧

## URL/ルーティング

iOSネイティブアプリのため、URLルーティングなし。`ReadingListEntryCollectionViewController`のイニシャライザで`ReadingList`オブジェクトを受け取り、対象リストが決定される。

## 入出力項目

| 項目名 | 型 | 入力/出力 | 必須 | 説明 |
|--------|------|---------|------|------|
| readingList | ReadingList | 入力 | 必須 | 表示対象のリーディングリスト |
| dataStore | MWKDataStore | 入力 | 必須 | データストアへの参照 |
| searchString | String? | 入力 | 任意 | 検索フィルター文字列 |
| needsDetailHeaderView | Bool | 入力 | 任意 | ヘッダービュー表示有無 |

## 表示項目

| 項目名 | 説明 | 表示形式 |
|--------|------|----------|
| 記事タイトル | 記事の表示タイトル（displayTitle） | テキスト |
| 記事説明 | 記事の概要説明 | テキスト |
| 記事サムネイル | 記事のサムネイル画像 | 画像 |
| タグ | 所属リーディングリスト（デフォルトリスト時のみ） | バッジ |
| アラートアイコン | ダウンロード失敗等のエラー表示 | アイコン |
| リスト詳細ヘッダー | リスト名・説明・記事数（needsDetailHeaderView時） | ヘッダービュー |

## イベント仕様

### 1-記事セル選択

ユーザーがコレクションビューの記事セルをタップした際の処理。

1. `collectionView(_:didSelectItemAt:)`が呼び出される
2. editControllerがクローズ状態か確認
3. `articleURL(at:)`で選択されたインデックスパスに対応するURLを取得
4. delegateの`readingListEntryCollectionViewControllerDidSelectArticleURL`を呼び出し
5. ReadingListsFunnelでログを記録

### 2-スワイプアクション

セルを左にスワイプした際に表示されるアクションメニュー。

1. `availableActions(at:)`が呼び出される
2. 共有アクション（articleURLが存在する場合）
3. 削除アクション

### 3-バッチ編集ツールバーアクション

複数記事選択時のツールバーアクション。

1. `didPerformBatchEditToolbarAction(_:completion:)`が呼び出される
2. 選択されたインデックスパスから記事を取得
3. アクションタイプに応じて処理：
   - addTo: AddArticlesToReadingListViewControllerをモーダル表示
   - moveTo: moveFromReadingListを指定してAddArticlesToReadingListViewControllerを表示
   - remove: delete()で記事を削除

### 4-プルリフレッシュ

プルダウンによるリフレッシュ操作。

1. `refresh()`が呼び出される
2. `dataStore.readingListsController.fullSync`で同期実行
3. `retryFailedArticleDownloads`で失敗した記事のダウンロードを再試行
4. 完了後にendRefreshing()とreloadData()

### 5-ソート変更

ソートオプション変更時の処理。

1. `presentSortAlert(from:)`でアラートを表示
2. ユーザーがソート順を選択
3. `readingList.sortOrder`を更新
4. `reset()`でFRCを再設定し表示を更新

### 6-検索文字列変更

検索バーのテキスト変更時の処理。

1. delegateから`saved(_:searchBar:textDidChange:)`が呼び出される
2. `updateSearchString()`でsearchStringプロパティを更新
3. searchPredicateが生成される
4. FRCの述語が更新され、表示が絞り込まれる

### 7-記事変更通知

バックグラウンドで記事データが更新された際の処理。

1. `WMFArticleUpdated`通知を受信
2. `articleDidChange(_:)`で更新された記事がこのリストに含まれるか確認
3. 該当する可視セルのみをreloadItems

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 記事削除 | ReadingListEntry | DELETE | リストから記事エントリを削除 |
| ソート順変更 | ReadingList | UPDATE | sortOrderカラムを更新 |
| 同期 | ReadingListEntry | SYNC | リモートとローカルを同期 |
| データフェッチ | ReadingListEntry | SELECT | リスト内エントリを取得 |

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

#### ReadingListEntry

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | list, isDeletedLocally | list == readingList && isDeletedLocally != YES | basePredicate |
| DELETE | - | readingListsController.remove(articles:readingList:) | 複数記事対応 |

#### ReadingList

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | sortOrder | SortActionType.rawValue | ソート順変更時 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|----------|
| articleDeletedNotification | 通知 | 記事を削除しました（複数形対応） | 記事削除成功時（VoiceOver） |
| sortAlertTitle | タイトル | 並べ替え | ソートアラート表示時 |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| 無効なインデックスパス | isValidIndexPath確認後、nilを返却 |
| 記事取得失敗 | アラートボタンでエラー回復画面を表示 |
| 削除処理エラー | DDLogErrorでログ出力 |
| ダウンロード再試行エラー | DDLogErrorでログ出力 |

## 備考

- ColumnarCollectionViewControllerを継承し、多数のプロトコルに準拠
- EditableCollection、UpdatableCollection、SearchableCollectionプロトコルで編集・更新・検索機能を提供
- デフォルトリストではタグ表示が有効になり、記事が所属する他のリストを表示
- isDefaultがtrueの場合、一部の編集ボタンが非表示（shouldShowEditButtonsForEmptyState）
- eventLoggingCategoryは.saved

---

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

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

### 推奨読解順序

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

リーディングリストと記事エントリの関係を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ReadingList | `WMF/Sources/WMF/DataStore/ReadingList*.swift` | リストデータモデル、isDefault、sortOrder |
| 1-2 | ReadingListEntry | `WMF/Sources/WMF/DataStore/ReadingListEntry*.swift` | エントリデータモデル、articleKey、inMemoryKey |
| 1-3 | WMFArticle | `WMF/Sources/WMF/DataStore/WMFArticle*.swift` | 記事データモデル |

**読解のコツ**: ReadingListEntryはReadingListとWMFArticleを橋渡しするエンティティ。inMemoryKeyを使用して記事を取得する。

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

ViewControllerの初期化と基本プロパティを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ReadingListEntryCollectionViewController.swift | `Wikipedia/Code/ReadingListEntryCollectionViewController.swift` | init、プロパティ定義 |

**主要処理フロー**:
1. **54-58行目**: `init(for:with:)` - readingListとdataStoreを設定
2. **20-21行目**: `basePredicate` - FRCのベースとなる述語
3. **28-33行目**: `searchPredicate` - 検索時の追加述語

#### Step 3: データバインディングを理解する

FRCとUICollectionViewの連携を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ReadingListEntryCollectionViewController.swift | `Wikipedia/Code/ReadingListEntryCollectionViewController.swift` | setupFetchedResultsController、CollectionViewUpdaterDelegate |
| 3-2 | CollectionViewUpdater.swift | `Wikipedia/Code/CollectionViewUpdater.swift` | FRCデリゲートとUI更新 |

**主要処理フロー**:
- **79-84行目**: `viewWillAppear` - FRCセットアップとフェッチ
- **512-529行目**: `collectionViewUpdater(_:didUpdate:)` - 可視セルの再設定

#### Step 4: 編集機能を理解する

バッチ編集とスワイプアクションの実装を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ReadingListEntryCollectionViewController.swift | `Wikipedia/Code/ReadingListEntryCollectionViewController.swift` | ActionDelegate実装、didPerformBatchEditToolbarAction |

**主要処理フロー**:
- **35-41行目**: `availableBatchEditToolbarActions` - バッチ編集アクション定義
- **365-375行目**: `availableActions(at:)` - スワイプアクション定義
- **414-452行目**: `didPerformBatchEditToolbarAction` - バッチ操作実行

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

```
ReadingListEntryCollectionViewController
    │
    ├─ init(for:with:)
    │      ├─ readingList設定
    │      └─ dataStore設定
    │
    ├─ viewWillAppear(_:)
    │      ├─ setupFetchedResultsController()
    │      ├─ setupCollectionViewUpdater()
    │      └─ fetch()
    │
    ├─ basePredicate → NSPredicate
    │      └─ "list == %@ && isDeletedLocally != YES"
    │
    ├─ article(at:) → WMFArticle?
    │      ├─ entry(at:) → ReadingListEntry
    │      └─ dataStore.fetchArticle(withKey:variant:)
    │
    ├─ delete([WMFArticle])
    │      └─ readingListsController.remove(articles:readingList:)
    │
    └─ refresh()
           ├─ fullSync()
           └─ retryFailedArticleDownloads()
```

### データフロー図

```
[CoreData]                 [FRC/Updater]              [UI]

ReadingListEntry ◀──── NSFetchedResultsController ───▶ UICollectionView
       │                         │                          │
       │ (list参照)              │ (デリゲート)              │ (セル表示)
       ▼                         ▼                          ▼
WMFArticle ◀────────── fetchArticle(withKey:) ────────▶ SavedArticlesCell
                                                             │
                                                    ┌────────┴────────┐
                                                    ▼                 ▼
                                              タップ遷移         スワイプ操作
                                              Article画面        共有/削除
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ReadingListEntryCollectionViewController.swift | `Wikipedia/Code/ReadingListEntryCollectionViewController.swift` | ソース | メインViewController |
| ColumnarCollectionViewController.swift | `Wikipedia/Code/ColumnarCollectionViewController.swift` | ソース | カラムレイアウト基底クラス |
| CollectionViewUpdater.swift | `Wikipedia/Code/CollectionViewUpdater.swift` | ソース | FRC→UICollectionView橋渡し |
| SavedArticlesCollectionViewCell.swift | `Wikipedia/Code/SavedArticlesCollectionViewCell.swift` | ソース | 記事セルUI |
| AddArticlesToReadingListViewController.swift | `Wikipedia/Code/AddArticlesToReadingListViewController.swift` | ソース | バッチ追加/移動画面 |
| ReadingListsController.swift | `WMF/Sources/WMF/ReadingLists/ReadingListsController.swift` | ソース | リーディングリスト操作 |
| ReadingList | `WMF/Sources/WMF/DataStore/` | ソース | リストデータモデル |
| ReadingListEntry | `WMF/Sources/WMF/DataStore/` | ソース | エントリデータモデル |
