# 機能設計書 55-続きを読むウィジェット

## 概要

本ドキュメントは、Wikipedia iOSアプリにおける「続きを読む」ウィジェット（Continue Reading Widget）の設計を記述する。このウィジェットは、ユーザーが最近読んでいた記事への素早いアクセスを提供するiOSウィジェット機能である。

### 本機能の処理概要

**業務上の目的・背景**：ユーザーがホーム画面やToday画面から最近閲覧していた記事に素早くアクセスできるようにすることで、継続的な読書体験を促進する。中断した記事の閲覧をシームレスに再開できる。

**機能の利用シーン**：ユーザーがホーム画面でウィジェットを確認し、タップすることで最近閲覧していた記事をWikipediaアプリで開く。記事のサムネイル画像、タイトル、閲覧からの経過日数が表示される。

**主要な処理内容**：
1. 最近閲覧した記事情報の取得
2. ウィジェットUIの表示（画像、タイトル、説明）
3. 閲覧からの経過日数表示
4. タップ時のアプリ起動と記事表示
5. 記事がない場合の空状態表示

**関連システム・外部連携**：
- NotificationCenter（NCWidgetProviding）
- App Group経由でのMWKDataStore共有
- WidgetController（データストア管理）

**権限による制御**：特別な権限は不要。ウィジェット追加はユーザーの任意。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 5 | Article画面 | 遷移先 | ウィジェットタップで記事表示 |

## 機能種別

ウィジェット / データ表示

## 入力仕様

### 入力パラメータ

ウィジェットシステムからの自動更新リクエスト

### 入力データソース

- MWKDataStore.viewContext.openArticleURL（現在開いている記事）
- MWKDataStore.viewContext.mostRecentlyReadArticle（最近読んだ記事）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| articleTitle | String | 記事タイトル |
| articleSubtitle | String | Wikidata説明またはスニペット |
| thumbnailImage | UIImage | サムネイル画像 |
| daysAgo | String | 閲覧からの経過日数 |

### 出力先

iOSウィジェット表示領域

## 処理フロー

### 処理シーケンス

```
1. ウィジェット更新リクエスト
   └─ widgetPerformUpdate(completionHandler:)
2. データストア取得
   └─ WidgetController.shared.startWidgetUpdateTask()
3. 記事データ取得
   └─ openArticleURL または mostRecentlyReadArticle
4. 表示データ構築
   └─ タイトル、説明、画像、経過日数
5. 画像読み込み
   └─ imageView.wmf_setImage()
6. 表示更新
   └─ completionHandler(.newData) or (.noData)
```

### フローチャート

```mermaid
flowchart TD
    A[ウィジェット更新リクエスト] --> B[データストア取得]
    B --> C{開いている記事あり?}
    C -->|Yes| D[開いている記事を使用]
    C -->|No| E{最近読んだ記事あり?}
    E -->|Yes| F[最近読んだ記事を使用]
    E -->|No| G[空状態表示]
    D --> H[表示データ構築]
    F --> H
    H --> I{記事URLに変更あり?}
    I -->|Yes| J[UI更新]
    I -->|No| K[完了（noData）]
    J --> L{画像あり?}
    L -->|Yes| M[画像読み込み]
    L -->|No| N[画像非表示]
    M --> O[完了（newData）]
    N --> O
    G --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-55-01 | 優先順位 | 開いている記事 > 最近読んだ記事 | 記事選択時 |
| BR-55-02 | フラグメント付加 | 閲覧位置のフラグメントをURLに付加 | 記事URL生成時 |
| BR-55-03 | 経過日数表示 | 相対的な日時表示 | 閲覧日時がある場合 |
| BR-55-04 | 空状態メッセージ | 「最近読んだ記事はありません」を表示 | 記事なし時 |

### 計算ロジック

- 経過日数: viewedDateからの相対日時計算

## データベース操作仕様

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 記事取得 | WMFArticle | SELECT | 最近閲覧した記事情報取得 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| noData | データなし | 閲覧記事が存在しない | 空状態表示 |
| imageLoadError | 画像読み込みエラー | サムネイル取得失敗 | 画像を非表示に |

### リトライ仕様

なし（次回更新時に再試行）

## トランザクション仕様

読み取り専用（SELECT操作のみ）

## パフォーマンス要件

- ウィジェット更新は非同期で実行
- 画像読み込みはフェイス検出付き

## セキュリティ考慮事項

- App Groupを経由したデータ共有
- ウィジェット拡張内でのデータストアアクセス

## 備考

このウィジェットは非推奨（@available(*, deprecated)）とマークされており、iOS 14以降のホーム画面ウィジェットへの移行が推奨されている。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | WMFArticle | `WMF Framework/` | 記事データモデル |

**読解のコツ**: url, displayTitleHTML, capitalizedWikidataDescriptionOrSnippet, viewedDate, viewedFragmentに注目

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | WMFTodayContinueReadingWidgetViewController.swift | `ContinueReadingWidget/` | メインコントローラ |

**主要処理フロー**:
1. **37-46行目**: viewDidLoad() - 初期設定
2. **57-67行目**: widgetPerformUpdate() - 更新エントリーポイント
3. **89-156行目**: updateView() - ビュー更新ロジック
4. **170-172行目**: continueReading() - タップハンドリング

#### Step 3: データストア管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | WidgetController.swift | `WMF Framework/Widget/` | データストア管理 |

**主要処理フロー**:
- **68-77行目**: startWidgetUpdateTask() - データストア取得と解放

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

```
iOS Widget System
    │
    └─ WMFTodayContinueReadingWidgetViewController
           ├─ widgetPerformUpdate()
           │      └─ WidgetController.startWidgetUpdateTask()
           │             └─ MWKDataStore取得
           │
           ├─ updateView()
           │      ├─ viewContext.openArticleURL
           │      ├─ viewContext.mostRecentlyReadArticle
           │      └─ imageView.wmf_setImage()
           │             └─ ImageCacheController
           │
           └─ continueReading()
                  └─ openApp(with: articleURL)
```

### データフロー図

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

Widget更新 ───▶ WidgetController ───▶ MWKDataStore
リクエスト        │                        │
                 │                        ▼
                 │               viewContext取得
                 │                        │
                 ▼                        ▼
        WMFTodayContinueReading... ───▶ WMFArticle
                 │                        │
                 ▼                        ▼
           UI更新 ───────────────▶ ウィジェット表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| WMFTodayContinueReadingWidgetViewController.swift | `ContinueReadingWidget/` | ソース | ウィジェットUI |
| ExtensionViewController.swift | `WMF Framework/` | ソース | 基底クラス |
| WidgetController.swift | `WMF Framework/Widget/` | ソース | データストア管理 |
| WMFArticle+Extensions.swift | `WMF Framework/` | ソース | 記事拡張 |
| ImageCacheController.swift | `WMF Framework/` | ソース | 画像キャッシュ |
