# 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 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?> 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( 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? additionalImages; @HiveField(8) double? rating; @HiveField(9) String? memo; @HiveField(10) List? 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はどう実装すべきですか? ``` --- **頑張ってください!「魔法のような体験」を一緒に作りましょう!🍶✨**