# 機能設計書 62-テーマ適用

## 概要

本ドキュメントは、Wikipedia iOSアプリにおける「テーマ適用」機能の設計仕様を記載する。アプリ全体のテーマ（ライト/ダーク/セピア等）の適用と管理機能を提供するものである。

### 本機能の処理概要

本機能は、アプリ全体の視覚的テーマを管理し、ユーザーの好みや閲覧環境に応じた表示スタイルを提供する。ライト、セピア、ダーク、ブラックの4種類のテーマと、画像の明度調整機能を含む。

**業務上の目的・背景**：長時間の記事閲覧において、目の疲労軽減と読みやすさの向上を図る。夜間や暗い環境での閲覧に対応し、ユーザーの閲覧体験を最適化する。また、セピアテーマにより紙の書籍に近い読書体験も提供する。

**機能の利用シーン**：
- 夜間や暗い環境で記事を読む際にダーク/ブラックテーマに切り替える
- 長時間の閲覧時に目の負担を軽減するためテーマを変更する
- 個人の好みに応じて読書体験をカスタマイズする
- 画面輝度とテーマを連動させて最適な表示を実現する

**主要な処理内容**：
1. テーマ（ライト/セピア/ダーク/ブラック）の選択と適用
2. アプリ全体の色設定（背景色、テキスト色、リンク色等）の変更
3. 画像の明度調整（ダーク系テーマ時の減光機能）
4. フォントサイズの調整
5. 画面輝度の調整
6. テーマ設定の永続化と復元
7. Themeableプロトコルによる全ViewControllerへの適用

**関連システム・外部連携**：
- UserDefaults: テーマ設定の永続化
- WebKit: 記事WebViewへのテーマ適用（CSS注入）
- WMFComponents: SwiftUIコンポーネントへのテーマ適用

**権限による制御**：認証不要で利用可能。全ユーザーが同等の機能を利用できる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 5 | Article画面 | 主画面 | 読書テーマコントロールの表示とテーマ切り替え |
| 42 | AppearanceSettings画面 | 主画面 | テーマ・フォントサイズ等の外観設定 |
| 75 | ReadingThemesControls画面 | 主画面 | 読書テーマコントロール |
| 76 | ThemeableViewController画面 | 基盤機能 | テーマ対応ViewControllerの基底機能 |

## 機能種別

設定管理 / UI制御 / 状態管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| themeName | String | No | テーマ名（light/sepia/dark/black等） | 有効なテーマ名のみ |
| isImageDimmingEnabled | Bool | No | 画像減光の有効/無効 | - |
| fontSizeMultiplier | Int | No | フォントサイズ係数 | 0-6の範囲 |
| brightness | Float | No | 画面輝度 | 0.0-1.0の範囲 |

### 入力データソース

- UserDefaults: 保存済みテーマ設定の読み込み
- UITraitCollection: システムの外観モード（ライト/ダーク）
- UIScreen.main.brightness: 現在の画面輝度

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| theme | Theme | 適用されるテーマオブジェクト |
| colors | Colors | テーマに対応する色セット |
| preferredStatusBarStyle | UIStatusBarStyle | ステータスバースタイル |
| keyboardAppearance | UIKeyboardAppearance | キーボード外観 |
| scrollIndicatorStyle | UIScrollView.IndicatorStyle | スクロールインジケータスタイル |

### 出力先

- 全てのThemeable準拠ViewController
- NavigationBar、TabBar、SearchBar等のUIコンポーネント
- WebView（CSSスタイル注入）
- SwiftUIビュー（WMFTheme経由）

## 処理フロー

### 処理シーケンス

```
1. テーマ選択（ReadingThemesControlsViewController）
   └─ ユーザーがテーマボタンをタップ
   └─ userDidSelect(theme:)を呼び出し
   └─ NotificationCenter経由で通知を送信

2. テーマ通知の受信
   └─ WMFUserDidSelectThemeNotification
   └─ themeName、isImageDimmingEnabledを取得

3. テーマの解決
   └─ Theme.withName(_:)でThemeオブジェクトを取得
   └─ withDimmingEnabled(_:)で減光設定を適用

4. テーマの適用
   └─ 各ViewControllerのapply(theme:)を呼び出し
   └─ Colorsプロパティから色を取得して適用
   └─ WebViewにはCSSとして注入

5. テーマの永続化
   └─ UserDefaultsにテーマ名を保存
```

### フローチャート

```mermaid
flowchart TD
    A[テーマボタンタップ] --> B{選択テーマ}
    B -->|Light| C[Theme.light]
    B -->|Sepia| D[Theme.sepia]
    B -->|Dark| E[Theme.dark]
    B -->|Black| F[Theme.black]

    C --> G[画像減光設定確認]
    D --> G
    E --> G
    F --> G

    G -->|減光ON| H[withDimmingEnabled true]
    G -->|減光OFF| I[通常テーマ]

    H --> J[Notification送信]
    I --> J

    J --> K[各VC受信]
    K --> L[apply theme]
    L --> M[Colors適用]
    M --> N[UI更新]

    J --> O[UserDefaults保存]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-62-01 | デフォルトテーマ | 初回起動時はlightテーマを適用 | テーマ未設定時 |
| BR-62-02 | 画像減光 | ダーク/ブラックテーマ時のみ画像減光オプションが有効 | isDark == true |
| BR-62-03 | システム連動 | システムの外観モードに応じてテーマを自動切り替え | Automatic設定時 |
| BR-62-04 | テーマ継承 | 子ViewControllerは親のテーマを継承する | Themeable準拠時 |

### 計算ロジック

画像不透明度の計算:
```swift
// ダーク系テーマで減光有効時
imageOpacity = Theme.dimmedImageOpacity // 0.65

// 通常時
imageOpacity = 1.0
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| テーマ保存 | UserDefaults | UPDATE | テーマ名の保存 |
| テーマ読み込み | UserDefaults | SELECT | 保存済みテーマ名の取得 |
| 減光設定保存 | UserDefaults | UPDATE | 画像減光設定の保存 |

### テーブル別操作詳細

#### UserDefaults

| 操作 | 項目（キー名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE/SELECT | themeName | "light"/"sepia"/"dark"/"black"等 | テーマ名 |
| UPDATE/SELECT | wmf_isImageDimmingEnabled | Bool | 画像減光設定 |
| UPDATE/SELECT | wmf_IsSyntaxHighlightingEnabled | Bool | シンタックスハイライト設定 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| E-62-01 | テーマ名不正 | 存在しないテーマ名指定 | デフォルトテーマ（light）を適用 |
| E-62-02 | 色解決失敗 | Colorsプロパティへのアクセス失敗 | フォールバック色を使用 |

### リトライ仕様

テーマ適用はリトライ不要。失敗時はデフォルト値で継続。

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

テーマ設定の保存はUserDefaultsへの単一書き込みであり、トランザクション管理は不要。

## パフォーマンス要件

- テーマ切り替え完了: 100ms以内（体感遅延なし）
- 全画面への適用: 画面遷移時に即時反映
- メモリ影響: テーマオブジェクトはシングルトン管理

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

- テーマ設定はローカルのみに保存
- 外部への設定情報送信なし
- WebViewへのCSS注入はサニタイズ済みの固定値のみ

## 備考

- Themeableプロトコルに準拠することで自動的にテーマ対応
- SwiftUIではWMFTheme経由でテーマを変換して使用
- Widget用に別途widgetLight/widgetDarkテーマが存在

---

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

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

### 推奨読解順序

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

テーマ機能の中核となるThemeクラスとColorsクラスを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Theme.swift | `WMF Framework/Theme.swift` | Themeクラス、Colorsクラス、Themeableプロトコルを理解する |

**読解のコツ**:
- Colors.Identifierでテーマの種別（light/sepia/dark/black）を区別
- 各色プロパティはswitch文でIdentifierに応じた色を返す
- Themeクラスはstaticプロパティで事前定義されたテーマを保持

**主要処理フロー**:
1. **94-108行目**: Colorsクラスのstatic定義（light/sepia/dark/black）
2. **820-993行目**: Themeクラスの定義と静的テーマオブジェクト
3. **995-999行目**: Themeableプロトコル定義

#### Step 2: テーマ適用の基盤を理解する

ThemeableViewControllerの実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ThemeableViewController.swift | `Wikipedia/Code/ThemeableViewController.swift` | 基本的なテーマ適用パターンを理解する |

**主要処理フロー**:
- **3-8行目**: ThemeableViewController基底クラスの実装
- **6-8行目**: apply(theme:)メソッドでthemeプロパティを更新

#### Step 3: テーマ選択UIを理解する

ReadingThemesControlsViewControllerの実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ReadingThemesControlsViewController.swift | `Wikipedia/Code/ReadingThemesControlsViewController.swift` | テーマ選択UIとNotification発火を理解する |

**主要処理フロー**:
- **12-14行目**: 通知名とキーの定義
- **77-90行目**: テーマボタンのアクセシビリティラベル設定
- **211-214行目**: userDidSelect(theme:)でNotification発火
- **216-230行目**: 各テーマボタンのアクション
- **240-281行目**: apply(theme:)でのUI更新

#### Step 4: SwiftUI連携を理解する

WMFThemeへの変換とObservableThemeを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Theme.swift | `WMF Framework/Theme.swift` | 末尾のTheme extension（wmfTheme変換）を確認 |
| 4-2 | WMFTheme.swift | `WMFComponents/Sources/WMFComponents/Style/WMFTheme.swift` | SwiftUI用テーマ定義を確認 |

**主要処理フロー**:
- **1001-1008行目**: ObservableThemeクラス（SwiftUI用）
- **1010-1032行目**: Theme.wmfTheme(from:)変換メソッド

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

```
ReadingThemesControlsViewController
    │
    ├─ lightThemeButtonPressed()
    │      └─ userDidSelect(theme: Theme.light)
    │
    ├─ sepiaThemeButtonPressed()
    │      └─ userDidSelect(theme: Theme.sepia)
    │
    ├─ darkThemeButtonPressed()
    │      └─ userDidSelect(theme: Theme.dark.withDimmingEnabled(...))
    │
    └─ blackThemeButtonPressed()
           └─ userDidSelect(theme: Theme.black.withDimmingEnabled(...))
                  │
                  └─ NotificationCenter.post(WMFUserDidSelectThemeNotification)
                         │
                         └─ 各ViewController受信
                                │
                                └─ apply(theme:)
                                       │
                                       ├─ view.backgroundColor = theme.colors.paperBackground
                                       ├─ label.textColor = theme.colors.primaryText
                                       └─ ...その他のUI要素
```

### データフロー図

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

テーマボタンタップ ───▶ userDidSelect(theme:) ───▶ Notification送信
                                                         │
                                                         ▼
                                                    受信側VC
                                                         │
                                                         ▼
                              Theme ◀─── apply(theme:) ───▶ Colors取得
                                                              │
                                                              ▼
                                                       UI要素に色適用
                                                              │
                                                              ▼
                                             ┌────────────────┼────────────────┐
                                             │                │                │
                                             ▼                ▼                ▼
                                      backgroundColor    textColor        tintColor

UserDefaults ◀─────────────────────────── テーマ名保存
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Theme.swift | `WMF Framework/Theme.swift` | ソース | テーマ・色定義の中核クラス |
| ThemeableViewController.swift | `Wikipedia/Code/ThemeableViewController.swift` | ソース | テーマ対応VCの基底クラス |
| ReadingThemesControlsViewController.swift | `Wikipedia/Code/ReadingThemesControlsViewController.swift` | ソース | テーマ選択UI |
| ReadingThemesControlsProtocols.swift | `Wikipedia/Code/ReadingThemesControlsProtocols.swift` | ソース | テーマコントロールのプロトコル定義 |
| ThemeableTextView.swift | `Wikipedia/Code/ThemeableTextView.swift` | ソース | テーマ対応テキストビュー |
| ThemeableTextField.swift | `WMF Framework/ThemeableTextField.swift` | ソース | テーマ対応テキストフィールド |
| UISearchBar+Theme.swift | `Wikipedia/Code/UISearchBar+Theme.swift` | ソース | 検索バーのテーマ適用 |
| UITabBar+Theme.swift | `Wikipedia/Code/UITabBar+Theme.swift` | ソース | タブバーのテーマ適用 |
| WMFTheme.swift | `WMFComponents/Sources/WMFComponents/Style/WMFTheme.swift` | ソース | SwiftUI用テーマ定義 |
| ArticleCollectionViewCell+Themeable.swift | `WMF Framework/ArticleCollectionViewCell+Themeable.swift` | ソース | 記事セルのテーマ適用 |
| ReadingThemesControlsViewController.xib | `Wikipedia/iOS Native/ReadingThemesControlsViewController.xib` | XIB | テーマコントロールUI定義 |
