ponshu-room-lite/docs/archive/ANTIGRAVITY_PROMPT.md

692 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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はどう実装すべきですか
```
---
**頑張ってください!「魔法のような体験」を一緒に作りましょう!🍶✨**