# Antigravity向け - 新生ぽんるーむ実装プロンプト

**実装日**: 2025-12-29以降
**実装担当**: Antigravity
**サポート**: Claude (Anthropic)

---

## 🚨 最重要指示

### ⛔ 絶対にやってはいけないこと

```
❌ 既存のWeb版（ponshu-room/lib/）からコードを1行もコピーしない
❌ 既存プロジェクトを編集しない
❌ Web版のUIを踏襲しない
```

### ✅ やるべきこと

```
✅ 完全に新しいFlutterプロジェクトを作成
✅ FINAL_REQUIREMENTS.mdに100%従う
✅ スキャンアプリ（mai_quick_scan）の成功パターンを参考にする
✅ 写真を主役にする
```

---

## 📱 プロジェクト作成（コピペして実行）

### ステップ1: 新規プロジェクト作成

```bash
# 親ディレクトリへ移動
cd C:\Users\maita\posimai-project

# 完全に新しいFlutterプロジェクトを作成
flutter create ponshu_room_reborn

cd ponshu_room_reborn
```

### ステップ2: pubspec.yaml編集

**完全に置き換え**:

```yaml
name: ponshu_room_reborn
description: "Reborn Ponshu Room - My Digital Sake Cellar"
publish_to: 'none'
version: 2.0.0+1

environment:
  sdk: ^3.10.1

dependencies:
  flutter:
    sdk: flutter

  # 状態管理（スキャンアプリと同じ）
  flutter_riverpod: ^2.6.1
  hooks_riverpod: ^2.6.1
  flutter_hooks: ^0.20.5

  # ローカルDB
  hive: ^2.2.3
  hive_flutter: ^1.1.0

  # AI解析
  google_generative_ai: ^0.4.7
  http: ^1.6.0

  # カメラ・画像
  camera: ^0.11.0+2
  image_picker: ^1.2.1
  image: ^4.3.0

  # UI
  google_fonts: ^6.2.1
  fl_chart: ^1.1.1

  # 共有・保存
  share_plus: ^12.0.1
  path_provider: ^2.1.5

  # その他
  intl: ^0.20.2
  package_info_plus: ^9.0.0
  url_launcher: ^6.3.2
  countries_world_map: ^1.3.0
  cupertino_icons: ^1.0.8

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^6.0.0
  hive_generator: ^2.0.1
  build_runner: ^2.4.13

flutter:
  uses-material-design: true
```

```bash
flutter pub get
```

### ステップ3: Android設定

`android/app/build.gradle.kts` を編集:

```kotlin
android {
    namespace = "com.posimai.ponshu_room"
    compileSdk = 36  // Android 15+ 対応

    defaultConfig {
        applicationId = "com.posimai.ponshu_room"
        minSdk = 21
        targetSdk = 35
        versionCode = 1
        versionName = "2.0.0"
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_17.toString()
    }

    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("debug")
        }
    }
}
```

---

## 🎨 デザイン仕様（最重要）

### 写真を主役にする「ナロー・マージン」設計

#### ❌ 従来の余白設計（ダメな例）

```dart
ListView.separated(
  padding: EdgeInsets.all(16),  // ← 余白が大きすぎ
  separatorBuilder: (context, index) => SizedBox(height: 16),  // ← 隙間が大きすぎ
  itemBuilder: (context, index) => SakeCard(...),
)
```

**問題点**: 写真が小さくなる、迫力がない

#### ✅ 新生ぽんるーむの余白設計（正しい例）

```dart
ListView.separated(
  padding: EdgeInsets.symmetric(horizontal: 8, vertical: 12),  // ← 最小限
  separatorBuilder: (context, index) => SizedBox(height: 4),   // ← 細い隙間
  itemBuilder: (context, index) => SakeCard(...),
)
```

**メリット**: 写真が大きい、迫力がある、Instagram的

### カードデザイン - 「フル幅カード」

```dart
// lib/widgets/sake_card.dart
class SakeCard extends StatelessWidget {
  final SakeItem item;

  const SakeCard({required this.item});

  @override
  Widget build(BuildContext context) {
    final screenWidth = MediaQuery.of(context).size.width;

    return Card(
      margin: EdgeInsets.zero,  // ← カード自体のマージンはゼロ
      elevation: 0,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(0),  // ← 角丸なし（フル幅）
        side: BorderSide(
          color: Color(0xFFE8E8E8),
          width: 0.5,  // ← 繊細なボーダー
        ),
      ),
      child: Container(
        decoration: BoxDecoration(
          border: Border(
            bottom: BorderSide(
              color: Color(0xFFE8E8E8),
              width: 0.5,
            ),
          ),
        ),
        child: Padding(
          padding: EdgeInsets.all(12),  // ← カード内の余白は最小限
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 左: 写真（できるだけ大きく）
              ClipRRect(
                borderRadius: BorderRadius.circular(8),  // ← 写真だけ角丸
                child: Image.network(
                  item.imagePath,
                  width: 120,  // ← 大きく！
                  height: 120,
                  fit: BoxFit.cover,
                ),
              ),
              SizedBox(width: 12),
              // 右: テキスト情報
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    // 銘柄名（明朝体、大きく）
                    Text(
                      item.brandName ?? '日本酒',
                      style: GoogleFonts.notoSerifJp(
                        fontSize: 18,
                        fontWeight: FontWeight.w600,
                        color: Color(0xFF1A1A1A),
                      ),
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                    ),
                    SizedBox(height: 4),
                    // 酒蔵・産地
                    Text(
                      '${item.breweryName ?? ''} | ${item.prefecture ?? ''}',
                      style: GoogleFonts.notoSansJp(
                        fontSize: 11,
                        color: Color(0xFF8A8A8A),
                      ),
                    ),
                    SizedBox(height: 8),
                    // 評価
                    if (item.rating != null)
                      Row(
                        children: List.generate(
                          5,
                          (index) => Icon(
                            index < item.rating!.round()
                                ? Icons.star
                                : Icons.star_border,
                            size: 16,
                            color: Color(0xFFFFC107),
                          ),
                        ),
                      ),
                    SizedBox(height: 4),
                    // 種類
                    if (item.type != null)
                      Text(
                        item.type!,
                        style: GoogleFonts.notoSansJp(
                          fontSize: 11,
                          color: Color(0xFF4A4A4A),
                        ),
                      ),
                    // キャッチコピー
                    if (item.catchCopy != null) ...[
                      SizedBox(height: 6),
                      Text(
                        item.catchCopy!,
                        style: GoogleFonts.notoSerifJp(
                          fontSize: 12,
                          fontStyle: FontStyle.italic,
                          color: Color(0xFF376495),
                        ),
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ],
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
```

**レイアウトイメージ**:
```
┌────────────────────────────────────┐
│ [120x120] │ 獺祭               ← 明朝体18px
│   写真    │ 旭酒造 | 山口県    ← ゴシック体11px
│  角丸8px  │ ⭐⭐⭐⭐⭐ 純米大吟醸
│           │ "夜風と楽しみたい、淡麗な一滴"
└────────────────────────────────────┘
  ↑ 0.5pxのボーダーで区切り
```

---

## 🍶 Gemini 3.0統合（核心機能）

### APIキー設定

```dart
// lib/secrets.dart
class Secrets {
  static const String geminiApiKey = 'AIzaSyA2BSr16R2k0bHjSYcSUdmLoY8PKwaFts0';
}
```

### リアルタイム実況付きGemini解析

```dart
// lib/services/gemini_service.dart
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:google_generative_ai/google_generative_ai.dart';
import 'dart:convert';
import '../secrets.dart';

class GeminiService {
  late final GenerativeModel _model;

  GeminiService() {
    _model = GenerativeModel(
      model: 'gemini-2.5-flash-latest',  // スキャンアプリで実績あり
      apiKey: Secrets.geminiApiKey,
      generationConfig: GenerationConfig(
        temperature: 0.1,
      ),
    );
  }

  /// リアルタイム実況付きで解析
  Stream<String> analyzeSakeLabelWithCommentary(Uint8List imageBytes) async* {
    // ステージ1: 開始
    yield 'ラベルを読んでいます...';
    await Future.delayed(Duration(milliseconds: 500));

    // ステージ2: 解析中
    yield 'お酒の情報を確認しています...';

    try {
      final prompt = '''
この画像は日本酒のボトルまたはラベルの写真です。
ラベルに書かれているテキスト情報を正確に読み取り、JSON形式で返してください。

【重要な指示】
1. ラベルに明確に書かれている情報のみを抽出してください
2. 推測や想像で値を入れないでください（読めない場合はnullまたは省略）
3. 数値は必ず数字のみ（単位記号%などは除く）
4. このお酒の印象を一言で表す「キャッチコピー」を自動生成してください

【出力フォーマット】
{
  "brandName": "銘柄名",
  "type": "特定名称",
  "alcoholContent": 数値,
  "polishingRatio": 数値,
  "breweryName": "酒蔵名",
  "prefecture": "都道府県名",
  "catchCopy": "夜風と楽しみたい、淡麗な一滴"
}

**JSON以外の余計な説明は一切不要です。JSONのみを出力してください。**
''';

      final content = [
        Content.multi([
          TextPart(prompt),
          DataPart('image/jpeg', imageBytes),
        ])
      ];

      final response = await _model.generateContent(content);
      final text = response.text ?? '';

      // ステージ3: 都道府県を検出した場合
      if (text.contains('県')) {
        final prefectureMatch = RegExp(r'([^\s]+県)').firstMatch(text);
        if (prefectureMatch != null) {
          yield 'お、これは${prefectureMatch.group(1)}の銘柄ですね...';
          await Future.delayed(Duration(milliseconds: 300));
        }
      }

      // ステージ4: データ整理中
      yield 'データを整理しています...';
      await Future.delayed(Duration(milliseconds: 300));

      // JSON抽出
      final jsonMatch = RegExp(r'\{[\s\S]*\}').firstMatch(text);
      if (jsonMatch != null) {
        final jsonString = jsonMatch.group(0)!;
        final data = jsonDecode(jsonString);

        // ステージ5: 完了
        yield '解析完了！';

        // データを返す（特別な形式）
        yield 'DATA:$jsonString';
      } else {
        yield 'エラー: データを読み取れませんでした';
      }
    } catch (e) {
      yield 'エラー: $e';
    }
  }

  /// シンプルな解析（実況なし）
  Future<Map<String, dynamic>?> analyzeSakeLabel(Uint8List imageBytes) async {
    try {
      final prompt = '''
この画像は日本酒のボトルまたはラベルの写真です。
ラベルに書かれているテキスト情報を正確に読み取り、JSON形式で返してください。

【重要な指示】
1. ラベルに明確に書かれている情報のみを抽出してください
2. 推測や想像で値を入れないでください（読めない場合はnullまたは省略）
3. 数値は必ず数字のみ（単位記号%などは除く）
4. このお酒の印象を一言で表す「キャッチコピー」を自動生成してください

【出力フォーマット】
{
  "brandName": "銘柄名",
  "type": "特定名称",
  "alcoholContent": 数値,
  "polishingRatio": 数値,
  "breweryName": "酒蔵名",
  "prefecture": "都道府県名",
  "catchCopy": "夜風と楽しみたい、淡麗な一滴"
}

**JSON以外の余計な説明は一切不要です。JSONのみを出力してください。**
''';

      final content = [
        Content.multi([
          TextPart(prompt),
          DataPart('image/jpeg', imageBytes),
        ])
      ];

      final response = await _model.generateContent(content);
      final text = response.text ?? '';

      final jsonMatch = RegExp(r'\{[\s\S]*\}').firstMatch(text);
      if (jsonMatch != null) {
        final jsonString = jsonMatch.group(0)!;
        return jsonDecode(jsonString);
      }

      return null;
    } catch (e) {
      debugPrint('Gemini API Error: $e');
      return null;
    }
  }
}
```

### リアルタイム実況の使用例

```dart
// lib/screens/input_screen.dart（一部）
StreamBuilder<String>(
  stream: geminiService.analyzeSakeLabelWithCommentary(imageBytes),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      final message = snapshot.data!;

      // データが返ってきた場合
      if (message.startsWith('DATA:')) {
        final jsonString = message.substring(5);
        final data = jsonDecode(jsonString);
        // フォームに自動入力
        _fillForm(data);
        return ResultForm(data: data);
      }

      // 実況メッセージを表示
      return AnalyzingOverlay(currentStage: message);
    }

    return AnalyzingOverlay(currentStage: 'カメラを起動しています...');
  },
)
```

---

## 📦 データモデル

### SakeItem (Hive Model)

```dart
// lib/models/sake_item.dart
import 'package:hive/hive.dart';

part 'sake_item.g.dart';

@HiveType(typeId: 0)
class SakeItem extends HiveObject {
  @HiveField(0)
  String? brandName;

  @HiveField(1)
  String? breweryName;

  @HiveField(2)
  String? prefecture;

  @HiveField(3)
  String? type;

  @HiveField(4)
  double? alcoholContent;

  @HiveField(5)
  int? polishingRatio;

  @HiveField(6)
  String imagePath;

  @HiveField(7)
  List<String>? additionalImages;

  @HiveField(8)
  double? rating;

  @HiveField(9)
  String? memo;

  @HiveField(10)
  List<String>? tags;

  @HiveField(11)
  bool isFavorite;

  @HiveField(12)
  bool isWishlist;

  @HiveField(13)
  DateTime createdAt;

  @HiveField(14)
  DateTime? updatedAt;

  @HiveField(15)
  int? price;

  @HiveField(16)
  int? volume;

  @HiveField(17)
  String? catchCopy;  // AIが生成したキャッチコピー

  @HiveField(18)
  double? sweetnessScore;  // -1.0（辛口）～ 1.0（甘口）

  @HiveField(19)
  double? bodyScore;  // -1.0（淡麗）～ 1.0（濃醇）

  SakeItem({
    this.brandName,
    this.breweryName,
    this.prefecture,
    this.type,
    this.alcoholContent,
    this.polishingRatio,
    required this.imagePath,
    this.additionalImages,
    this.rating,
    this.memo,
    this.tags,
    this.isFavorite = false,
    this.isWishlist = false,
    required this.createdAt,
    this.updatedAt,
    this.price,
    this.volume,
    this.catchCopy,
    this.sweetnessScore,
    this.bodyScore,
  });
}
```

```bash
# 生成コマンド
flutter pub run build_runner build
```

---

## 🚀 実装チェックリスト

### Phase 1: MVP（5時間）

- [ ] プロジェクト作成（`flutter create ponshu_room_reborn`）
- [ ] `pubspec.yaml` 設定
- [ ] Android設定（compileSdk: 36, targetSdk: 35）
- [ ] `lib/secrets.dart` 作成（APIキー）
- [ ] `lib/models/sake_item.dart` 作成
- [ ] `flutter pub run build_runner build`
- [ ] `lib/theme/app_theme.dart` 作成（posimaiカラー）
- [ ] `lib/main.dart` 作成（Hive初期化）
- [ ] `lib/screens/home/home_screen.dart` 作成（4タブ）
- [ ] `lib/services/gemini_service.dart` 作成（リアルタイム実況）
- [ ] `lib/widgets/sake_card.dart` 作成（フル幅カード）
- [ ] `lib/widgets/analyzing_overlay.dart` 作成（実況UI）
- [ ] カメラ撮影機能
- [ ] 入力フォーム
- [ ] 詳細画面
- [ ] **SafeArea対応** ← Android 15必須

### Phase 2: 「美録」UI洗練（3時間）

- [ ] 余白調整（padding: 8dp, separator: 4dp）
- [ ] 明朝体×ゴシック体の適用
- [ ] Hero遷移
- [ ] Instagram用画像生成機能

### Phase 3: 「遊び心」機能拡張（4時間）

- [ ] フレーバー・マトリックス
- [ ] 日本酒・制覇マップ + バッジ
- [ ] AIソムリエ（質問例、チャット）
- [ ] マイページ統計グラフ

### Phase 4: 共有機能（2時間）

- [ ] シンプルテキスト共有
- [ ] Instagram用正方形画像生成
- [ ] キャッチコピー付き共有

---

## ✅ 完成基準

### MVP完成の定義
- [ ] カメラで日本酒を撮影できる
- [ ] Gemini 3.0でリアルタイム実況しながら解析
- [ ] キャッチコピーが自動生成される
- [ ] データをHiveに保存できる
- [ ] フル幅カード（写真120x120）で表示
- [ ] 詳細表示できる
- [ ] SafeAreaで見切れない
- [ ] Android 15 (Xiaomi 14T Pro) で動作する

### 最終完成の定義
- [ ] すべての機能が動作
- [ ] フレーバー・マトリックス表示
- [ ] 日本酒・制覇マップ + バッジ
- [ ] Instagram用正方形画像生成
- [ ] 「雑誌のような」デザイン
- [ ] 60fpsの滑らかな動作
- [ ] 「魔法のような」心地よさ

---

## 📞 進捗報告

各フェーズ完了後に以下を報告してください:

```
Phase 1-1 完了: プロジェクト初期化
Phase 1-2 完了: データモデル
...

問題点:
- XXXでエラーが発生しました
- YYYの実装方法が不明です

質問:
- ZZZはどう実装すべきですか？
```

---

**頑張ってください！「魔法のような体験」を一緒に作りましょう！🍶✨**
