ponshu-room-lite/ANTIGRAVITY_PROMPT.md

692 lines
18 KiB
Markdown
Raw Permalink Normal View History

# 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: MVP5時間
- [ ] プロジェクト作成(`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はどう実装すべきですか
```
---
**頑張ってください!「魔法のような体験」を一緒に作りましょう!🍶✨**