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

23 KiB
Raw Blame History

新生ぽんるーむ - 最終完全要件定義書

プロジェクト名: 新生ぽんるーむ (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: MVP5時間

チェックリスト

  • プロジェクト初期化(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

セクション構成:

  1. Synologyバックアップステータス(右上)

    🏠 Home Lab Sync: OK
    
  2. 酒蔵サマリー

    ┌─────────┬─────────┬─────────┐
    │    1    │    0    │    0    │
    │ 飲んだ本数│お気に入り│ 買いたい │
    └─────────┴─────────┴─────────┘
    
  3. フレーバー・マトリックス

         甘口
          ↑
          │  ●(あなた)
    濃醇 ←─┼─→ 淡麗
          │
          ↓
         辛口
    
    あなたが選ぶ酒は、フルーティーな甘口に偏っています
    
  4. よく飲む都道府県

    🥇 青森県 ━━━━━━━━━━ 1本
    
  5. 飲酒傾向グラフ

    • 月別飲酒本数直近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! 🍶