# Phase A 緊急修正計画（v1.0.10）

## 🎯 目的
ユーザー体験を著しく阻害している3つの問題を緊急修正

---

## 📋 修正タスク一覧

### A1. 酒向タイプ診断「おすすめを見る」ボタン修正

#### 現状の問題
- ボタン名: 「おすすめを見る」
- 実際の動作: 診断結果を保存するだけ
- **ユーザーの期待**: おすすめの日本酒が表示される
- **実際**: SnackBarで「保存しました」メッセージのみ → **混乱を招く**

#### 修正方針
**オプション1**: ボタン名を現状の動作に合わせる（最小限の変更）

```dart
// lib/widgets/mbti/mbti_result_card.dart:170
// Before:
label: const Text("おすすめを見る"),

// After:
label: const Text("診断結果を保存"),
```

**オプション2**: おすすめ機能を簡易実装する（時間がある場合）

```dart
// lib/screens/placeholders/sommelier_screen.dart:438-446
onShowRecommendations: () {
  Navigator.pop(dialogContext);
  ref.read(userProfileProvider.notifier).setSakePersonaMbti(result.type.code);

  if (!mounted) return;

  // Filter recommendations based on MBTI type
  final recommendations = MBTIType.types[result.type.code]?.recommendedStyles ?? '';
  final allItems = ref.read(allSakeItemsProvider).value ?? [];
  final matchedItems = allItems.where((item) {
    // Simple matching: check if flavor tags or type match recommendations
    return item.hiddenSpecs.type?.contains(recommendations) ?? false ||
           item.hiddenSpecs.flavorTags.any((tag) => recommendations.contains(tag));
  }).toList();

  Navigator.of(context).push(MaterialPageRoute(
    builder: (_) => RecommendationsScreen(
      mbtiType: result.type.code,
      recommendations: matchedItems,
    ),
  ));
},
```

**推奨**: **オプション1**（ボタン名変更のみ）
- 理由: 最小限の変更で混乱を即座に解消
- 将来的に本格的なレコメンド機能を実装する際、ボタンを「おすすめを見る」に戻せばOK

#### ファイル
- `lib/widgets/mbti/mbti_result_card.dart` (1箇所のみ)

---

### A2. SnackBar Duration 統一

#### 現状の問題
| 箇所 | Duration | 問題 |
|------|----------|------|
| 解析エラー | **未指定** | ずっと表示され続ける |
| API制限 | **未指定** | ずっと表示され続ける |
| Draft保存 | 5秒 | OK（やや長い） |
| 登録成功 | 4秒/6秒 | バラバラ |
| ギャラリー保存失敗 | 1秒 | 短すぎる |

#### 修正方針
**すべてのSnackBarに明示的なdurationを指定**

#### 基準
```dart
// 成功系（通常）
Duration(seconds: 3)

// 成功系（重要情報あり: バッジ、レベルアップ等）
Duration(seconds: 4)

// 警告系
Duration(seconds: 4)

// エラー系
Duration(seconds: 5)
```

#### 修正箇所リスト

##### camera_screen.dart
```dart
// 1. API利用制限エラー (Line 183-186)
ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(
    content: Text('AI利用制限中です。あと${remaining.inSeconds}秒お待ちください。'),
    duration: const Duration(seconds: 5), // 追加
    backgroundColor: appColors.error,      // 追加
  ),
);

// 2. ギャラリー保存失敗 (Line 230-232)
ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(
    content: Text('ギャラリー保存に失敗しました: $e'),
    duration: const Duration(seconds: 4), // 変更（1秒→4秒）
    backgroundColor: appColors.warning,    // 追加
  ),
);

// 3. 解析エラー (Line 286-288)
ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(
    content: Text('解析エラー: $e'),
    duration: const Duration(seconds: 5), // 追加
    backgroundColor: appColors.error,      // 追加
  ),
);
```

##### pending_analysis_screen.dart
```dart
// 1. オフライン警告 (Line 63-71)
ScaffoldMessenger.of(context).showSnackBar(
  const SnackBar(
    content: Text('オフライン状態です。インターネット接続を確認してください。'),
    backgroundColor: Colors.red,
    duration: Duration(seconds: 4), // 追加
  ),
);

// 2. 一括解析成功 (Line 105)
ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(
    content: Text('✅ すべてのDraft（$successCount件）を解析しました！'),
    backgroundColor: Colors.green,
    duration: const Duration(seconds: 3), // 追加
  ),
);

// 3. Draft削除成功 (Line 209)
ScaffoldMessenger.of(context).showSnackBar(
  const SnackBar(
    content: Text('Draftを削除しました'),
    duration: Duration(seconds: 3), // 追加
  ),
);

// 4. Draft削除エラー (Line 213)
ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(
    content: Text('削除エラー: $e'),
    duration: const Duration(seconds: 5), // 追加
  ),
);

// 5. 全件削除成功 (Line ~260頃、正確な行番号は要確認)
ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(
    content: Text('$count件のDraftを削除しました'),
    duration: const Duration(seconds: 3), // 追加
  ),
);
```

##### sommelier_screen.dart
```dart
// 1. 診断結果保存 (Line 444-446)
ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(
    content: Text('「${result.type.title}」として診断結果を保存しました！'),
    duration: const Duration(seconds: 3), // 追加
  ),
);

// 2. データ不足エラー (Line 393-398)
ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(
    content: Text('データ不足です！あと${5 - sakeList.length}本の記録が必要です。'),
    backgroundColor: Theme.of(context).colorScheme.error,
    duration: const Duration(seconds: 4), // 追加
  ),
);
```

#### ファイル
- `lib/screens/camera_screen.dart` (3箇所)
- `lib/screens/pending_analysis_screen.dart` (5箇所)
- `lib/screens/placeholders/sommelier_screen.dart` (2箇所)

**合計**: 10箇所

---

### A3. camera_screen.dart のテーマカラー修正

#### 現状の問題
1. **撮影後ダイアログ**: `AppTheme.posimaiBlue` 固定 → 和モダンテーマ無視
2. **オフラインSnackBar**: `Colors.orange` 固定 → テーマカラー無視
3. **経験値SnackBar**: `Colors.yellow`, `Colors.greenAccent` 固定 → テーマカラー無視

#### 修正方針
**すべてテーマカラー（`appColors.*`）を使用**

ただし、以下の配慮が必要：
- **オフライン警告**: オレンジは視覚的に「警告」を意味するため、テーマに関係なくオレンジ系を維持
- **経験値/バッジ**: ゲーミフィケーション演出なので、黄色・緑は維持してもOK（ただし、ダークモードで視認性確保）

#### 修正内容

##### 1. 撮影後ダイアログ (Line 301-328)
```dart
Future<void> _handleCapturedImage(String imagePath, {bool fromGallery = false}) async {
  // ...
  final appColors = Theme.of(context).extension<AppColors>()!; // 追加

  await showDialog(
    context: context,
    barrierDismissible: false,
    builder: (ctx) => AlertDialog(
      title: Text(fromGallery ? '画像を読み込みました' : '写真を保存しました'),
      content: const Text('さらに別の面も撮影・追加すると、\nAI解析の精度がアップします！'),
      actions: [
        OutlinedButton(
          onPressed: () {
            Navigator.of(context).pop();
            _analyzeImages();
          },
          style: OutlinedButton.styleFrom(
            foregroundColor: appColors.brandPrimary, // 追加
            side: BorderSide(color: appColors.brandPrimary), // 追加
          ),
          child: const Text('解析開始'),
        ),
        FilledButton(
          onPressed: () {
            Navigator.of(context).pop();
          },
          style: FilledButton.styleFrom(
            backgroundColor: appColors.brandPrimary, // 変更（AppTheme.posimaiBlue → appColors.brandPrimary）
            foregroundColor: Colors.white,
          ),
          child: const Text('さらに追加'),
        ),
      ],
    ),
  );
}
```

##### 2. オフラインSnackBar (Line 110-131)
**Option A**: テーマカラー適用（推奨）
```dart
// appColorsにwarningカラーがない場合は、固定色を調整
final warningColor = Theme.of(context).brightness == Brightness.dark
    ? Colors.orange.shade700  // ダークモード: 濃いオレンジ
    : Colors.orange.shade600; // ライトモード: 通常オレンジ

ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(
    content: Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Icon(LucideIcons.wifiOff, color: Colors.white, size: 16), // 白に変更（視認性）
            SizedBox(width: 8),
            Text('オフライン検知', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white)),
          ],
        ),
        SizedBox(height: 4),
        Text('写真を「解析待ち」として保存しました。', style: TextStyle(color: Colors.white)),
        Text('オンライン復帰後、ホーム画面から解析できます。', style: TextStyle(color: Colors.white)),
      ],
    ),
    duration: Duration(seconds: 5),
    backgroundColor: warningColor, // 変更
  ),
);
```

**Option B**: 現状維持（オレンジ固定）
- 警告色は universal なので、テーマに関わらず `Colors.orange` でOK
- その場合は変更不要

**推奨**: **Option B**（現状維持）
- オレンジは「注意・警告」の universal color
- テーマ変更の影響を受けない方が視認性が高い

##### 3. 経験値SnackBar (Line 264-273)
**Option A**: テーマカラー適用
```dart
final isDark = Theme.of(context).brightness == Brightness.dark;

ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(
    content: Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: messageWidgets,
    ),
    duration: Duration(seconds: newBadges.isNotEmpty ? 6 : 4),
    backgroundColor: isDark ? Colors.grey.shade800 : null, // ダークモード対応
  ),
);

// アイコン色も調整
Row(
  children: [
    Icon(LucideIcons.sparkles,
         color: isDark ? Colors.yellow.shade300 : Colors.yellow, // ダークモード: 薄い黄色
         size: 16),
    // ...
  ],
)
```

**Option B**: 現状維持（黄色・緑固定）
- ゲーミフィケーション演出は色が重要
- 黄色=経験値、緑=バッジ は直感的

**推奨**: **Option B**（現状維持）、ただし**ダークモード対応のみ追加**
```dart
final isDark = Theme.of(context).brightness == Brightness.dark;

Icon(LucideIcons.sparkles,
     color: isDark ? Colors.yellow.shade300 : Colors.yellow,
     size: 16),
// ...
Text('バッジ獲得: ${badge.name}',
     style: TextStyle(
       fontWeight: FontWeight.bold,
       color: isDark ? Colors.green.shade300 : Colors.greenAccent,
     )),
```

#### ファイル
- `lib/screens/camera_screen.dart` (2箇所: ダイアログ、経験値SnackBar)

---

## 📊 修正サマリー

| タスク | 優先度 | 修正箇所数 | 推定時間 |
|--------|--------|------------|----------|
| A1. ボタン名修正 | 🔥 最高 | 1箇所 | 5分 |
| A2. SnackBar Duration | 🔥 最高 | 10箇所 | 30分 |
| A3. テーマカラー修正 | 🔥 高 | 2箇所 | 20分 |
| **合計** | - | **13箇所** | **約1時間** |

---

## ✅ テストチェックリスト

### A1. ボタン名修正
- [ ] 酒向タイプ診断を実行
- [ ] 結果ダイアログのボタンが「診断結果を保存」になっているか確認
- [ ] ボタン押下 → SnackBarで「保存しました」メッセージ表示
- [ ] ダイアログが閉じることを確認

### A2. SnackBar Duration
- [ ] カメラ撮影 → API制限エラー → 5秒で自動的に消える
- [ ] オフライン撮影 → Draft保存 → 5秒で消える
- [ ] 解析エラー発生 → 5秒で消える
- [ ] Draft一括解析成功 → 3秒で消える
- [ ] Draft削除 → 3秒で消える

### A3. テーマカラー修正
- [ ] **爽やかテーマ**: 撮影後ダイアログのボタンが青色
- [ ] **和モダンテーマ**: 撮影後ダイアログのボタンが和モダン色（赤茶系）
- [ ] ライトモード: 経験値SnackBar の黄色が視認可能
- [ ] ダークモード: 経験値SnackBar の黄色が薄くなり視認可能

---

## 🚀 リリース手順

### 1. 修正実装
```bash
# ブランチ作成（任意）
git checkout -b fix/phase-a-v1.0.10

# 修正実施
# ... コード編集 ...

# ビルドテスト
flutter analyze
flutter build apk --release --dart-define=IS_PRO_VERSION=false
```

### 2. バージョン更新
```yaml
# pubspec.yaml
version: 1.0.10+18
```

### 3. リリースビルド
```bash
# Lite版
flutter build apk --release --dart-define=IS_PRO_VERSION=false
cp build/app/outputs/flutter-apk/app-release.apk ponshu-room-lite-v1.0.10-release.apk

# Pro版
flutter build apk --release --dart-define=IS_PRO_VERSION=true
cp build/app/outputs/flutter-apk/app-release.apk ponshu-room-pro-v1.0.10-release.apk
```

### 4. リリースノート作成
`RELEASE_NOTES_v1.0.10.md` を作成（テンプレートは v1.0.9 参照）

---

## 📝 注意事項

### AppColors 拡張が必要な場合
もし `appColors.warning` や `appColors.success` が存在しない場合：

```dart
// lib/theme/app_colors.dart に追加
extension AppColors on ThemeData {
  // ... 既存のプロパティ ...

  Color get warning => brightness == Brightness.dark
      ? Colors.orange.shade700
      : Colors.orange.shade600;

  Color get success => brightness == Brightness.dark
      ? Colors.green.shade700
      : Colors.green.shade600;

  Color get error => colorScheme.error;
}
```

---

## 🔮 将来機能バックログ（Phase D以降）

### D1. おすすめ日本酒機能（さけのわAPI連携）
**概要**: MBTI診断結果に基づいて、さけのわDBのAPIと連携してパーソナライズされたおすすめを表示

**実装イメージ**:
1. ユーザーのMBTI診断タイプを取得
2. さけのわAPI で該当タイプに合う特性（甘口/辛口、香り高い、etc.）で検索
3. 検索結果から上位10件を表示
4. ユーザーがタップで詳細閲覧・お気に入り登録可能

**技術的要件**:
- さけのわAPI仕様確認（認証、レート制限）
- APIキーの安全な管理
- キャッシュ戦略（毎回APIを叩かない）
- オフライン時のフォールバック

**優先度**: 🔶 中
**推定実装時間**: 4-6時間

---

### D2. 酒蔵マップの制覇率代替指標
**背景**: 47都道府県の制覇率は心理的ハードルが高く（沖縄、九州の日本酒はレア）、達成感を感じにくい

**代替案**:
1. **飲んだ酒蔵数**: 日本には1000以上の酒蔵 → コレクション性
2. **出会った銘柄数**: シンプルな種類カウント（バッジとも連動）
3. **味わいマップ埋め率**: 甘口/辛口、濃醇/淡麗の4象限分布チャート
4. **好みの発見率**: ★4以上の銘柄比率

**推奨**: **飲んだ酒蔵数** + **味わいマップ埋め率** の2つを追加

**技術的要件**:
- 酒蔵名の正規化（表記ゆれ対策）
- 味わいマップの座標計算ロジック
- UI設計（マップ画面に統合）

**優先度**: 🔶 中
**推定実装時間**: 3-4時間

---

### D3. ギャラリー画像の圧縮・永続化
**背景**: Antigravityコードレビューで発見された画像最適化の不備

**現状の問題**:
1. **カメラ撮影**: ✅ 圧縮済み（1024px / 85% JPEG）
2. **ギャラリー選択**: ❌ 未対応
   - `XFile.path`（image_pickerの一時キャッシュ）をそのままDBに保存
   - 圧縮処理なし
   - 原寸大の画像をGemini APIに送信

**3つのリスク**:
| リスク | 深刻度 | 内容 |
|--------|--------|------|
| **永続化リスク** | 🔴 高 | OSのキャッシュクリーンアップで画像リンク切れの可能性 |
| **API/通信コスト** | 🟡 中 | 数MBの高画質画像を送信 → 解析時間増・モバイル通信量増大 |
| **アプリ容量** | 🟡 中 | 表示に不要な高解像度で保存 → 端末容量圧迫 |

**修正方針**:
```dart
// lib/screens/camera_screen.dart:264-280
Future<void> _pickFromGallery() async {
  final picker = ImagePicker();
  final List<XFile> images = await picker.pickMultiImage();

  if (images.isNotEmpty && mounted) {
    // 🔧 修正: ギャラリー画像も圧縮 + Documents領域にコピー
    final directory = await getApplicationDocumentsDirectory();

    for (var img in images) {
      // 1. 一時パスから圧縮
      final finalPath = join(directory.path, '${const Uuid().v4()}.jpg');
      final compressedPath = await ImageCompressionService.compressForGemini(
        img.path,
        targetPath: finalPath
      );

      // 2. 圧縮済み永続パスを追加
      _capturedImages.add(compressedPath);
    }

    // 既存の通知処理...
  }
}
```

**期待効果**:
- ✅ 画像リンク切れ防止（永続的なDocuments領域に保存）
- ✅ API送信サイズ削減（カメラ撮影と同じ1024px / 85% JPEG）
- ✅ 端末容量節約

**技術的要件**:
- ImageCompressionService の再利用（既存コード）
- 一時ファイルの削除処理（オプション）
- エラーハンドリング（圧縮失敗時のフォールバック）

**検証項目**:
- [ ] ギャラリーから大容量画像（5MB以上）を選択 → 圧縮されてDB保存
- [ ] アプリ再起動後も画像が正常に表示される
- [ ] 圧縮後のファイルサイズがカメラ撮影時と同等（数百KB程度）

**優先度**: 🔶 中（現在は機能的に動作しているが、データ安定性に問題）
**推定実装時間**: 30分

**発見者**: Antigravity（コードレビュアー）
**報告日**: 2026年1月31日

---

**計画作成日**: 2026年1月31日
**最終更新日**: 2026年1月31日（Phase C完了後、D3追加）
**対象バージョン**: v1.0.10
**推定実装時間**: Phase A-C約2時間 / Phase D以降7.5-10.5時間
**推定テスト時間**: 約30分
