23 KiB
23 KiB
新生ぽんるーむ - 最終完全要件定義書
プロジェクト名: 新生ぽんるーむ (Reborn Ponshu Room) バージョン: 2.0 - "My Digital Sake Cellar" 作成日: 2025-12-29 設計: Claude (Anthropic) + Gemini (Google AI) + posimai 実装担当: Antigravity + Claude
🎯 プロジェクトビジョン
「目の前の一本を撮るだけで、魔法のようにデータが溜まっていく」
3つの核心コンセプト
🍶 1. 「瞬撮」- 魔法の解析体験
カメラを向けて撮るだけで、AIが全てを読み取る。 ユーザーはただ「撮る」だけ。
🎨 2. 「美録」- インスタ映えする情報の見せ方
雑誌のようなレイアウトで、いつでも見返したくなる。 そのままインスタに投稿できる美しさ。
🧩 3. 「遊び心」- 意味のあるデータ分析
自分の好みを「発見」する楽しさ。 日本全国制覇マップ、フレーバーマトリックス。
📱 技術スタック
必須要件
Flutter SDK: 3.38.3+
Dart: 3.10.1+
Android:
compileSdk: 36
targetSdk: 35 (Android 15対応)
minSdk: 21 (Android 5.0+)
依存パッケージ
dependencies:
flutter:
sdk: flutter
# 状態管理(Riverpod - スキャンアプリで実績あり)
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解析(Gemini 3.0)
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
🎨 UI/UXデザイン原則
デザインコンセプト
「雑誌のような洗練、魔法のような心地よさ」
カラーパレット
// posimaiブランドカラー
static const Color posimaiBlue = Color(0xFF376495);
// ベースカラー
static const Color warmOffWhite = Color(0xFFFAFAF9); // 背景
static const Color richBlack = Color(0xFF1A1A1A); // テキスト
static const Color charcoalGray = Color(0xFF4A4A4A); // サブテキスト
static const Color warmGray = Color(0xFF8A8A8A); // 補助テキスト
// アクセントカラー
static const Color dustyPink = Color(0xFFE8B4B8); // お気に入り
static const Color softYellow = Color(0xFFFEF3C7); // ウィッシュリスト
タイポグラフィ
// 銘柄名・タイトル: 明朝体(格調高く)
headlineMedium: GoogleFonts.notoSerifJp(
fontSize: 20,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
color: richBlack,
)
// データ・ボディ: ゴシック体(読みやすく)
bodyMedium: GoogleFonts.notoSansJp(
fontSize: 13,
fontWeight: FontWeight.w400,
color: charcoalGray,
)
// ラベル・キャプション: ゴシック体
bodySmall: GoogleFonts.notoSansJp(
fontSize: 11,
fontWeight: FontWeight.w500,
color: warmGray,
)
🍶 1. 「瞬撮」- 魔法の解析体験
APIキー設定
// lib/secrets.dart
class Secrets {
static const String geminiApiKey = 'AIzaSyA2BSr16R2k0bHjSYcSUdmLoY8PKwaFts0';
}
Gemini 3.0統合
重要: 以下のいずれかを使用
// オプション1: 最新の3.0プレビュー(推奨)
model: 'gemini-3.0-flash-latest'
// オプション2: 安定版2.5 Flash(スキャンアプリで実績)
model: 'gemini-2.5-flash-latest'
AIソムリエのリアルタイム実況
解析中のUI:
class AnalyzingOverlay extends StatelessWidget {
final String currentStage;
const AnalyzingOverlay({required this.currentStage});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.black.withOpacity(0.7),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(posimaiBlue),
),
SizedBox(height: 24),
Text(
currentStage,
style: GoogleFonts.notoSansJp(
fontSize: 16,
color: Colors.white,
),
),
],
),
),
);
}
}
ステージの例:
[
'ラベルを読んでいます...',
'お、これは〇〇県の銘柄ですね...',
'精米歩合を確認中...',
'データを整理しています...',
]
自動ポエム生成
Geminiのレスポンスに以下を追加:
final prompt = '''
この画像は日本酒のボトルまたはラベルの写真です。
ラベルに書かれているテキスト情報を正確に読み取り、JSON形式で返してください。
【重要な指示】
1. ラベルに明確に書かれている情報のみを抽出してください
2. 推測や想像で値を入れないでください(読めない場合はnullまたは省略)
3. 数値は必ず数字のみ(単位記号%などは除く)
4. このお酒の印象を一言で表す「キャッチコピー」を自動生成してください
【出力フォーマット】
{
"brandName": "銘柄名",
"type": "特定名称",
"alcoholContent": 数値,
"polishingRatio": 数値,
"breweryName": "酒蔵名",
"prefecture": "都道府県名",
"catchCopy": "夜風と楽しみたい、淡麗な一滴" // ← 自動生成
}
**JSON以外の余計な説明は一切不要です。JSONのみを出力してください。**
''';
🎨 2. 「美録」- インスタ映えする情報の見せ方
モダン・カタログ・カード
Web版の縦並びを捨て、左右2段構成を採用:
// lib/widgets/sake_card.dart
class SakeCard extends StatelessWidget {
final SakeItem item;
const SakeCard({required this.item});
@override
Widget build(BuildContext context) {
return Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: Color(0xFFE8E8E8), width: 1),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 左: 写真(角丸12px)
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
item.imagePath,
width: 100,
height: 100,
fit: BoxFit.cover,
),
),
SizedBox(width: 16),
// 右: テキスト情報
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 銘柄名(明朝体、大きく)
Text(
item.brandName ?? '日本酒',
style: GoogleFonts.notoSerifJp(
fontSize: 18,
fontWeight: FontWeight.w600,
color: richBlack,
),
),
SizedBox(height: 4),
// 酒蔵・産地(ゴシック体、控えめ)
Text(
'${item.breweryName ?? ''} | ${item.prefecture ?? ''}',
style: GoogleFonts.notoSansJp(
fontSize: 11,
color: warmGray,
),
),
SizedBox(height: 8),
// 評価
StarRating(rating: item.rating ?? 0),
SizedBox(height: 4),
// 種類
Text(
item.type ?? '',
style: GoogleFonts.notoSansJp(
fontSize: 11,
color: charcoalGray,
),
),
// キャッチコピー(NEW!)
if (item.catchCopy != null) ...[
SizedBox(height: 8),
Text(
item.catchCopy!,
style: GoogleFonts.notoSerifJp(
fontSize: 12,
fontStyle: FontStyle.italic,
color: posimaiBlue,
),
),
],
],
),
),
],
),
),
);
}
}
インスタ専用・共有カード生成
正方形(1:1)の画像を自動生成:
// lib/services/instagram_share_service.dart
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:image/image.dart' as img;
class InstagramShareService {
static Future<String> generateInstagramImage(SakeItem item) async {
// 1. 画像を読み込み
final imageBytes = await File(item.imagePath).readAsBytes();
final image = img.decodeImage(imageBytes)!;
// 2. 正方形(1080x1080)にクロップ
final size = 1080;
final square = img.copyResizeCropSquare(image, size: size);
// 3. 下半分にposimaiカラーのオーバーレイ
final overlay = img.Image(width: size, height: size ~/ 2);
img.fillRect(
overlay,
x1: 0,
y1: 0,
x2: size,
y2: size ~/ 2,
color: img.ColorRgb8(55, 100, 149), // posimaiBlue
);
// 4. オーバーレイを合成
img.compositeImage(
square,
overlay,
dstX: 0,
dstY: size ~/ 2,
);
// 5. テキストを描画(銘柄名、評価、ハッシュタグ)
// TODO: 白抜き明朝体でテキスト描画
// 6. 保存
final directory = await getTemporaryDirectory();
final path = '${directory.path}/instagram_${DateTime.now().millisecondsSinceEpoch}.jpg';
await File(path).writeAsBytes(img.encodeJpg(square));
return path;
}
static Future<void> shareToInstagram(SakeItem item) async {
// インスタ用画像を生成
final imagePath = await generateInstagramImage(item);
// 共有
await Share.shareXFiles(
[XFile(imagePath)],
text: '''
🍶 ${item.brandName ?? '日本酒'}
${item.catchCopy ?? ''}
#ぽんるーむ #日本酒 #${item.prefecture ?? ''} #日本酒好きと繋がりたい
''',
);
}
}
レイアウトイメージ:
┌────────────────────┐
│ │
│ [日本酒ラベル写真] │ ← 上半分(540px)
│ │
├────────────────────┤
│ posimaiブルー背景 │ ← 下半分(540px)
│ │
│ 獺祭(明朝体・白) │ ← 銘柄名
│ ⭐⭐⭐⭐⭐ │ ← 評価
│ │
│ 夜風と楽しみたい、 │ ← キャッチコピー
│ 淡麗な一滴 │
│ │
│ #ぽんるーむ │ ← ハッシュタグ
│ [logo] │ ← posimaiロゴ(右下)
└────────────────────┘
🧩 3. 「遊び心」- 意味のあるデータ分析
フレーバー・マトリックス
4象限チャートで味の傾向を可視化:
甘口
↑
│
濃醇 ←─┼─→ 淡麗
│
↓
辛口
実装:
// lib/widgets/flavor_matrix.dart
class FlavorMatrix extends StatelessWidget {
final List<SakeItem> items;
const FlavorMatrix({required this.items});
@override
Widget build(BuildContext context) {
// AIが解析したフレーバータグから傾向を計算
// 例: "甘口"タグが多い → 甘口寄り
// "フルーティー"タグが多い → 淡麗寄り
return Container(
height: 200,
child: CustomPaint(
painter: FlavorMatrixPainter(
userPosition: _calculateUserPosition(),
),
),
);
}
Offset _calculateUserPosition() {
// ユーザーの好みを計算
// 甘口/辛口、濃醇/淡麗の2軸で位置を決定
return Offset(0.3, 0.6); // 例: やや甘口、やや淡麗
}
}
日本酒・制覇マップ
都道府県マップをposimaiカラーで塗りつぶし:
// lib/screens/home/map_tab.dart
SimpleMap(
instructions: SMapJapan.instructions,
defaultColor: warmGray, // 未踏破
colors: SMapJapanColors(
// データから自動計算
..._prefectureColors(),
).toMap(),
callback: (id, name, tapDetails) {
// タップで詳細表示
_showPrefectureDetail(name);
},
)
Map<String, Color> _prefectureColors() {
final counts = _countByPrefecture();
return counts.map((prefecture, count) {
if (count == 0) return MapEntry(prefecture, warmGray);
if (count < 3) return MapEntry(prefecture, posimaiBlue.withOpacity(0.3));
return MapEntry(prefecture, posimaiBlue);
});
}
バッジ表示:
// マップの上部に表示
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [
Text(
'制覇: ${_completedPrefectures()} / 47',
style: GoogleFonts.notoSerifJp(
fontSize: 24,
fontWeight: FontWeight.bold,
color: posimaiBlue,
),
),
SizedBox(height: 8),
Text(
'あと${47 - _completedPrefectures()}県で全国制覇!',
style: GoogleFonts.notoSansJp(
fontSize: 13,
color: charcoalGray,
),
),
],
),
)
Synology バックアップ・ステータス
マイページの右上に控えめに表示:
// lib/screens/home/profile_tab.dart
Positioned(
top: 16,
right: 16,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.green[50],
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.green[200]!),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.cloud_done, size: 16, color: Colors.green[700]),
SizedBox(width: 4),
Text(
'Home Lab Sync: OK',
style: GoogleFonts.notoSansJp(
fontSize: 11,
color: Colors.green[700],
fontWeight: FontWeight.w500,
),
),
],
),
),
)
📦 データモデル(更新版)
SakeItem (Hive Model)
キャッチコピーを追加:
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;
// 🆕 AIが生成したキャッチコピー
@HiveField(17)
String? catchCopy;
// 🆕 フレーバープロファイル(甘口/辛口、濃醇/淡麗)
@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,
});
}
🚀 実装優先順位
Phase 1: MVP(5時間)
チェックリスト
- プロジェクト初期化(
flutter create ponshu_room_reborn) - Android設定(compileSdk: 36, targetSdk: 35)
- 依存関係追加
- Hiveセットアップ
- SakeItemモデル(キャッチコピー含む)
- posimaiテーマ
- ホーム画面骨組み(4タブ)
- Gemini 3.0解析 + リアルタイム実況
- カメラ撮影
- 入力フォーム
- 詳細画面
- SafeArea対応
Phase 2: 「美録」UI洗練(3時間)
チェックリスト
- モダン・カタログ・カード(左右2段構成)
- 明朝体×ゴシック体の適用
- インスタ専用画像生成機能
- Hero遷移
- アニメーション(200-300ms)
Phase 3: 「遊び心」機能拡張(4時間)
チェックリスト
- フレーバー・マトリックス
- 日本酒・制覇マップ + バッジ
- Synologyバックアップ・ステータス表示
- AIソムリエ(質問例、チャット形式)
- マイページ統計グラフ
- 検索・フィルタ・ソート
Phase 4: 共有機能(2時間)
チェックリスト
- シンプルテキスト共有
- Instagram用正方形画像生成
- キャッチコピー付き共有
合計所要時間: 14時間
📐 画面構成
ホーム画面(HomeScreen)
ボトムナビゲーション
┌─────┬─────┬─────┬─────┐
│ 🍶 │ 🗺️ │ 🤖 │ 👤 │
│ 酒 │ マップ│ AI │ MY │
└─────┴─────┴─────┴─────┘
タブ1: 酒リスト(ListTab)
モダン・カタログ・カード:
┌────────────────────────────┐
│ [100x100] │ 獺祭 │ ← 明朝体、大きく
│ 写真 │ 旭酒造 | 山口県 │ ← ゴシック体、控えめ
│ 角丸12px │ ⭐⭐⭐⭐⭐ 純米大吟醸 │
│ │ "夜風と楽しみたい、淡麗な一滴" │ ← キャッチコピー
└────────────────────────────┘
タブ2: マップ(MapTab)
日本酒・制覇マップ:
- 未踏破: warmGray
- 3本未満: posimaiBlue (30% opacity)
- 3本以上: posimaiBlue (100%)
- バッジ: 「制覇: 5 / 47」「あと42県で全国制覇!」
タブ3: AIソムリエ(AiTab)
質問例ボタン:
Wrap(
spacing: 8,
children: [
OutlinedButton(
child: Text('純米大吟醸とは?'),
onPressed: () => _askAI('純米大吟醸について詳しく教えて'),
),
OutlinedButton(
child: Text('山田錦について教えて'),
onPressed: () => _askAI('山田錦という酒米の特徴を教えて'),
),
OutlinedButton(
child: Text('刺身に合う日本酒は?'),
onPressed: () => _askAI('刺身に合う日本酒のおすすめを教えて'),
),
OutlinedButton(
child: Text('初心者におすすめの銘柄'),
onPressed: () => _askAI('日本酒初心者におすすめの銘柄を教えて'),
),
],
)
タブ4: マイページ(ProfileTab)
セクション構成:
-
Synologyバックアップステータス(右上)
🏠 Home Lab Sync: OK -
酒蔵サマリー
┌─────────┬─────────┬─────────┐ │ 1 │ 0 │ 0 │ │ 飲んだ本数│お気に入り│ 買いたい │ └─────────┴─────────┴─────────┘ -
フレーバー・マトリックス
甘口 ↑ │ ●(あなた) 濃醇 ←─┼─→ 淡麗 │ ↓ 辛口 あなたが選ぶ酒は、フルーティーな甘口に偏っています -
よく飲む都道府県
🥇 青森県 ━━━━━━━━━━ 1本 -
飲酒傾向グラフ
- 月別飲酒本数(直近6ヶ月)
- 評価分布(1-5星)
🔐 プライバシー・セキュリティ
データ保存場所
デフォルト(全ユーザー)
✅ ローカル(Hive DB)のみ
✅ 写真はアプリ専用ディレクトリ
❌ 外部サーバーへの送信なし
オプション(posimai専用)
✅ Synology NAS連携(WebDAV/FTP)
✅ 自動バックアップ
✅ "Home Lab Sync: OK" ステータス表示
📄 ファイル構成
lib/
├── main.dart
├── secrets.dart
├── models/
│ ├── sake_item.dart
│ └── sake_item.g.dart
├── providers/
│ ├── sake_repository_provider.dart
│ ├── gemini_provider.dart
│ └── camera_provider.dart
├── services/
│ ├── hive_service.dart
│ ├── gemini_service.dart
│ ├── share_service.dart
│ └── instagram_share_service.dart ← 🆕
├── screens/
│ ├── home/
│ │ ├── home_screen.dart
│ │ ├── list_tab.dart
│ │ ├── map_tab.dart
│ │ ├── ai_tab.dart
│ │ └── profile_tab.dart
│ ├── detail_screen.dart
│ └── input_screen.dart
├── widgets/
│ ├── sake_card.dart ← 🆕 左右2段構成
│ ├── star_rating.dart
│ ├── prefecture_dropdown.dart
│ ├── tag_chip.dart
│ ├── flavor_matrix.dart ← 🆕
│ └── analyzing_overlay.dart ← 🆕 リアルタイム実況
└── theme/
└── app_theme.dart
✅ 完成基準
MVP完成の定義
- カメラで日本酒を撮影できる
- Gemini 3.0でリアルタイム実況しながら解析
- キャッチコピーが自動生成される
- データをHiveに保存できる
- モダン・カタログ・カード(左右2段)で表示
- 詳細表示できる
- SafeAreaで見切れない
- Android 15 (Xiaomi 14T Pro) で動作する
最終完成の定義
- すべての機能が動作
- フレーバー・マトリックス表示
- 日本酒・制覇マップ + バッジ
- Instagram用正方形画像生成
- Synologyバックアップステータス表示
- 「雑誌のような」デザイン
- 60fpsの滑らかな動作
- 「魔法のような」心地よさ
最終更新: 2025-12-29 設計: Claude (Anthropic) + Gemini (Google AI) + posimai 実装担当: Antigravity + Claude
Let's create the magic! 🍶✨