v1.0.8 - Original (Ponshu Room Lite MVP Complete)
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(Get-ChildItem libwidgets -Filter *.dart -Recurse)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.build/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
.swiftpm/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
/coverage/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
|
|
||||||
|
# Security
|
||||||
|
lib/secrets.dart
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "19074d12f7eaf6a8180cd4036a430c1d76de904e"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
base_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
- platform: android
|
||||||
|
create_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
base_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
- platform: ios
|
||||||
|
create_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
base_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
- platform: linux
|
||||||
|
create_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
base_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
- platform: macos
|
||||||
|
create_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
base_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
- platform: web
|
||||||
|
create_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
base_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
- platform: windows
|
||||||
|
create_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
base_revision: 19074d12f7eaf6a8180cd4036a430c1d76de904e
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
|
|
@ -0,0 +1,691 @@
|
||||||
|
# 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: 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はどう実装すべきですか?
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**頑張ってください!「魔法のような体験」を一緒に作りましょう!🍶✨**
|
||||||
|
|
@ -0,0 +1,510 @@
|
||||||
|
# ぽんるーむ (Pon-Room) - 共通仕様書 v1.0
|
||||||
|
|
||||||
|
**作成日**: 2026-01-03
|
||||||
|
**対象**: AI開発エージェント(Cursor/Antigravity/Claude/Gemini)
|
||||||
|
**目的**: すべての開発者・AIエージェントが参照する唯一のバイブル
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 1. プロジェクト概要
|
||||||
|
|
||||||
|
### 1.1 アプリケーション情報
|
||||||
|
- **アプリ名**: ぽんるーむ (Pon-Room)
|
||||||
|
- **コンセプト**: 日本酒の「記録・解析・循環」を支えるAIプラットフォーム
|
||||||
|
- **バージョン**: 1.0.9+47
|
||||||
|
- **最終更新**: 2026-01-03
|
||||||
|
|
||||||
|
### 1.2 ターゲットユーザー
|
||||||
|
- **B2C (Consumer Mode)**: 一般ユーザー
|
||||||
|
- 日本酒体験の記録・診断・共有
|
||||||
|
- ゲーミフィケーション(ポイント・バッジ・レベル)
|
||||||
|
- 「酒向(しゅこう)カード」による自己表現
|
||||||
|
|
||||||
|
- **B2B (Business Mode)**: 飲食店
|
||||||
|
- 自店の日本酒メニュー作成(PDF出力)
|
||||||
|
- QRコード自動埋め込み
|
||||||
|
- インスタ投稿支援
|
||||||
|
|
||||||
|
### 1.3 アプリの循環ロジック
|
||||||
|
```
|
||||||
|
[飲食店] PDF + QRメニュー作成
|
||||||
|
↓
|
||||||
|
[客] スキャンして詳細表示・ポイント獲得
|
||||||
|
↓
|
||||||
|
[客] 自分の記録が貯まる → 酒向カード生成
|
||||||
|
↓
|
||||||
|
[飲食店] 客の好みを理解してレコメンド
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ 2. 技術スタック
|
||||||
|
|
||||||
|
### 2.1 フロントエンド
|
||||||
|
- **Framework**: Flutter 3.10.1
|
||||||
|
- **SDK**: Dart 3.10.1
|
||||||
|
- **対応プラットフォーム**: iOS, Android, Web
|
||||||
|
|
||||||
|
### 2.2 バックエンド・データベース
|
||||||
|
- **Database**: Cloud Firestore (Firebase)
|
||||||
|
- **Authentication**: Firebase Auth
|
||||||
|
- **Storage**: Firebase Storage (画像保存)
|
||||||
|
|
||||||
|
### 2.3 AI・機械学習
|
||||||
|
- **Local OCR**: Google ML Kit
|
||||||
|
- 用途: ラベルからのテキスト抽出
|
||||||
|
- メリット: 無料、高速、オフライン動作
|
||||||
|
|
||||||
|
- **LLM Analysis**: Gemini API
|
||||||
|
- モデル: `gemini-2.5-flash` / `gemini-3.0-flash`
|
||||||
|
- 用途: テキストの構造化、キャッチコピー生成
|
||||||
|
- 重要: 画像を直接送らず、OCRテキストのみ送信(トークン節約)
|
||||||
|
|
||||||
|
### 2.4 主要パッケージ
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
# 画像・カメラ
|
||||||
|
image_picker: ^1.2.1
|
||||||
|
gal: ^2.3.0 # カメラロール保存
|
||||||
|
|
||||||
|
# PDF生成
|
||||||
|
pdf: ^3.10.0
|
||||||
|
printing: ^5.11.0
|
||||||
|
|
||||||
|
# QRコード
|
||||||
|
qr_flutter: ^4.1.0
|
||||||
|
mobile_scanner: ^3.5.0 # QRスキャン
|
||||||
|
|
||||||
|
# データベース
|
||||||
|
hive: ^2.2.3
|
||||||
|
hive_flutter: ^1.1.0
|
||||||
|
|
||||||
|
# AI
|
||||||
|
google_generative_ai: ^0.4.7
|
||||||
|
google_ml_kit: ^0.16.0 # OCR用
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 3. 共通データ構造(JSON Schema)
|
||||||
|
|
||||||
|
### 3.1 基本方針
|
||||||
|
- **display_data**: カードUIで表示する最小限の情報(シンプル維持)
|
||||||
|
- **hidden_specs**: 詳細画面・PDF・分析で使用する情報
|
||||||
|
- **badges**: ゲーミフィケーション要素
|
||||||
|
- **gamification**: ポイント・レベル・診断結果
|
||||||
|
- **user_data**: ユーザーの主観的情報
|
||||||
|
- **metadata**: システム管理用
|
||||||
|
|
||||||
|
### 3.2 SakeItem完全JSON構造
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"display_data": {
|
||||||
|
"name": "獺祭 純米大吟醸 磨き二割三分",
|
||||||
|
"catch_phrase": "華やかな香りと洗練された味わい",
|
||||||
|
"image_path": "path/to/image.jpg",
|
||||||
|
"rating": 4.5
|
||||||
|
},
|
||||||
|
|
||||||
|
"hidden_specs": {
|
||||||
|
"brewery": "旭酒造株式会社",
|
||||||
|
"prefecture": "山口県",
|
||||||
|
"type": "純米大吟醸",
|
||||||
|
"alcohol_content": 16.0,
|
||||||
|
"polishing_ratio": 23,
|
||||||
|
"sake_meter_value": 4.0,
|
||||||
|
"rice_variety": "山田錦",
|
||||||
|
"yeast": "自社酵母",
|
||||||
|
"manufacturing_year_month": "2024.10",
|
||||||
|
"qr_code_url": "https://pon-room.app/sake/abc123"
|
||||||
|
},
|
||||||
|
|
||||||
|
"badges": {
|
||||||
|
"is_recommended": false,
|
||||||
|
"is_seasonal": false,
|
||||||
|
"season_tag": "春限定"
|
||||||
|
},
|
||||||
|
|
||||||
|
"gamification": {
|
||||||
|
"pon_points": 10,
|
||||||
|
"sake_mbti_type": "フルーティー・モダン型",
|
||||||
|
"rarity_level": "レア"
|
||||||
|
},
|
||||||
|
|
||||||
|
"user_data": {
|
||||||
|
"is_favorite": false,
|
||||||
|
"is_wishlist": false,
|
||||||
|
"tags": ["甘口", "フルーティー", "冷酒向き"],
|
||||||
|
"memo": "お祝い事にぴったり。冷やして飲むのがおすすめ。",
|
||||||
|
"drink_location": "○○レストラン",
|
||||||
|
"companion": "△△さん",
|
||||||
|
"purchase_location": "××酒販店",
|
||||||
|
"price": 5000
|
||||||
|
},
|
||||||
|
|
||||||
|
"metadata": {
|
||||||
|
"created_at": "2026-01-03T12:34:56Z",
|
||||||
|
"updated_at": "2026-01-03T12:34:56Z",
|
||||||
|
"app_type": "sake",
|
||||||
|
"app_mode": "consumer",
|
||||||
|
"version": "1.0",
|
||||||
|
"scanned_count": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 4. UI構成ルール
|
||||||
|
|
||||||
|
### 4.1 タブ構成(5タブ制)
|
||||||
|
|
||||||
|
| タブ | Consumer Mode (B2C) | Business Mode (B2B) |
|
||||||
|
|------|---------------------|---------------------|
|
||||||
|
| **タブ1** | 日本酒カードリスト(自分の記録) | 日本酒カードリスト(店舗在庫) |
|
||||||
|
| **タブ2** | QRスキャン・AR情報表示 | PDFメニュー作成・QR埋め込み |
|
||||||
|
| **タブ3** | AIソムリエ・酒向カード診断 | インスタ投稿支援(AI文章生成) |
|
||||||
|
| **タブ4** | 酒蔵マップ(聖地巡礼) | 店舗設定・在庫管理 |
|
||||||
|
| **タブ5** | マイページ(レベル・バッジ・設定) | アナリティクス(スキャン統計) |
|
||||||
|
|
||||||
|
### 4.2 カード表示ルール(重要)
|
||||||
|
|
||||||
|
**表示項目(display_dataのみ):**
|
||||||
|
- 銘柄名 (`display_data.name`)
|
||||||
|
- 画像 (`display_data.image_path`)
|
||||||
|
- 評価 (`display_data.rating`) - 星表示
|
||||||
|
- バッジ (`badges`) - 店長推奨・季節限定アイコン
|
||||||
|
|
||||||
|
**非表示項目(詳細画面でのみ使用):**
|
||||||
|
- `hidden_specs` の全項目
|
||||||
|
- `user_data` の詳細情報
|
||||||
|
|
||||||
|
**理由:** シンプルなUIを維持し、挫折を防ぐ
|
||||||
|
|
||||||
|
### 4.3 詳細画面の表示項目
|
||||||
|
|
||||||
|
**全て表示:**
|
||||||
|
- `display_data` 全項目
|
||||||
|
- `hidden_specs` 全項目(スペック表として)
|
||||||
|
- `badges` (アイコン付き)
|
||||||
|
- `user_data` 全項目
|
||||||
|
|
||||||
|
### 4.4 PDF出力の表示項目
|
||||||
|
|
||||||
|
**主要項目:**
|
||||||
|
- `display_data.name`, `image_path`
|
||||||
|
- `hidden_specs` の以下:
|
||||||
|
- type, alcohol_content, polishing_ratio
|
||||||
|
- sake_meter_value, brewery, prefecture
|
||||||
|
- `badges` (アイコン付き)
|
||||||
|
- QRコード(`hidden_specs.qr_code_url` から生成)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤖 5. AI解析フロー(Hybrid Analysis)
|
||||||
|
|
||||||
|
### 5.1 撮影から保存まで
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 撮影
|
||||||
|
├─ ImagePicker or Camera パッケージ
|
||||||
|
└─ カメラロール自動保存(gal パッケージ使用)★重要
|
||||||
|
|
||||||
|
2. OCR(テキスト抽出)
|
||||||
|
├─ Google ML Kit(ローカル・無料)
|
||||||
|
├─ 画像 → 全テキスト抽出
|
||||||
|
└─ 抽出テキストのみを次へ
|
||||||
|
|
||||||
|
3. AI解析(構造化)
|
||||||
|
├─ 抽出テキストを Gemini API へ送信
|
||||||
|
├─ プロンプト: 「JSONフォーマットで返して」
|
||||||
|
└─ 上記のJSON構造で回答を取得
|
||||||
|
|
||||||
|
4. 保存
|
||||||
|
├─ Hive(ローカル)に即座保存
|
||||||
|
└─ Firebase(クラウド)に同期(将来実装)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 AI解析プロンプト例
|
||||||
|
|
||||||
|
```
|
||||||
|
あなたは日本酒のラベル解析の専門家です。
|
||||||
|
以下のOCRテキストから、日本酒の情報を抽出してJSON形式で回答してください。
|
||||||
|
|
||||||
|
【OCRテキスト】
|
||||||
|
{ocrText}
|
||||||
|
|
||||||
|
【出力ルール】
|
||||||
|
1. display_data: 銘柄名と魅力的なキャッチコピー(あなたが生成)
|
||||||
|
2. hidden_specs: 詳細スペック(読み取れた範囲で)
|
||||||
|
3. 読み取れない項目はnullにする
|
||||||
|
4. JSONのみを返す(```jsonなどのマークダウン記法は不要)
|
||||||
|
5. キャッチコピーは20文字以内で簡潔に
|
||||||
|
|
||||||
|
【出力フォーマット】
|
||||||
|
{JSON構造をここに記載}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 コスト最適化のポイント
|
||||||
|
|
||||||
|
**❌ NG: 画像を直接Geminiに送信**
|
||||||
|
- 高トークン消費
|
||||||
|
- エラー発生率が高い
|
||||||
|
|
||||||
|
**✅ OK: OCRテキストのみ送信**
|
||||||
|
- トークン消費70-90%削減
|
||||||
|
- 安定した動作
|
||||||
|
- 後から再解析が不要
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 6. ゲーミフィケーション機能
|
||||||
|
|
||||||
|
### 6.1 ポンポイント(仮称)
|
||||||
|
|
||||||
|
**獲得条件:**
|
||||||
|
- 日本酒を記録: +5pt
|
||||||
|
- QRスキャン: +10pt
|
||||||
|
- 酒蔵訪問: +30pt(位置情報連動)
|
||||||
|
- 評価・レビュー投稿: +3pt
|
||||||
|
|
||||||
|
**レベルシステム:**
|
||||||
|
```
|
||||||
|
0-49pt: 利き酒初心者
|
||||||
|
50-149pt: 日本酒愛好家
|
||||||
|
150-299pt: 酒豪
|
||||||
|
300-499pt: 利き酒師
|
||||||
|
500pt+: 酒マスター
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 酒向(しゅこう)カード
|
||||||
|
|
||||||
|
**概要:** MBTIライクな自己診断カード
|
||||||
|
|
||||||
|
**診断軸(レーダーチャート):**
|
||||||
|
1. 甘口 ←→ 辛口
|
||||||
|
2. 濃醇 ←→ 淡麗
|
||||||
|
3. フルーティー ←→ 米の旨味
|
||||||
|
4. 冷酒 ←→ 熱燗
|
||||||
|
|
||||||
|
**表示項目:**
|
||||||
|
- 診断タイプ(例: フルーティー・モダン型)
|
||||||
|
- レーダーチャート
|
||||||
|
- 好きな銘柄TOP3
|
||||||
|
- AIの一言コメント
|
||||||
|
|
||||||
|
**用途:**
|
||||||
|
- 店員に見せて好みを伝える
|
||||||
|
- SNSシェア(画像として保存)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 7. QR循環ロジック
|
||||||
|
|
||||||
|
### 7.1 BtoB: PDFメニュー作成時
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 飲食店が店舗在庫を登録
|
||||||
|
2. PDFメニュー生成時、各銘柄にQRコード埋め込み
|
||||||
|
3. QRコード内容: https://pon-room.app/sake/{sakeId}
|
||||||
|
4. 印刷して店内に設置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 BtoC: QRスキャン時
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 客がアプリでQRスキャン
|
||||||
|
2. 銘柄詳細をAR風に表示(オーバーレイ)
|
||||||
|
3. 「記録する」ボタンで自分のリストに追加
|
||||||
|
4. ポンポイント+10pt獲得
|
||||||
|
5. 酒蔵リンクへの誘導
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 循環の価値
|
||||||
|
|
||||||
|
- **客**: スキャンする度にポイントが貯まる
|
||||||
|
- **店**: 客の興味データが蓄積(アナリティクス)
|
||||||
|
- **蔵元**: ECサイトへの誘導で売上向上
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎓 8. オンボーディング(使い方ガイド)
|
||||||
|
|
||||||
|
### 8.1 初回起動時
|
||||||
|
|
||||||
|
**4ステップガイド:**
|
||||||
|
1. ようこそ!日本酒の記録を始めよう
|
||||||
|
2. カメラで撮影するだけでAIが自動解析
|
||||||
|
3. お気に入りを記録して自分だけのコレクションを
|
||||||
|
4. 飲食店の方はBusinessモードへ切り替え可能
|
||||||
|
|
||||||
|
### 8.2 再表示機能
|
||||||
|
|
||||||
|
- 各画面右上の「?」アイコン
|
||||||
|
- タップでガイドを再表示
|
||||||
|
|
||||||
|
### 8.3 Businessモード専用ガイド
|
||||||
|
|
||||||
|
**3ステップ(モード切り替え時のみ表示):**
|
||||||
|
1. 店舗情報を設定しましょう
|
||||||
|
2. メニューを作成してPDF出力
|
||||||
|
3. QRコードで客とつながる
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 9. 開発優先順位(Phase別)
|
||||||
|
|
||||||
|
### Phase 0: 基盤整備 ✅
|
||||||
|
- [x] CommonSpecification.md 作成
|
||||||
|
- [ ] SakeItemモデルの拡張
|
||||||
|
|
||||||
|
### Phase 1: 安心の確保(1-2時間)
|
||||||
|
- [ ] カメラロール保存実装(gal)
|
||||||
|
- [ ] iOS/Android権限設定
|
||||||
|
- [ ] 撮影後の自動保存フロー
|
||||||
|
|
||||||
|
### Phase 2: BtoB機能完成(4-6時間)
|
||||||
|
- [ ] PDF + printing 実装
|
||||||
|
- [ ] モックアップ厳密再現
|
||||||
|
- [ ] QRコード埋め込み
|
||||||
|
- [ ] レイアウト定数化
|
||||||
|
|
||||||
|
### Phase 3: AI最適化(3-4時間)
|
||||||
|
- [ ] Google ML Kit 導入
|
||||||
|
- [ ] OCRテキスト抽出
|
||||||
|
- [ ] Gemini APIへのテキスト送信切り替え
|
||||||
|
|
||||||
|
### Phase 4: ゲーミフィケーション(後回しOK)
|
||||||
|
- [ ] ポンポイントシステム
|
||||||
|
- [ ] 酒向カード生成
|
||||||
|
- [ ] QRスキャン機能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 10. レイアウト定数(PDF用)
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class PDFLayoutConstants {
|
||||||
|
// 余白
|
||||||
|
static const double pageMargin = 24.0;
|
||||||
|
static const double sectionSpacing = 20.0;
|
||||||
|
static const double itemSpacing = 12.0;
|
||||||
|
|
||||||
|
// フォントサイズ
|
||||||
|
static const double titleFontSize = 24.0;
|
||||||
|
static const double headingFontSize = 14.0;
|
||||||
|
static const double bodyFontSize = 11.0;
|
||||||
|
static const double labelFontSize = 9.0;
|
||||||
|
|
||||||
|
// 線の太さ
|
||||||
|
static const double borderWidthThin = 0.5;
|
||||||
|
static const double borderWidthMedium = 1.0;
|
||||||
|
static const double borderWidthThick = 2.0;
|
||||||
|
|
||||||
|
// 色(posimaiカラー)
|
||||||
|
static final PdfColor borderColor = PdfColor.fromHex('#E2E8F0');
|
||||||
|
static final PdfColor labelColor = PdfColor.fromHex('#64748B');
|
||||||
|
static final PdfColor accentColor = PdfColor.fromHex('#376495');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 11. セキュリティ・プライバシー
|
||||||
|
|
||||||
|
### 11.1 データ保存場所
|
||||||
|
|
||||||
|
**✅ ローカル保存:**
|
||||||
|
- Hive DB: アプリ専用ディレクトリ
|
||||||
|
- 写真: カメラロール(ユーザー端末)
|
||||||
|
|
||||||
|
**✅ 外部送信(一時的、保存されない):**
|
||||||
|
- Gemini API: OCRテキストのみ(画像は送らない)
|
||||||
|
|
||||||
|
**❌ 外部送信なし:**
|
||||||
|
- 個人情報の無断クラウド保存
|
||||||
|
- サードパーティへのデータ販売
|
||||||
|
|
||||||
|
### 11.2 将来のFirebase連携
|
||||||
|
|
||||||
|
- ユーザーが明示的に「同期」を選択した場合のみ
|
||||||
|
- 画像はFirebase Storageへ
|
||||||
|
- テキストデータはFirestoreへ
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 12. 量産対応(ワイン・ビールアプリへの展開)
|
||||||
|
|
||||||
|
### 12.1 拡張方法
|
||||||
|
|
||||||
|
`metadata.app_type` を変更するだけで転用可能:
|
||||||
|
- `sake` → 日本酒
|
||||||
|
- `wine` → ワイン
|
||||||
|
- `beer` → クラフトビール
|
||||||
|
- `whisky` → ウイスキー
|
||||||
|
|
||||||
|
### 12.2 共通化できる機能
|
||||||
|
|
||||||
|
- 撮影・OCR・AI解析フロー
|
||||||
|
- ポイントシステム
|
||||||
|
- PDF出力・QR循環
|
||||||
|
- マイページ・設定
|
||||||
|
|
||||||
|
### 12.3 差分化が必要な箇所
|
||||||
|
|
||||||
|
- JSONの `hidden_specs` フィールド
|
||||||
|
- ワイン: ぶどう品種、産地、ヴィンテージ
|
||||||
|
- ビール: ホップ、モルト、IBU値
|
||||||
|
- キャッチコピーのトーン
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 13. AIエージェントへの指示
|
||||||
|
|
||||||
|
### 13.1 開発時の鉄則
|
||||||
|
|
||||||
|
1. **この仕様書を最優先のルール(バイブル)として参照せよ**
|
||||||
|
2. **display_data と hidden_specs を明確に分離せよ**
|
||||||
|
3. **カードUIはシンプルに保ち、display_dataのみ使用せよ**
|
||||||
|
4. **挫折防止のため、段階的に実装せよ(Phase順守)**
|
||||||
|
5. **モックアップがある場合は1px単位で厳密再現せよ**
|
||||||
|
|
||||||
|
### 13.2 コード生成時の注意
|
||||||
|
|
||||||
|
- null安全性を徹底
|
||||||
|
- エラーハンドリングを統一
|
||||||
|
- デバッグログを適切に配置
|
||||||
|
- コメントは日本語で簡潔に
|
||||||
|
|
||||||
|
### 13.3 質問・提案時のルール
|
||||||
|
|
||||||
|
- 仕様書と矛盾する提案をする場合は理由を明記
|
||||||
|
- 新機能追加時はPhaseを提案
|
||||||
|
- データ構造変更時はJSON例を提示
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 14. サポート・連絡先
|
||||||
|
|
||||||
|
**プロジェクトオーナー**: posimai
|
||||||
|
**開発支援AI**: Claude (Anthropic), Gemini (Google AI), ChatGPT (OpenAI)
|
||||||
|
**バージョン管理**: Git (GitHub)
|
||||||
|
**CI/CD**: Vercel (Web), Firebase Hosting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 15. 最後に
|
||||||
|
|
||||||
|
この仕様書は「挫折しない日本酒アプリ開発」のために、複数のAIの知恵を結集して作成されました。
|
||||||
|
|
||||||
|
**重要な心構え:**
|
||||||
|
- シンプルから始める
|
||||||
|
- データは豊富に持つが、表示は最小限に
|
||||||
|
- ユーザーが「楽しい」と感じる体験を最優先に
|
||||||
|
|
||||||
|
**Let's build the best sake app together! 🍶✨**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**End of CommonSpecification.md v1.0**
|
||||||
|
|
@ -0,0 +1,510 @@
|
||||||
|
# ぽんるーむ (Pon-Room) - 共通仕様書 v1.0
|
||||||
|
|
||||||
|
**作成日**: 2026-01-03
|
||||||
|
**対象**: AI開発エージェント(Cursor/Antigravity/Claude/Gemini)
|
||||||
|
**目的**: すべての開発者・AIエージェントが参照する唯一のバイブル
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 1. プロジェクト概要
|
||||||
|
|
||||||
|
### 1.1 アプリケーション情報
|
||||||
|
- **アプリ名**: ぽんるーむ (Pon-Room)
|
||||||
|
- **コンセプト**: 日本酒の「記録・解析・循環」を支えるAIプラットフォーム
|
||||||
|
- **バージョン**: 1.0.9+47
|
||||||
|
- **最終更新**: 2026-01-03
|
||||||
|
|
||||||
|
### 1.2 ターゲットユーザー
|
||||||
|
- **B2C (Consumer Mode)**: 一般ユーザー
|
||||||
|
- 日本酒体験の記録・診断・共有
|
||||||
|
- ゲーミフィケーション(ポイント・バッジ・レベル)
|
||||||
|
- 「酒向(しゅこう)カード」による自己表現
|
||||||
|
|
||||||
|
- **B2B (Business Mode)**: 飲食店
|
||||||
|
- 自店の日本酒メニュー作成(PDF出力)
|
||||||
|
- QRコード自動埋め込み
|
||||||
|
- インスタ投稿支援
|
||||||
|
|
||||||
|
### 1.3 アプリの循環ロジック
|
||||||
|
```
|
||||||
|
[飲食店] PDF + QRメニュー作成
|
||||||
|
↓
|
||||||
|
[客] スキャンして詳細表示・ポイント獲得
|
||||||
|
↓
|
||||||
|
[客] 自分の記録が貯まる → 酒向カード生成
|
||||||
|
↓
|
||||||
|
[飲食店] 客の好みを理解してレコメンド
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ 2. 技術スタック
|
||||||
|
|
||||||
|
### 2.1 フロントエンド
|
||||||
|
- **Framework**: Flutter 3.10.1
|
||||||
|
- **SDK**: Dart 3.10.1
|
||||||
|
- **対応プラットフォーム**: iOS, Android, Web
|
||||||
|
|
||||||
|
### 2.2 バックエンド・データベース
|
||||||
|
- **Database**: Cloud Firestore (Firebase)
|
||||||
|
- **Authentication**: Firebase Auth
|
||||||
|
- **Storage**: Firebase Storage (画像保存)
|
||||||
|
|
||||||
|
### 2.3 AI・機械学習
|
||||||
|
- **Local OCR**: Google ML Kit
|
||||||
|
- 用途: ラベルからのテキスト抽出
|
||||||
|
- メリット: 無料、高速、オフライン動作
|
||||||
|
|
||||||
|
- **LLM Analysis**: Gemini API
|
||||||
|
- モデル: `gemini-2.5-flash` / `gemini-3.0-flash`
|
||||||
|
- 用途: テキストの構造化、キャッチコピー生成
|
||||||
|
- 重要: 画像を直接送らず、OCRテキストのみ送信(トークン節約)
|
||||||
|
|
||||||
|
### 2.4 主要パッケージ
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
# 画像・カメラ
|
||||||
|
image_picker: ^1.2.1
|
||||||
|
gal: ^2.3.0 # カメラロール保存
|
||||||
|
|
||||||
|
# PDF生成
|
||||||
|
pdf: ^3.10.0
|
||||||
|
printing: ^5.11.0
|
||||||
|
|
||||||
|
# QRコード
|
||||||
|
qr_flutter: ^4.1.0
|
||||||
|
mobile_scanner: ^3.5.0 # QRスキャン
|
||||||
|
|
||||||
|
# データベース
|
||||||
|
hive: ^2.2.3
|
||||||
|
hive_flutter: ^1.1.0
|
||||||
|
|
||||||
|
# AI
|
||||||
|
google_generative_ai: ^0.4.7
|
||||||
|
google_ml_kit: ^0.16.0 # OCR用
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 3. 共通データ構造(JSON Schema)
|
||||||
|
|
||||||
|
### 3.1 基本方針
|
||||||
|
- **display_data**: カードUIで表示する最小限の情報(シンプル維持)
|
||||||
|
- **hidden_specs**: 詳細画面・PDF・分析で使用する情報
|
||||||
|
- **badges**: ゲーミフィケーション要素
|
||||||
|
- **gamification**: ポイント・レベル・診断結果
|
||||||
|
- **user_data**: ユーザーの主観的情報
|
||||||
|
- **metadata**: システム管理用
|
||||||
|
|
||||||
|
### 3.2 SakeItem完全JSON構造
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"display_data": {
|
||||||
|
"name": "獺祭 純米大吟醸 磨き二割三分",
|
||||||
|
"catch_phrase": "華やかな香りと洗練された味わい",
|
||||||
|
"image_path": "path/to/image.jpg",
|
||||||
|
"rating": 4.5
|
||||||
|
},
|
||||||
|
|
||||||
|
"hidden_specs": {
|
||||||
|
"brewery": "旭酒造株式会社",
|
||||||
|
"prefecture": "山口県",
|
||||||
|
"type": "純米大吟醸",
|
||||||
|
"alcohol_content": 16.0,
|
||||||
|
"polishing_ratio": 23,
|
||||||
|
"sake_meter_value": 4.0,
|
||||||
|
"rice_variety": "山田錦",
|
||||||
|
"yeast": "自社酵母",
|
||||||
|
"manufacturing_year_month": "2024.10",
|
||||||
|
"qr_code_url": "https://pon-room.app/sake/abc123"
|
||||||
|
},
|
||||||
|
|
||||||
|
"badges": {
|
||||||
|
"is_recommended": false,
|
||||||
|
"is_seasonal": false,
|
||||||
|
"season_tag": "春限定"
|
||||||
|
},
|
||||||
|
|
||||||
|
"gamification": {
|
||||||
|
"pon_points": 10,
|
||||||
|
"sake_mbti_type": "フルーティー・モダン型",
|
||||||
|
"rarity_level": "レア"
|
||||||
|
},
|
||||||
|
|
||||||
|
"user_data": {
|
||||||
|
"is_favorite": false,
|
||||||
|
"is_wishlist": false,
|
||||||
|
"tags": ["甘口", "フルーティー", "冷酒向き"],
|
||||||
|
"memo": "お祝い事にぴったり。冷やして飲むのがおすすめ。",
|
||||||
|
"drink_location": "○○レストラン",
|
||||||
|
"companion": "△△さん",
|
||||||
|
"purchase_location": "××酒販店",
|
||||||
|
"price": 5000
|
||||||
|
},
|
||||||
|
|
||||||
|
"metadata": {
|
||||||
|
"created_at": "2026-01-03T12:34:56Z",
|
||||||
|
"updated_at": "2026-01-03T12:34:56Z",
|
||||||
|
"app_type": "sake",
|
||||||
|
"app_mode": "consumer",
|
||||||
|
"version": "1.0",
|
||||||
|
"scanned_count": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 4. UI構成ルール
|
||||||
|
|
||||||
|
### 4.1 タブ構成(5タブ制)
|
||||||
|
|
||||||
|
| タブ | Consumer Mode (B2C) | Business Mode (B2B) |
|
||||||
|
|------|---------------------|---------------------|
|
||||||
|
| **タブ1** | 日本酒カードリスト(自分の記録) | 日本酒カードリスト(店舗在庫) |
|
||||||
|
| **タブ2** | QRスキャン・AR情報表示 | PDFメニュー作成・QR埋め込み |
|
||||||
|
| **タブ3** | AIソムリエ・酒向カード診断 | インスタ投稿支援(AI文章生成) |
|
||||||
|
| **タブ4** | 酒蔵マップ(聖地巡礼) | 店舗設定・在庫管理 |
|
||||||
|
| **タブ5** | マイページ(レベル・バッジ・設定) | アナリティクス(スキャン統計) |
|
||||||
|
|
||||||
|
### 4.2 カード表示ルール(重要)
|
||||||
|
|
||||||
|
**表示項目(display_dataのみ):**
|
||||||
|
- 銘柄名 (`display_data.name`)
|
||||||
|
- 画像 (`display_data.image_path`)
|
||||||
|
- 評価 (`display_data.rating`) - 星表示
|
||||||
|
- バッジ (`badges`) - 店長推奨・季節限定アイコン
|
||||||
|
|
||||||
|
**非表示項目(詳細画面でのみ使用):**
|
||||||
|
- `hidden_specs` の全項目
|
||||||
|
- `user_data` の詳細情報
|
||||||
|
|
||||||
|
**理由:** シンプルなUIを維持し、挫折を防ぐ
|
||||||
|
|
||||||
|
### 4.3 詳細画面の表示項目
|
||||||
|
|
||||||
|
**全て表示:**
|
||||||
|
- `display_data` 全項目
|
||||||
|
- `hidden_specs` 全項目(スペック表として)
|
||||||
|
- `badges` (アイコン付き)
|
||||||
|
- `user_data` 全項目
|
||||||
|
|
||||||
|
### 4.4 PDF出力の表示項目
|
||||||
|
|
||||||
|
**主要項目:**
|
||||||
|
- `display_data.name`, `image_path`
|
||||||
|
- `hidden_specs` の以下:
|
||||||
|
- type, alcohol_content, polishing_ratio
|
||||||
|
- sake_meter_value, brewery, prefecture
|
||||||
|
- `badges` (アイコン付き)
|
||||||
|
- QRコード(`hidden_specs.qr_code_url` から生成)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤖 5. AI解析フロー(Hybrid Analysis)
|
||||||
|
|
||||||
|
### 5.1 撮影から保存まで
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 撮影
|
||||||
|
├─ ImagePicker or Camera パッケージ
|
||||||
|
└─ カメラロール自動保存(gal パッケージ使用)★重要
|
||||||
|
|
||||||
|
2. OCR(テキスト抽出)
|
||||||
|
├─ Google ML Kit(ローカル・無料)
|
||||||
|
├─ 画像 → 全テキスト抽出
|
||||||
|
└─ 抽出テキストのみを次へ
|
||||||
|
|
||||||
|
3. AI解析(構造化)
|
||||||
|
├─ 抽出テキストを Gemini API へ送信
|
||||||
|
├─ プロンプト: 「JSONフォーマットで返して」
|
||||||
|
└─ 上記のJSON構造で回答を取得
|
||||||
|
|
||||||
|
4. 保存
|
||||||
|
├─ Hive(ローカル)に即座保存
|
||||||
|
└─ Firebase(クラウド)に同期(将来実装)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 AI解析プロンプト例
|
||||||
|
|
||||||
|
```
|
||||||
|
あなたは日本酒のラベル解析の専門家です。
|
||||||
|
以下のOCRテキストから、日本酒の情報を抽出してJSON形式で回答してください。
|
||||||
|
|
||||||
|
【OCRテキスト】
|
||||||
|
{ocrText}
|
||||||
|
|
||||||
|
【出力ルール】
|
||||||
|
1. display_data: 銘柄名と魅力的なキャッチコピー(あなたが生成)
|
||||||
|
2. hidden_specs: 詳細スペック(読み取れた範囲で)
|
||||||
|
3. 読み取れない項目はnullにする
|
||||||
|
4. JSONのみを返す(```jsonなどのマークダウン記法は不要)
|
||||||
|
5. キャッチコピーは20文字以内で簡潔に
|
||||||
|
|
||||||
|
【出力フォーマット】
|
||||||
|
{JSON構造をここに記載}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 コスト最適化のポイント
|
||||||
|
|
||||||
|
**❌ NG: 画像を直接Geminiに送信**
|
||||||
|
- 高トークン消費
|
||||||
|
- エラー発生率が高い
|
||||||
|
|
||||||
|
**✅ OK: OCRテキストのみ送信**
|
||||||
|
- トークン消費70-90%削減
|
||||||
|
- 安定した動作
|
||||||
|
- 後から再解析が不要
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 6. ゲーミフィケーション機能
|
||||||
|
|
||||||
|
### 6.1 ポンポイント(仮称)
|
||||||
|
|
||||||
|
**獲得条件:**
|
||||||
|
- 日本酒を記録: +5pt
|
||||||
|
- QRスキャン: +10pt
|
||||||
|
- 酒蔵訪問: +30pt(位置情報連動)
|
||||||
|
- 評価・レビュー投稿: +3pt
|
||||||
|
|
||||||
|
**レベルシステム:**
|
||||||
|
```
|
||||||
|
0-49pt: 利き酒初心者
|
||||||
|
50-149pt: 日本酒愛好家
|
||||||
|
150-299pt: 酒豪
|
||||||
|
300-499pt: 利き酒師
|
||||||
|
500pt+: 酒マスター
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 酒向(しゅこう)カード
|
||||||
|
|
||||||
|
**概要:** MBTIライクな自己診断カード
|
||||||
|
|
||||||
|
**診断軸(レーダーチャート):**
|
||||||
|
1. 甘口 ←→ 辛口
|
||||||
|
2. 濃醇 ←→ 淡麗
|
||||||
|
3. フルーティー ←→ 米の旨味
|
||||||
|
4. 冷酒 ←→ 熱燗
|
||||||
|
|
||||||
|
**表示項目:**
|
||||||
|
- 診断タイプ(例: フルーティー・モダン型)
|
||||||
|
- レーダーチャート
|
||||||
|
- 好きな銘柄TOP3
|
||||||
|
- AIの一言コメント
|
||||||
|
|
||||||
|
**用途:**
|
||||||
|
- 店員に見せて好みを伝える
|
||||||
|
- SNSシェア(画像として保存)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 7. QR循環ロジック
|
||||||
|
|
||||||
|
### 7.1 BtoB: PDFメニュー作成時
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 飲食店が店舗在庫を登録
|
||||||
|
2. PDFメニュー生成時、各銘柄にQRコード埋め込み
|
||||||
|
3. QRコード内容: https://pon-room.app/sake/{sakeId}
|
||||||
|
4. 印刷して店内に設置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 BtoC: QRスキャン時
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 客がアプリでQRスキャン
|
||||||
|
2. 銘柄詳細をAR風に表示(オーバーレイ)
|
||||||
|
3. 「記録する」ボタンで自分のリストに追加
|
||||||
|
4. ポンポイント+10pt獲得
|
||||||
|
5. 酒蔵リンクへの誘導
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 循環の価値
|
||||||
|
|
||||||
|
- **客**: スキャンする度にポイントが貯まる
|
||||||
|
- **店**: 客の興味データが蓄積(アナリティクス)
|
||||||
|
- **蔵元**: ECサイトへの誘導で売上向上
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎓 8. オンボーディング(使い方ガイド)
|
||||||
|
|
||||||
|
### 8.1 初回起動時
|
||||||
|
|
||||||
|
**4ステップガイド:**
|
||||||
|
1. ようこそ!日本酒の記録を始めよう
|
||||||
|
2. カメラで撮影するだけでAIが自動解析
|
||||||
|
3. お気に入りを記録して自分だけのコレクションを
|
||||||
|
4. 飲食店の方はBusinessモードへ切り替え可能
|
||||||
|
|
||||||
|
### 8.2 再表示機能
|
||||||
|
|
||||||
|
- 各画面右上の「?」アイコン
|
||||||
|
- タップでガイドを再表示
|
||||||
|
|
||||||
|
### 8.3 Businessモード専用ガイド
|
||||||
|
|
||||||
|
**3ステップ(モード切り替え時のみ表示):**
|
||||||
|
1. 店舗情報を設定しましょう
|
||||||
|
2. メニューを作成してPDF出力
|
||||||
|
3. QRコードで客とつながる
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 9. 開発優先順位(Phase別)
|
||||||
|
|
||||||
|
### Phase 0: 基盤整備 ✅
|
||||||
|
- [x] CommonSpecification.md 作成
|
||||||
|
- [ ] SakeItemモデルの拡張
|
||||||
|
|
||||||
|
### Phase 1: 安心の確保(1-2時間)
|
||||||
|
- [ ] カメラロール保存実装(gal)
|
||||||
|
- [ ] iOS/Android権限設定
|
||||||
|
- [ ] 撮影後の自動保存フロー
|
||||||
|
|
||||||
|
### Phase 2: BtoB機能完成(4-6時間)
|
||||||
|
- [ ] PDF + printing 実装
|
||||||
|
- [ ] モックアップ厳密再現
|
||||||
|
- [ ] QRコード埋め込み
|
||||||
|
- [ ] レイアウト定数化
|
||||||
|
|
||||||
|
### Phase 3: AI最適化(3-4時間)
|
||||||
|
- [ ] Google ML Kit 導入
|
||||||
|
- [ ] OCRテキスト抽出
|
||||||
|
- [ ] Gemini APIへのテキスト送信切り替え
|
||||||
|
|
||||||
|
### Phase 4: ゲーミフィケーション(後回しOK)
|
||||||
|
- [ ] ポンポイントシステム
|
||||||
|
- [ ] 酒向カード生成
|
||||||
|
- [ ] QRスキャン機能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 10. レイアウト定数(PDF用)
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class PDFLayoutConstants {
|
||||||
|
// 余白
|
||||||
|
static const double pageMargin = 24.0;
|
||||||
|
static const double sectionSpacing = 20.0;
|
||||||
|
static const double itemSpacing = 12.0;
|
||||||
|
|
||||||
|
// フォントサイズ
|
||||||
|
static const double titleFontSize = 24.0;
|
||||||
|
static const double headingFontSize = 14.0;
|
||||||
|
static const double bodyFontSize = 11.0;
|
||||||
|
static const double labelFontSize = 9.0;
|
||||||
|
|
||||||
|
// 線の太さ
|
||||||
|
static const double borderWidthThin = 0.5;
|
||||||
|
static const double borderWidthMedium = 1.0;
|
||||||
|
static const double borderWidthThick = 2.0;
|
||||||
|
|
||||||
|
// 色(posimaiカラー)
|
||||||
|
static final PdfColor borderColor = PdfColor.fromHex('#E2E8F0');
|
||||||
|
static final PdfColor labelColor = PdfColor.fromHex('#64748B');
|
||||||
|
static final PdfColor accentColor = PdfColor.fromHex('#376495');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 11. セキュリティ・プライバシー
|
||||||
|
|
||||||
|
### 11.1 データ保存場所
|
||||||
|
|
||||||
|
**✅ ローカル保存:**
|
||||||
|
- Hive DB: アプリ専用ディレクトリ
|
||||||
|
- 写真: カメラロール(ユーザー端末)
|
||||||
|
|
||||||
|
**✅ 外部送信(一時的、保存されない):**
|
||||||
|
- Gemini API: OCRテキストのみ(画像は送らない)
|
||||||
|
|
||||||
|
**❌ 外部送信なし:**
|
||||||
|
- 個人情報の無断クラウド保存
|
||||||
|
- サードパーティへのデータ販売
|
||||||
|
|
||||||
|
### 11.2 将来のFirebase連携
|
||||||
|
|
||||||
|
- ユーザーが明示的に「同期」を選択した場合のみ
|
||||||
|
- 画像はFirebase Storageへ
|
||||||
|
- テキストデータはFirestoreへ
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 12. 量産対応(ワイン・ビールアプリへの展開)
|
||||||
|
|
||||||
|
### 12.1 拡張方法
|
||||||
|
|
||||||
|
`metadata.app_type` を変更するだけで転用可能:
|
||||||
|
- `sake` → 日本酒
|
||||||
|
- `wine` → ワイン
|
||||||
|
- `beer` → クラフトビール
|
||||||
|
- `whisky` → ウイスキー
|
||||||
|
|
||||||
|
### 12.2 共通化できる機能
|
||||||
|
|
||||||
|
- 撮影・OCR・AI解析フロー
|
||||||
|
- ポイントシステム
|
||||||
|
- PDF出力・QR循環
|
||||||
|
- マイページ・設定
|
||||||
|
|
||||||
|
### 12.3 差分化が必要な箇所
|
||||||
|
|
||||||
|
- JSONの `hidden_specs` フィールド
|
||||||
|
- ワイン: ぶどう品種、産地、ヴィンテージ
|
||||||
|
- ビール: ホップ、モルト、IBU値
|
||||||
|
- キャッチコピーのトーン
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 13. AIエージェントへの指示
|
||||||
|
|
||||||
|
### 13.1 開発時の鉄則
|
||||||
|
|
||||||
|
1. **この仕様書を最優先のルール(バイブル)として参照せよ**
|
||||||
|
2. **display_data と hidden_specs を明確に分離せよ**
|
||||||
|
3. **カードUIはシンプルに保ち、display_dataのみ使用せよ**
|
||||||
|
4. **挫折防止のため、段階的に実装せよ(Phase順守)**
|
||||||
|
5. **モックアップがある場合は1px単位で厳密再現せよ**
|
||||||
|
|
||||||
|
### 13.2 コード生成時の注意
|
||||||
|
|
||||||
|
- null安全性を徹底
|
||||||
|
- エラーハンドリングを統一
|
||||||
|
- デバッグログを適切に配置
|
||||||
|
- コメントは日本語で簡潔に
|
||||||
|
|
||||||
|
### 13.3 質問・提案時のルール
|
||||||
|
|
||||||
|
- 仕様書と矛盾する提案をする場合は理由を明記
|
||||||
|
- 新機能追加時はPhaseを提案
|
||||||
|
- データ構造変更時はJSON例を提示
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 14. サポート・連絡先
|
||||||
|
|
||||||
|
**プロジェクトオーナー**: posimai
|
||||||
|
**開発支援AI**: Claude (Anthropic), Gemini (Google AI), ChatGPT (OpenAI)
|
||||||
|
**バージョン管理**: Git (GitHub)
|
||||||
|
**CI/CD**: Vercel (Web), Firebase Hosting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 15. 最後に
|
||||||
|
|
||||||
|
この仕様書は「挫折しない日本酒アプリ開発」のために、複数のAIの知恵を結集して作成されました。
|
||||||
|
|
||||||
|
**重要な心構え:**
|
||||||
|
- シンプルから始める
|
||||||
|
- データは豊富に持つが、表示は最小限に
|
||||||
|
- ユーザーが「楽しい」と感じる体験を最優先に
|
||||||
|
|
||||||
|
**Let's build the best sake app together! 🍶✨**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**End of CommonSpecification.md v1.0**
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
CommonSpecification.md (v1.0)
|
||||||
|
1. プロジェクト概要
|
||||||
|
アプリ名: ぽんるーむ(Pon-Room)
|
||||||
|
|
||||||
|
コンセプト: 日本酒の「記録・解析・循環」を支えるAIアプリ。
|
||||||
|
|
||||||
|
ターゲット: - B2C: 一般ユーザー(自分の日本酒体験を記録・診断・共有)
|
||||||
|
|
||||||
|
B2B: 飲食店(自店の日本酒メニューをPDF/QR付きで作成・印刷)
|
||||||
|
|
||||||
|
2. 技術スタック
|
||||||
|
Frontend: Flutter (iOS/Android/Web)
|
||||||
|
|
||||||
|
Backend/DB: Cloud Firestore, Firebase Auth
|
||||||
|
|
||||||
|
AI/ML: - Local OCR: Google ML Kit (テキスト抽出)
|
||||||
|
|
||||||
|
LLM Analysis: Gemini API (2.5 Flash / 3 Flash)
|
||||||
|
|
||||||
|
Library: - gal: カメラロール保存用
|
||||||
|
|
||||||
|
pdf, printing: PDF生成・印刷用
|
||||||
|
|
||||||
|
qr_flutter: QRコード生成用
|
||||||
|
|
||||||
|
3. 共通データ構造 (JSON Schema)
|
||||||
|
すべてのAI解析およびデータ保存はこの形式に従うこと。
|
||||||
|
|
||||||
|
JSON
|
||||||
|
|
||||||
|
{
|
||||||
|
"display_data": {
|
||||||
|
"name": "銘柄名",
|
||||||
|
"catch_phrase": "AI生成のキャッチコピー",
|
||||||
|
"image_path": "local/path/to/image.jpg",
|
||||||
|
"rating": 4.5
|
||||||
|
},
|
||||||
|
"hidden_specs": {
|
||||||
|
"brewery": "蔵元名",
|
||||||
|
"prefecture": "都道府県",
|
||||||
|
"type": "特定名称(純米大吟醸等)",
|
||||||
|
"alcohol_content": 16.0,
|
||||||
|
"polishing_ratio": 23,
|
||||||
|
"rice_variety": "使用米",
|
||||||
|
"sake_meter_value": 0.0,
|
||||||
|
"qr_code_url": "https://pon-room.app/sake/12345"
|
||||||
|
},
|
||||||
|
"badges": {
|
||||||
|
"is_recommended": false,
|
||||||
|
"is_seasonal": false,
|
||||||
|
"season_tag": "春限定"
|
||||||
|
},
|
||||||
|
"gamification": {
|
||||||
|
"pon_points": 10,
|
||||||
|
"sake_mbti_type": "フルーティー・モダン型"
|
||||||
|
},
|
||||||
|
"user_data": {
|
||||||
|
"is_favorite": false,
|
||||||
|
"memo": "テイスティングメモ",
|
||||||
|
"created_at": "ISO8601形式"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"app_type": "sake",
|
||||||
|
"version": "1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
4. 機能別ガイドライン
|
||||||
|
4.1 撮影・解析フロー (Hybrid Analysis)
|
||||||
|
撮影: 写真撮影後、即座に「カメラロール(端末のギャラリー)」へ保存する(galパッケージ使用)。
|
||||||
|
|
||||||
|
OCR: Google ML Kitで画像から生テキストを抽出。
|
||||||
|
|
||||||
|
AI解析: 抽出テキストのみをGeminiへ送信し、上記JSON形式で構造化データを取得。
|
||||||
|
|
||||||
|
注意: 画像を直接AIに送るのではなく、テキストを介することでトークン節約とエラー回避を行う。
|
||||||
|
|
||||||
|
4.2 UI構成 (5タブ・切り替え制)
|
||||||
|
タブ1 (リスト): カード形式。display_dataのみを表示し、極力シンプルに保つ。
|
||||||
|
|
||||||
|
タブ2 (スキャン/作成): - Consumerモード: QRスキャン(他店メニューの読み取り・ポイント獲得)。
|
||||||
|
|
||||||
|
Businessモード: PDFメニュー作成(QRコード自動埋め込み)。
|
||||||
|
|
||||||
|
タブ3 (AI/診断): AIソムリエ、酒蔵マップ(将来拡張)。
|
||||||
|
|
||||||
|
タブ4 (マップ): 酒蔵巡りマップ(未実装)。
|
||||||
|
|
||||||
|
タブ5 (マイページ): - 酒向(しゅこう)カード: MBTI風の自己診断結果、バッジ、獲得ポイントを表示。
|
||||||
|
|
||||||
|
設定: 右上の「歯車アイコン」内にモード切り替え、使い方ガイド、システム設定を集約。
|
||||||
|
|
||||||
|
4.3 オンボーディング (Onboarding)
|
||||||
|
初回起動時に4枚のステップガイドを表示。
|
||||||
|
|
||||||
|
各画面右上の「?」アイコンでガイドを再表示。
|
||||||
|
|
||||||
|
Businessモード切り替え時は専用の3ステップガイドを追加表示。
|
||||||
|
|
||||||
|
4.4 B2B/B2C 循環ロジック
|
||||||
|
B2B: PDF出力時に各銘柄の qr_code_url をQRコードとして埋め込む。
|
||||||
|
|
||||||
|
B2C: そのQRをスキャンすると、詳細情報が表示され「ポンポイント」が貯まる仕組みを想定。
|
||||||
|
|
||||||
|
5. 開発優先順位 (Roadmap)
|
||||||
|
Phase 1: CommonSpecification.md に基づくデータモデルの再定義。
|
||||||
|
|
||||||
|
Phase 2: カメラロール保存機能の実装(データ消失リスク回避)。
|
||||||
|
|
||||||
|
Phase 3: PDF + printing 実装(Antigravity評価用のB2B機能)。
|
||||||
|
|
||||||
|
Phase 4: OCR + Gemini API連携の最適化。
|
||||||
|
|
||||||
|
Phase 5: マイページ(酒向カード)およびゲーミフィケーション実装。
|
||||||
|
|
@ -0,0 +1,923 @@
|
||||||
|
# 新生ぽんるーむ - 最終完全要件定義書
|
||||||
|
|
||||||
|
**プロジェクト名**: 新生ぽんるーむ (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. 「遊び心」- 意味のあるデータ分析
|
||||||
|
自分の好みを「発見」する楽しさ。
|
||||||
|
日本全国制覇マップ、フレーバーマトリックス。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 技術スタック
|
||||||
|
|
||||||
|
### 必須要件
|
||||||
|
```yaml
|
||||||
|
Flutter SDK: 3.38.3+
|
||||||
|
Dart: 3.10.1+
|
||||||
|
Android:
|
||||||
|
compileSdk: 36
|
||||||
|
targetSdk: 35 (Android 15対応)
|
||||||
|
minSdk: 21 (Android 5.0+)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 依存パッケージ
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
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デザイン原則
|
||||||
|
|
||||||
|
### デザインコンセプト
|
||||||
|
**「雑誌のような洗練、魔法のような心地よさ」**
|
||||||
|
|
||||||
|
### カラーパレット
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 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); // ウィッシュリスト
|
||||||
|
```
|
||||||
|
|
||||||
|
### タイポグラフィ
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 銘柄名・タイトル: 明朝体(格調高く)
|
||||||
|
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キー設定
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// lib/secrets.dart
|
||||||
|
class Secrets {
|
||||||
|
static const String geminiApiKey = 'AIzaSyA2BSr16R2k0bHjSYcSUdmLoY8PKwaFts0';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gemini 3.0統合
|
||||||
|
|
||||||
|
**重要**: 以下のいずれかを使用
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// オプション1: 最新の3.0プレビュー(推奨)
|
||||||
|
model: 'gemini-3.0-flash-latest'
|
||||||
|
|
||||||
|
// オプション2: 安定版2.5 Flash(スキャンアプリで実績)
|
||||||
|
model: 'gemini-2.5-flash-latest'
|
||||||
|
```
|
||||||
|
|
||||||
|
### AIソムリエのリアルタイム実況
|
||||||
|
|
||||||
|
**解析中のUI**:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**ステージの例**:
|
||||||
|
```dart
|
||||||
|
[
|
||||||
|
'ラベルを読んでいます...',
|
||||||
|
'お、これは〇〇県の銘柄ですね...',
|
||||||
|
'精米歩合を確認中...',
|
||||||
|
'データを整理しています...',
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自動ポエム生成
|
||||||
|
|
||||||
|
Geminiのレスポンスに以下を追加:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final prompt = '''
|
||||||
|
この画像は日本酒のボトルまたはラベルの写真です。
|
||||||
|
ラベルに書かれているテキスト情報を正確に読み取り、JSON形式で返してください。
|
||||||
|
|
||||||
|
【重要な指示】
|
||||||
|
1. ラベルに明確に書かれている情報のみを抽出してください
|
||||||
|
2. 推測や想像で値を入れないでください(読めない場合はnullまたは省略)
|
||||||
|
3. 数値は必ず数字のみ(単位記号%などは除く)
|
||||||
|
4. このお酒の印象を一言で表す「キャッチコピー」を自動生成してください
|
||||||
|
|
||||||
|
【出力フォーマット】
|
||||||
|
{
|
||||||
|
"brandName": "銘柄名",
|
||||||
|
"type": "特定名称",
|
||||||
|
"alcoholContent": 数値,
|
||||||
|
"polishingRatio": 数値,
|
||||||
|
"breweryName": "酒蔵名",
|
||||||
|
"prefecture": "都道府県名",
|
||||||
|
"catchCopy": "夜風と楽しみたい、淡麗な一滴" // ← 自動生成
|
||||||
|
}
|
||||||
|
|
||||||
|
**JSON以外の余計な説明は一切不要です。JSONのみを出力してください。**
|
||||||
|
''';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 2. 「美録」- インスタ映えする情報の見せ方
|
||||||
|
|
||||||
|
### モダン・カタログ・カード
|
||||||
|
|
||||||
|
**Web版の縦並びを捨て、左右2段構成を採用**:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 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)の画像を自動生成**:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 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象限チャートで味の傾向を可視化**:
|
||||||
|
|
||||||
|
```
|
||||||
|
甘口
|
||||||
|
↑
|
||||||
|
│
|
||||||
|
濃醇 ←─┼─→ 淡麗
|
||||||
|
│
|
||||||
|
↓
|
||||||
|
辛口
|
||||||
|
```
|
||||||
|
|
||||||
|
**実装**:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 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カラーで塗りつぶし**:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**バッジ表示**:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// マップの上部に表示
|
||||||
|
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 バックアップ・ステータス
|
||||||
|
|
||||||
|
**マイページの右上に控えめに表示**:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
**キャッチコピーを追加**:
|
||||||
|
|
||||||
|
```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;
|
||||||
|
|
||||||
|
// 🆕 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)
|
||||||
|
|
||||||
|
**質問例ボタン**:
|
||||||
|
```dart
|
||||||
|
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! 🍶✨**
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
# Gemini Pro API セットアップガイド
|
||||||
|
|
||||||
|
## 現在の状況
|
||||||
|
|
||||||
|
### Google One Proについて
|
||||||
|
**重要:** Google One Pro会員の特典とGemini APIの料金は**別物**です。
|
||||||
|
|
||||||
|
- **Google One Pro特典**: Gmail、Docs、DriveでのGemini機能が使える
|
||||||
|
- **Gemini API (開発者向け)**: アプリ開発用のAPI、無料枠と有料枠がある
|
||||||
|
|
||||||
|
**このアプリで使用しているのは開発者向けGemini APIです。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API制限の解除方法
|
||||||
|
|
||||||
|
### 方法1: 時間を待つ(無料)
|
||||||
|
現在レート制限エラーが出ている場合:
|
||||||
|
|
||||||
|
1. **1-2分待つ** - RPM(15回/分)制限は1分で解除
|
||||||
|
2. **アプリを再起動** - 内部カウンターがリセット
|
||||||
|
3. **5秒以上間隔を空けて解析** - 自動的に待機するようになりました
|
||||||
|
|
||||||
|
### 方法2: 別のGoogleアカウントを使用
|
||||||
|
新しいAPIキーを取得:
|
||||||
|
|
||||||
|
1. 別のGoogleアカウントでログイン
|
||||||
|
2. [Google AI Studio](https://aistudio.google.com/apikey) にアクセス
|
||||||
|
3. 新しいAPIキーを生成
|
||||||
|
4. `lib/secrets.dart` のAPIキーを差し替え
|
||||||
|
5. アプリを再ビルド
|
||||||
|
|
||||||
|
**注意:** 同じIPアドレスから使用すると制限が共有される可能性があります。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gemini API 有料プラン(Pay-as-you-go)への移行
|
||||||
|
|
||||||
|
### 重要な説明
|
||||||
|
|
||||||
|
**有料プラン ≠ より高性能なモデル**
|
||||||
|
|
||||||
|
有料プランにすると得られるのは:
|
||||||
|
- ✅ **RPM(リクエスト数)制限の大幅緩和**: 15回/分 → 1,000回/分
|
||||||
|
- ✅ **TPM(トークン数)制限の緩和**: 100万/分 → 400万/分
|
||||||
|
- ❌ **モデルの性能向上ではない**
|
||||||
|
|
||||||
|
**モデルの違い:**
|
||||||
|
|
||||||
|
| モデル | 特徴 | 速度 | 精度 | 料金 |
|
||||||
|
|--------|------|------|------|------|
|
||||||
|
| **gemini-2.5-flash** (現在使用中) | 高速・軽量 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 無料枠: 15RPM<br>有料: $0.075/1M入力 |
|
||||||
|
| **gemini-2.5-pro** | 高精度・重い | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 無料枠: 2RPM<br>有料: $1.25/1M入力 |
|
||||||
|
|
||||||
|
**推奨:**
|
||||||
|
- **このアプリでは `gemini-2.5-flash` で十分な精度**が出ています
|
||||||
|
- モデルは変更せず、**有料プランに移行するだけで制限が解除**されます
|
||||||
|
- Pro版は約17倍高額なので、必要性がない限り不要
|
||||||
|
|
||||||
|
### 料金体系(Pay-as-you-go)
|
||||||
|
|
||||||
|
#### gemini-2.5-flash(推奨)
|
||||||
|
- **無料版**: 15回/分、100万トークン/分
|
||||||
|
- **有料版**: 1,000回/分、400万トークン/分
|
||||||
|
- **料金**: $0.075/100万入力トークン、$0.30/100万出力トークン
|
||||||
|
|
||||||
|
**実際のコスト例:**
|
||||||
|
- 日本酒画像1枚解析 ≒ 5万トークン入力 = **約$0.004 (約0.6円)**
|
||||||
|
- 100枚解析しても **約60円**
|
||||||
|
|
||||||
|
#### gemini-2.5-pro(高精度が必要な場合のみ)
|
||||||
|
- **無料版**: 2回/分、3.2万トークン/分
|
||||||
|
- **有料版**: 1,000回/分、400万トークン/分
|
||||||
|
- **料金**: $1.25/100万入力トークン(flashの約17倍)
|
||||||
|
|
||||||
|
### 有料版への移行手順
|
||||||
|
|
||||||
|
#### 1. Google AI Studioで課金設定
|
||||||
|
```
|
||||||
|
1. https://aistudio.google.com/ にアクセス
|
||||||
|
2. 左メニュー「Billing」をクリック
|
||||||
|
3. 「Enable Pay-as-you-go」を選択
|
||||||
|
4. クレジットカード情報を登録
|
||||||
|
5. 利用上限を設定(例: 月$10まで)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. アプリのモデル設定(変更不要)
|
||||||
|
`lib/services/gemini_service.dart` の18行目:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 現在の設定(推奨: このまま)
|
||||||
|
static const String _modelName = 'gemini-2.5-flash';
|
||||||
|
|
||||||
|
// より高精度が必要な場合のみ(17倍高額)
|
||||||
|
// static const String _modelName = 'gemini-2.5-pro';
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意:** モデルを変更しなくても、課金設定するだけで**制限が大幅に緩和**されます
|
||||||
|
|
||||||
|
#### 3. アプリを再ビルド(モデル変更時のみ)
|
||||||
|
```bash
|
||||||
|
# モデルを変更した場合のみ必要
|
||||||
|
flutter clean
|
||||||
|
flutter build apk --release
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意:** 課金設定だけなら**再ビルド不要**です。同じAPKで制限が緩和されます。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 現在の制限対策(無料版)
|
||||||
|
|
||||||
|
アプリに実装済みの対策:
|
||||||
|
|
||||||
|
### 1. 自動レート制限保護
|
||||||
|
- **5秒間隔の強制**: 連続解析時に自動的に待機
|
||||||
|
- トークン消費量のログ出力(デバッグ時)
|
||||||
|
|
||||||
|
### 2. 画像サイズの最適化
|
||||||
|
- カメラ解像度: **high (1080p) → medium (720p)**
|
||||||
|
- ファイルサイズ約50%削減
|
||||||
|
- 認識精度は維持
|
||||||
|
|
||||||
|
### 3. ユーザー向け情報表示
|
||||||
|
- ホーム画面に「ℹ️」アイコン追加
|
||||||
|
- API制限の詳細説明
|
||||||
|
- 推奨事項の表示
|
||||||
|
|
||||||
|
### 4. 詳細なエラーメッセージ
|
||||||
|
```
|
||||||
|
AI使用制限に達しました。
|
||||||
|
無料版は1分間に15回までの制限があります。
|
||||||
|
1〜2分後に再度お試しください。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## おすすめの運用方法
|
||||||
|
|
||||||
|
### 無料版で運用する場合
|
||||||
|
- ✅ **5秒以上間隔を空けて解析**(自動化済み)
|
||||||
|
- ✅ 同じ画像を再解析しない
|
||||||
|
- ✅ エラーが出たら1-2分待つ
|
||||||
|
- ✅ 1日あたり100-200枚程度まで
|
||||||
|
|
||||||
|
### 有料版に移行する場合(推奨)
|
||||||
|
- ✅ 月数百円で制限をほぼ気にせず使える
|
||||||
|
- ✅ RPM 1,000回/分 → 実質無制限
|
||||||
|
- ✅ ビジネス利用に最適
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## トラブルシューティング
|
||||||
|
|
||||||
|
### Q: 新しいAPIキーでもすぐエラーが出る
|
||||||
|
A: 同じIPアドレスから利用している可能性があります。モバイルデータ通信に切り替えてテストしてください。
|
||||||
|
|
||||||
|
### Q: Google One Proで無制限にならないの?
|
||||||
|
A: Google One ProはGmail/Docs用の特典です。API料金は別途発生します。
|
||||||
|
|
||||||
|
### Q: 有料版にしたらいくらかかる?
|
||||||
|
A: 1日10枚程度なら月100円以下、100枚/日でも月700円程度です。
|
||||||
|
|
||||||
|
### Q: APIキーが流出したらどうなる?
|
||||||
|
A: Google AI Studioで即座に無効化し、新しいキーを発行してください。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参考リンク
|
||||||
|
|
||||||
|
- [Google AI Studio](https://aistudio.google.com/)
|
||||||
|
- [Gemini API料金表](https://ai.google.dev/pricing)
|
||||||
|
- [API使用量の確認](https://aistudio.google.com/quota)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**最終更新:** 2025-12-31
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
# ponshu_room_lite
|
||||||
|
|
||||||
|
A new Flutter project.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
This project is a starting point for a Flutter application.
|
||||||
|
|
||||||
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
|
||||||
|
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||||
|
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||||
|
|
||||||
|
For help getting started with Flutter development, view the
|
||||||
|
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||||
|
samples, guidance on mobile development, and a full API reference.
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
# 🚀 新生ぽんるーむ - スタートガイド
|
||||||
|
|
||||||
|
**プロジェクト名**: 新生ぽんるーむ (Reborn Ponshu Room)
|
||||||
|
**作成日**: 2025-12-29
|
||||||
|
**このフォルダの状態**: 完全にクリーン(設計書のみ)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ このフォルダの状況
|
||||||
|
|
||||||
|
```
|
||||||
|
現在のフォルダ: C:\Users\maita\posimai-project\ponshu_room_reborn
|
||||||
|
|
||||||
|
含まれるもの:
|
||||||
|
✅ FINAL_REQUIREMENTS.md(最終仕様書)
|
||||||
|
✅ ANTIGRAVITY_PROMPT.md(実装手順書)
|
||||||
|
✅ UI_UX_DECISION_GUIDE.md(デザイン決定ガイド)
|
||||||
|
✅ START_HERE.md(このファイル)
|
||||||
|
|
||||||
|
含まれないもの:
|
||||||
|
❌ 古いWeb版のコード(完全に除外)
|
||||||
|
❌ lib/ フォルダ
|
||||||
|
❌ pubspec.yaml
|
||||||
|
❌ 何のコードもありません
|
||||||
|
```
|
||||||
|
|
||||||
|
**これは意図的です。完全にゼロから始めます。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Antigravity向け - 最終プロンプト
|
||||||
|
|
||||||
|
以下をAntigravityに送ってください:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📝 Antigravityへの指示
|
||||||
|
|
||||||
|
```
|
||||||
|
@Antigravity
|
||||||
|
|
||||||
|
新生ぽんるーむをゼロから実装します。
|
||||||
|
このフォルダ(ponshu_room_reborn)は完全にクリーンです。
|
||||||
|
|
||||||
|
📂 現在のフォルダ:
|
||||||
|
C:\Users\maita\posimai-project\ponshu_room_reborn
|
||||||
|
|
||||||
|
📄 読み込むべきドキュメント:
|
||||||
|
1. ANTIGRAVITY_PROMPT.md(メイン実装手順)
|
||||||
|
2. FINAL_REQUIREMENTS.md(完全仕様書)
|
||||||
|
3. UI_UX_DECISION_GUIDE.md(デザインガイド)
|
||||||
|
|
||||||
|
🔑 新しいAPIキー:
|
||||||
|
AIzaSyA2BSr16R2k0bHjSYcSUdmLoY8PKwaFts0
|
||||||
|
|
||||||
|
🎨 デザイン方針:
|
||||||
|
- 余白は最小限(padding: 8dp, separator: 4dp)
|
||||||
|
- 写真を主役にする(120x120)
|
||||||
|
- フル幅カード
|
||||||
|
- 極細ボーダー(0.5px)で区切り
|
||||||
|
|
||||||
|
⚠️ 絶対にやってはいけないこと:
|
||||||
|
❌ ../ponshu-room/ フォルダを参照しない
|
||||||
|
❌ 古いコードをコピーしない
|
||||||
|
❌ Web版のUIを踏襲しない
|
||||||
|
|
||||||
|
✅ やるべきこと:
|
||||||
|
1. このフォルダで flutter create . を実行
|
||||||
|
2. ANTIGRAVITY_PROMPT.md の手順に従って実装
|
||||||
|
3. Gemini 2.5-flash-latest または gemini-3.0-flash-latest を使用
|
||||||
|
4. リアルタイム実況付きAI解析を実装
|
||||||
|
5. SafeAreaを徹底使用(Android 15対応)
|
||||||
|
|
||||||
|
🚀 開始:
|
||||||
|
ANTIGRAVITY_PROMPT.md の「プロジェクト作成」セクションから開始してください。
|
||||||
|
Phase 1-1(プロジェクト初期化)完了後に報告をお願いします。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 実装の流れ
|
||||||
|
|
||||||
|
### Phase 0: 準備(今ここ)
|
||||||
|
- [x] 新しいフォルダ作成
|
||||||
|
- [x] 設計書をコピー
|
||||||
|
- [ ] Antigravityに指示を送る
|
||||||
|
|
||||||
|
### Phase 1: MVP(5時間)
|
||||||
|
- [ ] プロジェクト初期化(`flutter create .`)
|
||||||
|
- [ ] pubspec.yaml設定
|
||||||
|
- [ ] Android設定(compileSdk: 36)
|
||||||
|
- [ ] secrets.dart作成(新APIキー)
|
||||||
|
- [ ] Hiveセットアップ
|
||||||
|
- [ ] Gemini解析(リアルタイム実況)
|
||||||
|
- [ ] フル幅カード
|
||||||
|
- [ ] SafeArea対応
|
||||||
|
|
||||||
|
### Phase 2: 美録(3時間)
|
||||||
|
- [ ] Instagram用画像生成
|
||||||
|
- [ ] Hero遷移
|
||||||
|
|
||||||
|
### Phase 3: 遊び心(4時間)
|
||||||
|
- [ ] フレーバー・マトリックス
|
||||||
|
- [ ] 日本酒・制覇マップ
|
||||||
|
|
||||||
|
### Phase 4: 共有(2時間)
|
||||||
|
- [ ] キャッチコピー付き共有
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 APIキーの設定
|
||||||
|
|
||||||
|
Antigravityが `lib/secrets.dart` を作成した後、以下が正しく設定されているか確認してください:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// lib/secrets.dart
|
||||||
|
class Secrets {
|
||||||
|
static const String geminiApiKey = 'AIzaSyA2BSr16R2k0bHjSYcSUdmLoY8PKwaFts0';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 完成確認チェックリスト
|
||||||
|
|
||||||
|
### MVP完成の確認
|
||||||
|
- [ ] カメラで日本酒を撮影できる
|
||||||
|
- [ ] Gemini解析で「ラベルを読んでいます...」と表示される
|
||||||
|
- [ ] キャッチコピーが自動生成される
|
||||||
|
- [ ] フル幅カード(写真120x120)で表示される
|
||||||
|
- [ ] Android 15 (Xiaomi 14T Pro) で見切れない
|
||||||
|
- [ ] データがHiveに保存される
|
||||||
|
|
||||||
|
### 最終完成の確認
|
||||||
|
- [ ] すべての機能が動作
|
||||||
|
- [ ] 「雑誌のような」デザイン
|
||||||
|
- [ ] 60fpsの滑らかな動作
|
||||||
|
- [ ] 「魔法のような」心地よさ
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 トラブルシューティング
|
||||||
|
|
||||||
|
### もしAntigravityが古いコードを参照しようとしたら
|
||||||
|
|
||||||
|
**即座に指摘してください**:
|
||||||
|
```
|
||||||
|
@Antigravity
|
||||||
|
|
||||||
|
古い ponshu-room フォルダは参照しないでください。
|
||||||
|
このフォルダ(ponshu_room_reborn)内のドキュメントだけを見てください。
|
||||||
|
```
|
||||||
|
|
||||||
|
### もし余白が大きすぎたら
|
||||||
|
|
||||||
|
**即座に修正を依頼してください**:
|
||||||
|
```
|
||||||
|
@Antigravity
|
||||||
|
|
||||||
|
余白が大きすぎます。以下に修正してください:
|
||||||
|
- ListView.padding: EdgeInsets.symmetric(horizontal: 8, vertical: 12)
|
||||||
|
- separatorBuilder: SizedBox(height: 4)
|
||||||
|
- 写真サイズ: 120x120
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎊 準備完了!
|
||||||
|
|
||||||
|
すべての準備が整いました。
|
||||||
|
|
||||||
|
**次のアクション**:
|
||||||
|
1. 上記の「Antigravityへの指示」をコピー
|
||||||
|
2. Antigravityに送信
|
||||||
|
3. 実装開始を待つ
|
||||||
|
|
||||||
|
**新生ぽんるーむの誕生を楽しみにしています!🍶✨**
|
||||||
|
|
@ -0,0 +1,397 @@
|
||||||
|
# 🚀 ぽんるーむ Lite - 完全スタートガイド
|
||||||
|
|
||||||
|
**プロジェクト名**: ぽんるーむ Lite (Ponshu Room Lite)
|
||||||
|
**バージョン**: 2.0 - "My Digital Sake Cellar with Personality"
|
||||||
|
**作成日**: 2025-12-29
|
||||||
|
**このフォルダの状態**: 完全にクリーン(設計書のみ)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 「Lite」の意味
|
||||||
|
|
||||||
|
**「Lite」= 機能制限ではなく、「洗練されたコア体験」**
|
||||||
|
|
||||||
|
- ✅ 写真を主役にした雑誌のようなUI
|
||||||
|
- ✅ 完全ローカル保存(プライバシー最優先)
|
||||||
|
- ✅ MBTI・四柱推命と日本酒を掛け合わせた「あなただけの診断」
|
||||||
|
- ✅ フォント切り替え機能(明朝体 ⇔ ゴシック体)
|
||||||
|
|
||||||
|
**将来の「Pro」版**:
|
||||||
|
- Synology NAS連携
|
||||||
|
- 高度な統計分析
|
||||||
|
- チーム共有機能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 このアプリの3つの魂
|
||||||
|
|
||||||
|
### 🍶 1. 「瞬撮」- 魔法の解析体験
|
||||||
|
カメラを向けて撮るだけで、Gemini 3.0がリアルタイム実況しながら解析。
|
||||||
|
```
|
||||||
|
「ラベルを読んでいます...」
|
||||||
|
「お、これは山口県の銘柄ですね...」
|
||||||
|
「データを整理しています...」
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🎨 2. 「美録」- インスタ映えする情報の見せ方
|
||||||
|
雑誌のようなレイアウト、選べるフォント、Instagram用正方形画像生成。
|
||||||
|
|
||||||
|
### 🧩 3. 「遊び心」- あなただけのパーソナライズ
|
||||||
|
- **MBTI × 日本酒相性診断**: 「このお酒はENFPのあなたにピッタリ!」
|
||||||
|
- **四柱推命 × 今日の一滴**: 「今日の運気を上げる辛口な一本」
|
||||||
|
- **フレーバー・マトリックス**: あなたの好みを可視化
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ このフォルダの状況
|
||||||
|
|
||||||
|
```
|
||||||
|
現在のフォルダ: C:\Users\maita\posimai-project\ponshu_room_lite
|
||||||
|
|
||||||
|
含まれるもの:
|
||||||
|
✅ FINAL_REQUIREMENTS.md(最終仕様書)
|
||||||
|
✅ ANTIGRAVITY_PROMPT.md(実装手順書)
|
||||||
|
✅ UI_UX_DECISION_GUIDE.md(デザイン決定ガイド)
|
||||||
|
✅ START_HERE_FINAL.md(このファイル)
|
||||||
|
|
||||||
|
含まれないもの:
|
||||||
|
❌ 古いWeb版のコード(完全に除外)
|
||||||
|
❌ lib/ フォルダ
|
||||||
|
❌ pubspec.yaml
|
||||||
|
❌ 何のコードもありません
|
||||||
|
```
|
||||||
|
|
||||||
|
**これは意図的です。完全にゼロから始めます。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Antigravity向け - 最終完全プロンプト
|
||||||
|
|
||||||
|
以下をAntigravityに送ってください:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```
|
||||||
|
@Antigravity
|
||||||
|
|
||||||
|
ぽんるーむ Lite をゼロから実装します。
|
||||||
|
このフォルダ(ponshu_room_lite)は完全にクリーンです。
|
||||||
|
|
||||||
|
📂 現在のフォルダ:
|
||||||
|
C:\Users\maita\posimai-project\ponshu_room_lite
|
||||||
|
|
||||||
|
📄 読み込むべきドキュメント:
|
||||||
|
1. START_HERE_FINAL.md(最初に読む)
|
||||||
|
2. ANTIGRAVITY_PROMPT.md(メイン実装手順)
|
||||||
|
3. FINAL_REQUIREMENTS.md(完全仕様書)
|
||||||
|
|
||||||
|
🔑 新しいAPIキー:
|
||||||
|
AIzaSyA2BSr16R2k0bHjSYcSUdmLoY8PKwaFts0
|
||||||
|
|
||||||
|
🎨 デザイン方針(Lite版の魂):
|
||||||
|
|
||||||
|
【写真最大化】
|
||||||
|
- 余白は最小限(padding: 8dp, separator: 4dp)
|
||||||
|
- 写真サイズ: 120x120
|
||||||
|
- フル幅カード
|
||||||
|
- 極細ボーダー(0.5px)で区切り
|
||||||
|
|
||||||
|
【フォント戦略】
|
||||||
|
- デフォルト: Noto Sans JP(親しみやすいゴシック体)
|
||||||
|
- 設定で切り替え可能: Noto Serif JP(高級感のある明朝体)
|
||||||
|
- ユーザーが好みで選べるようにする
|
||||||
|
|
||||||
|
【パーソナライズ機能の準備】
|
||||||
|
- UserProfileモデルを作成:
|
||||||
|
- MBTI診断結果(16タイプ)
|
||||||
|
- 生年月日(四柱推命用)
|
||||||
|
- 好みのフォント設定
|
||||||
|
- これらは Phase 2以降で使用(今は枠だけ準備)
|
||||||
|
|
||||||
|
⚠️ 絶対にやってはいけないこと:
|
||||||
|
❌ ../ponshu-room/ フォルダを参照しない
|
||||||
|
❌ 古いコードをコピーしない
|
||||||
|
❌ Web版のUIを踏襲しない
|
||||||
|
|
||||||
|
✅ やるべきこと(Phase 1):
|
||||||
|
|
||||||
|
【1-1. プロジェクト初期化】
|
||||||
|
1. このフォルダで flutter create . を実行
|
||||||
|
2. pubspec.yaml を設定(google_fonts, hive, riverpod, etc.)
|
||||||
|
3. Android設定(compileSdk: 36, targetSdk: 35)
|
||||||
|
|
||||||
|
【1-2. データモデル】
|
||||||
|
1. SakeItem モデル:
|
||||||
|
- 基本情報(銘柄名、酒蔵、都道府県、etc.)
|
||||||
|
- キャッチコピー(Geminiが自動生成)
|
||||||
|
- フレーバースコア(sweetnessScore, bodyScore)
|
||||||
|
|
||||||
|
2. UserProfile モデル(🆕):
|
||||||
|
- mbtiType: String? // 例: "ENFP"
|
||||||
|
- birthDate: DateTime? // 四柱推命用
|
||||||
|
- fontPreference: String // "serif" or "sans"
|
||||||
|
- createdAt: DateTime
|
||||||
|
|
||||||
|
【1-3. テーマ設定】
|
||||||
|
1. AppTheme を作成:
|
||||||
|
- posimaiカラー(#376495)
|
||||||
|
- 動的フォント切り替え対応
|
||||||
|
- デフォルトは Noto Sans JP
|
||||||
|
|
||||||
|
2. FontProvider(Riverpod)を作成:
|
||||||
|
- UserProfileからフォント設定を読み込み
|
||||||
|
- アプリ全体でリアクティブに切り替え
|
||||||
|
|
||||||
|
【1-4. Gemini AI解析】
|
||||||
|
1. GeminiService:
|
||||||
|
- モデル: gemini-2.5-flash-latest
|
||||||
|
- リアルタイム実況機能
|
||||||
|
- キャッチコピー自動生成
|
||||||
|
|
||||||
|
【1-5. UI実装】
|
||||||
|
1. ホーム画面(4タブ):
|
||||||
|
- 酒リスト(フル幅カード)
|
||||||
|
- マップ
|
||||||
|
- AIソムリエ
|
||||||
|
- プロフィール
|
||||||
|
|
||||||
|
2. フル幅カード:
|
||||||
|
- 左: 写真120x120
|
||||||
|
- 右: 銘柄名(選択したフォント)、データ
|
||||||
|
|
||||||
|
3. プロフィール画面:
|
||||||
|
- フォント切り替えスイッチ
|
||||||
|
- MBTI入力欄(今は空でOK)
|
||||||
|
- 生年月日入力欄(今は空でOK)
|
||||||
|
|
||||||
|
【1-6. SafeArea対応】
|
||||||
|
- すべての画面でSafeAreaを徹底使用
|
||||||
|
- Android 15で見切れ・重なりゼロ
|
||||||
|
|
||||||
|
🚀 開始:
|
||||||
|
ANTIGRAVITY_PROMPT.md の「プロジェクト作成」セクションから開始してください。
|
||||||
|
Phase 1-1(プロジェクト初期化)完了後に報告をお願いします。
|
||||||
|
|
||||||
|
💡 重要な追加仕様:
|
||||||
|
|
||||||
|
【フォント切り替えの実装】
|
||||||
|
設定画面に以下を追加:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 設定画面の一部
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('フォント設定'),
|
||||||
|
SegmentedButton(
|
||||||
|
segments: [
|
||||||
|
ButtonSegment(value: 'sans', label: Text('ゴシック')),
|
||||||
|
ButtonSegment(value: 'serif', label: Text('明朝')),
|
||||||
|
],
|
||||||
|
selected: {currentFont},
|
||||||
|
onSelectionChanged: (Set<String> newSelection) {
|
||||||
|
// UserProfileを更新
|
||||||
|
// AppThemeを再構築
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
【UserProfileモデルの完全コード】
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// lib/models/user_profile.dart
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
part 'user_profile.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 1)
|
||||||
|
class UserProfile extends HiveObject {
|
||||||
|
@HiveField(0)
|
||||||
|
String? mbtiType; // "ENFP", "INTJ", etc.
|
||||||
|
|
||||||
|
@HiveField(1)
|
||||||
|
DateTime? birthDate; // 四柱推命用
|
||||||
|
|
||||||
|
@HiveField(2)
|
||||||
|
String fontPreference; // "serif" or "sans"
|
||||||
|
|
||||||
|
@HiveField(3)
|
||||||
|
DateTime createdAt;
|
||||||
|
|
||||||
|
@HiveField(4)
|
||||||
|
DateTime? updatedAt;
|
||||||
|
|
||||||
|
UserProfile({
|
||||||
|
this.mbtiType,
|
||||||
|
this.birthDate,
|
||||||
|
this.fontPreference = 'sans', // デフォルトはゴシック
|
||||||
|
required this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
【Phase 2以降の予告】
|
||||||
|
- MBTI × 日本酒相性診断
|
||||||
|
- 四柱推命 × 今日の一滴
|
||||||
|
- フレーバー・マトリックス
|
||||||
|
- Instagram用画像生成(診断結果入り)
|
||||||
|
|
||||||
|
これらは Phase 1完了後に実装します。
|
||||||
|
今は UserProfile の枠だけ準備してください。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 実装の流れ
|
||||||
|
|
||||||
|
### Phase 0: 準備(今ここ)
|
||||||
|
- [x] 新しいフォルダ作成(ponshu_room_lite)
|
||||||
|
- [x] 設計書をコピー
|
||||||
|
- [ ] Antigravityに指示を送る
|
||||||
|
|
||||||
|
### Phase 1: MVP(5-6時間)
|
||||||
|
- [ ] プロジェクト初期化
|
||||||
|
- [ ] データモデル(SakeItem + UserProfile)
|
||||||
|
- [ ] フォント切り替え機能
|
||||||
|
- [ ] Gemini解析(リアルタイム実況)
|
||||||
|
- [ ] フル幅カード
|
||||||
|
- [ ] SafeArea対応
|
||||||
|
|
||||||
|
### Phase 2: パーソナライズ(3-4時間)
|
||||||
|
- [ ] MBTI入力UI
|
||||||
|
- [ ] MBTI × 日本酒相性診断
|
||||||
|
- [ ] 四柱推命 × 今日の一滴
|
||||||
|
- [ ] フレーバー・マトリックス
|
||||||
|
|
||||||
|
### Phase 3: 共有機能(2時間)
|
||||||
|
- [ ] Instagram用画像生成(診断結果入り)
|
||||||
|
- [ ] キャッチコピー付き共有
|
||||||
|
|
||||||
|
### Phase 4: 遊び心(3時間)
|
||||||
|
- [ ] 日本酒・制覇マップ
|
||||||
|
- [ ] AIソムリエチャット
|
||||||
|
|
||||||
|
**合計: 13-15時間**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 フォント選択のガイド
|
||||||
|
|
||||||
|
### デフォルト: Noto Sans JP(ゴシック体)
|
||||||
|
```
|
||||||
|
印象: 親しみやすい、読みやすい、診断コンテンツと相性◎
|
||||||
|
おすすめな人: MBTIや診断を楽しみたい、カジュアルに記録したい
|
||||||
|
```
|
||||||
|
|
||||||
|
### オプション: Noto Serif JP(明朝体)
|
||||||
|
```
|
||||||
|
印象: 高級感、伝統、雑誌のような洗練
|
||||||
|
おすすめな人: 日本酒の本格的な雰囲気を大切にしたい
|
||||||
|
```
|
||||||
|
|
||||||
|
**いつでも切り替え可能!設定画面からワンタップ。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 APIキーの設定
|
||||||
|
|
||||||
|
Antigravityが `lib/secrets.dart` を作成した後、以下が正しく設定されているか確認してください:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// lib/secrets.dart
|
||||||
|
class Secrets {
|
||||||
|
static const String geminiApiKey = 'AIzaSyA2BSr16R2k0bHjSYcSUdmLoY8PKwaFts0';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 完成確認チェックリスト
|
||||||
|
|
||||||
|
### Phase 1 MVP完成の確認
|
||||||
|
- [ ] カメラで日本酒を撮影できる
|
||||||
|
- [ ] Gemini解析で「ラベルを読んでいます...」と表示される
|
||||||
|
- [ ] キャッチコピーが自動生成される
|
||||||
|
- [ ] フル幅カード(写真120x120)で表示される
|
||||||
|
- [ ] フォント切り替えが動作する(ゴシック ⇔ 明朝)
|
||||||
|
- [ ] Android 15で見切れない
|
||||||
|
- [ ] UserProfileが保存される
|
||||||
|
|
||||||
|
### Phase 2 パーソナライズ完成の確認
|
||||||
|
- [ ] MBTI診断結果を入力できる
|
||||||
|
- [ ] 「このお酒はあなた(ENFP)にピッタリ!」と表示される
|
||||||
|
- [ ] フレーバー・マトリックスが表示される
|
||||||
|
|
||||||
|
### 最終完成の確認
|
||||||
|
- [ ] すべての機能が動作
|
||||||
|
- [ ] 「雑誌のような」デザイン
|
||||||
|
- [ ] 60fpsの滑らかな動作
|
||||||
|
- [ ] 「魔法のような」心地よさ
|
||||||
|
- [ ] 知人に見せたくなる
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 トラブルシューティング
|
||||||
|
|
||||||
|
### もしAntigravityが古いコードを参照しようとしたら
|
||||||
|
|
||||||
|
**即座に指摘してください**:
|
||||||
|
```
|
||||||
|
@Antigravity
|
||||||
|
|
||||||
|
古い ponshu-room フォルダは参照しないでください。
|
||||||
|
このフォルダ(ponshu_room_lite)内のドキュメントだけを見てください。
|
||||||
|
```
|
||||||
|
|
||||||
|
### もし余白が大きすぎたら
|
||||||
|
|
||||||
|
```
|
||||||
|
@Antigravity
|
||||||
|
|
||||||
|
余白が大きすぎます。以下に修正してください:
|
||||||
|
- ListView.padding: EdgeInsets.symmetric(horizontal: 8, vertical: 12)
|
||||||
|
- separatorBuilder: SizedBox(height: 4)
|
||||||
|
- 写真サイズ: 120x120
|
||||||
|
```
|
||||||
|
|
||||||
|
### もしフォント切り替えが動作しなかったら
|
||||||
|
|
||||||
|
```
|
||||||
|
@Antigravity
|
||||||
|
|
||||||
|
フォント切り替えが動作しません。
|
||||||
|
UserProfileのfontPreferenceを変更したら、AppThemeが再構築されるようにしてください。
|
||||||
|
Riverpodのproviderを使って、リアクティブに更新してください。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎊 準備完了!
|
||||||
|
|
||||||
|
すべての準備が整いました。
|
||||||
|
|
||||||
|
**次のアクション**:
|
||||||
|
1. 上記の「Antigravityへの指示」をコピー
|
||||||
|
2. Antigravityに送信
|
||||||
|
3. 実装開始を待つ
|
||||||
|
|
||||||
|
**「ぽんるーむ Lite」の誕生を楽しみにしています!🍶✨**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 将来の拡張(Pro版へ)
|
||||||
|
|
||||||
|
Phase 1-4が完了し、知人にも好評だったら:
|
||||||
|
|
||||||
|
### ぽんるーむ Pro(構想)
|
||||||
|
- Synology NAS自動バックアップ
|
||||||
|
- 複数デバイス同期
|
||||||
|
- チーム共有機能
|
||||||
|
- 高度な統計分析
|
||||||
|
- カスタムテーマ
|
||||||
|
- エクスポート機能強化
|
||||||
|
|
||||||
|
**まずは Lite を完璧に仕上げましょう!**
|
||||||
|
|
@ -0,0 +1,456 @@
|
||||||
|
# 新生ぽんるーむ - UI/UX決定ガイド
|
||||||
|
|
||||||
|
**対象**: プロダクトオーナー(posimai)
|
||||||
|
**目的**: シンプル・スタイリッシュ・今風なUIを作るための意思決定ガイド
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 今風なUI/UXとは?
|
||||||
|
|
||||||
|
### 2025年のモバイルアプリトレンド
|
||||||
|
|
||||||
|
#### ✅ 採用すべき要素
|
||||||
|
|
||||||
|
1. **ミニマリズム**
|
||||||
|
- 余白をたっぷり取る
|
||||||
|
- 要素を最小限に絞る
|
||||||
|
- 色数を3-4色に抑える
|
||||||
|
|
||||||
|
2. **マテリアル3デザイン**
|
||||||
|
- elevation: 0(フラット)
|
||||||
|
- border: 1px程度の細いライン
|
||||||
|
- 角丸: 8-12px程度
|
||||||
|
|
||||||
|
3. **タイポグラフィのメリハリ**
|
||||||
|
- 見出し: 大きく(20-24px)
|
||||||
|
- 本文: 適度(13-15px)
|
||||||
|
- キャプション: 小さく(11-12px)
|
||||||
|
- フォントは2種類まで
|
||||||
|
|
||||||
|
4. **自然なアニメーション**
|
||||||
|
- 200-300msの短いアニメーション
|
||||||
|
- easeInOut curve
|
||||||
|
- Hero遷移
|
||||||
|
|
||||||
|
5. **ダークモード非対応でOK**
|
||||||
|
- ライトモードのみでシンプルに
|
||||||
|
- 日本酒の写真が映えるのはライトモード
|
||||||
|
|
||||||
|
#### ❌ 避けるべき要素
|
||||||
|
|
||||||
|
1. グラデーション多用
|
||||||
|
2. 派手なドロップシャドウ(elevation: 8以上)
|
||||||
|
3. 過度な装飾・パターン
|
||||||
|
4. 5色以上のカラーパレット
|
||||||
|
5. 遅いアニメーション(500ms以上)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 意思決定チェックリスト
|
||||||
|
|
||||||
|
あなたが決めるべき5つの要素を選択してください。
|
||||||
|
|
||||||
|
### 1. カラースキーム
|
||||||
|
|
||||||
|
**質問**: posimaiブルー (#376495) 以外のアクセントカラーは?
|
||||||
|
|
||||||
|
#### オプションA: 柔らかいパステル(推奨)
|
||||||
|
```dart
|
||||||
|
お気に入り: Color(0xFFE8B4B8) // 淡いピンク
|
||||||
|
買いたい: Color(0xFFFEF3C7) // 淡い黄色
|
||||||
|
```
|
||||||
|
**印象**: 優しい、女性受け良い、Instagram映え
|
||||||
|
|
||||||
|
#### オプションB: 渋い和風
|
||||||
|
```dart
|
||||||
|
お気に入り: Color(0xFFB38867) // 茶色
|
||||||
|
買いたい: Color(0xFFDAA520) // 金色
|
||||||
|
```
|
||||||
|
**印象**: 格調高い、男性受け良い、日本酒らしい
|
||||||
|
|
||||||
|
#### オプションC: モノクロ+posimaiブルーのみ
|
||||||
|
```dart
|
||||||
|
お気に入り: posimaiBlue
|
||||||
|
買いたい: Color(0xFF4A4A4A) // グレー
|
||||||
|
```
|
||||||
|
**印象**: 究極にシンプル、プロフェッショナル
|
||||||
|
|
||||||
|
**あなたの選択**: [ A / B / C ]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. カードデザイン
|
||||||
|
|
||||||
|
**質問**: リスト表示のカードレイアウトは?
|
||||||
|
|
||||||
|
#### オプションA: 縦型カード(推奨)
|
||||||
|
```
|
||||||
|
┌──────────┐
|
||||||
|
│ │
|
||||||
|
│ [画像] │ ← 正方形
|
||||||
|
│ │
|
||||||
|
├──────────┤
|
||||||
|
│ 獺祭 │
|
||||||
|
│ ★★★★★ │
|
||||||
|
└──────────┘
|
||||||
|
```
|
||||||
|
**メリット**: Instagram的、写真が大きい、スクロールしやすい
|
||||||
|
|
||||||
|
#### オプションB: 横型カード
|
||||||
|
```
|
||||||
|
┌──────┬────────────┐
|
||||||
|
│ │ 獺祭 │
|
||||||
|
│[画像]│ 旭酒造 │
|
||||||
|
│ │ ★★★★★ │
|
||||||
|
└──────┴────────────┘
|
||||||
|
```
|
||||||
|
**メリット**: 情報が見やすい、1画面に多く表示
|
||||||
|
|
||||||
|
#### オプションC: グリッド表示(2列)
|
||||||
|
```
|
||||||
|
┌────┬────┐
|
||||||
|
│[画] │[画] │
|
||||||
|
│獺祭 │久保田│
|
||||||
|
└────┴────┘
|
||||||
|
```
|
||||||
|
**メリット**: コレクション感、Pinterest的
|
||||||
|
|
||||||
|
**あなたの選択**: [ A / B / C ]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. アニメーション
|
||||||
|
|
||||||
|
**質問**: 画面遷移のアニメーションは?
|
||||||
|
|
||||||
|
#### オプションA: Hero + Fade(推奨)
|
||||||
|
```dart
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
PageRouteBuilder(
|
||||||
|
pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
|
||||||
|
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
**印象**: 滑らか、高級感、Instagram的
|
||||||
|
|
||||||
|
#### オプションB: スライド
|
||||||
|
```dart
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(builder: (context) => DetailScreen()),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
**印象**: iOS的、シンプル、速い
|
||||||
|
|
||||||
|
#### オプションC: なし
|
||||||
|
```dart
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => DetailScreen()),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
**印象**: 最速、シンプル、パフォーマンス優先
|
||||||
|
|
||||||
|
**あなたの選択**: [ A / B / C ]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. ボトムナビゲーション
|
||||||
|
|
||||||
|
**質問**: アイコンのスタイルは?
|
||||||
|
|
||||||
|
#### オプションA: Material Icons(推奨)
|
||||||
|
```dart
|
||||||
|
Icons.liquor // 酒
|
||||||
|
Icons.map // マップ
|
||||||
|
Icons.smart_toy // AI
|
||||||
|
Icons.person // マイページ
|
||||||
|
```
|
||||||
|
**印象**: シンプル、認識しやすい、Androidらしい
|
||||||
|
|
||||||
|
#### オプションB: Cupertino Icons
|
||||||
|
```dart
|
||||||
|
CupertinoIcons.sparkles // 酒
|
||||||
|
CupertinoIcons.map // マップ
|
||||||
|
CupertinoIcons.chat_bubble // AI
|
||||||
|
CupertinoIcons.person // マイページ
|
||||||
|
```
|
||||||
|
**印象**: iOS的、おしゃれ、一貫性
|
||||||
|
|
||||||
|
#### オプションC: カスタムアイコン
|
||||||
|
```
|
||||||
|
🍶 徳利・おちょこ
|
||||||
|
🗺️ 地図
|
||||||
|
🤖 AIロボット
|
||||||
|
👤 人型
|
||||||
|
```
|
||||||
|
**印象**: 個性的、日本酒らしい、作成コスト高
|
||||||
|
|
||||||
|
**あなたの選択**: [ A / B / C ]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. FloatingActionButton(カメラボタン)
|
||||||
|
|
||||||
|
**質問**: カメラボタンの動作は?
|
||||||
|
|
||||||
|
#### オプションA: タップでカメラ、長押しでギャラリー(推奨)
|
||||||
|
```dart
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => launchCamera(),
|
||||||
|
onLongPress: () => launchGallery(),
|
||||||
|
child: FloatingActionButton(...),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
**メリット**: 1ボタンでシンプル、上級者向け
|
||||||
|
|
||||||
|
#### オプションB: タップで選択ダイアログ表示
|
||||||
|
```dart
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
child: AlertDialog(
|
||||||
|
title: Text('写真を選択'),
|
||||||
|
actions: [
|
||||||
|
TextButton(child: Text('カメラ'), onPressed: ...),
|
||||||
|
TextButton(child: Text('ギャラリー'), onPressed: ...),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**メリット**: 分かりやすい、初心者向け
|
||||||
|
|
||||||
|
#### オプションC: 2つのボタン(カメラ・ギャラリー)
|
||||||
|
```dart
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
FloatingActionButton(icon: Icons.camera, ...),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
FloatingActionButton(icon: Icons.photo_library, ...),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
**メリット**: 直感的、すぐ選べる
|
||||||
|
|
||||||
|
**あなたの選択**: [ A / B / C ]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 追加の細かい決定事項
|
||||||
|
|
||||||
|
### 6. 評価表示
|
||||||
|
|
||||||
|
**質問**: 星の評価表示は?
|
||||||
|
|
||||||
|
#### オプションA: 星アイコン(推奨)
|
||||||
|
```
|
||||||
|
★★★★★ 5.0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### オプションB: 数値のみ
|
||||||
|
```
|
||||||
|
5.0 / 5.0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### オプションC: プログレスバー
|
||||||
|
```
|
||||||
|
━━━━━ 100%
|
||||||
|
```
|
||||||
|
|
||||||
|
**あなたの選択**: [ A / B / C ]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. 検索バー
|
||||||
|
|
||||||
|
**質問**: 検索バーのデザインは?
|
||||||
|
|
||||||
|
#### オプションA: 角丸ピル型(推奨)
|
||||||
|
```dart
|
||||||
|
TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey[100],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### オプションB: 角丸ボックス型
|
||||||
|
```dart
|
||||||
|
TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**あなたの選択**: [ A / B ]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. 写真の表示比率
|
||||||
|
|
||||||
|
**質問**: カード内の写真の比率は?
|
||||||
|
|
||||||
|
#### オプションA: 正方形(1:1)(推奨)
|
||||||
|
```dart
|
||||||
|
AspectRatio(aspectRatio: 1)
|
||||||
|
```
|
||||||
|
**メリット**: Instagram的、統一感
|
||||||
|
|
||||||
|
#### オプションB: 4:3
|
||||||
|
```dart
|
||||||
|
AspectRatio(aspectRatio: 4/3)
|
||||||
|
```
|
||||||
|
**メリット**: 情報量が多い、カメラ標準
|
||||||
|
|
||||||
|
#### オプションC: 16:9
|
||||||
|
```dart
|
||||||
|
AspectRatio(aspectRatio: 16/9)
|
||||||
|
```
|
||||||
|
**メリット**: 横長、映画的
|
||||||
|
|
||||||
|
**あなたの選択**: [ A / B / C ]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. タグ表示
|
||||||
|
|
||||||
|
**質問**: タグのデザインは?
|
||||||
|
|
||||||
|
#### オプションA: 角丸チップ(推奨)
|
||||||
|
```dart
|
||||||
|
Chip(
|
||||||
|
label: Text('甘口'),
|
||||||
|
backgroundColor: Colors.blue[50],
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### オプションB: ボックス型
|
||||||
|
```dart
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue[50],
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Text('甘口'),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**あなたの選択**: [ A / B ]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. ローディング表示
|
||||||
|
|
||||||
|
**質問**: AI解析中のローディングは?
|
||||||
|
|
||||||
|
#### オプションA: CircularProgressIndicator(推奨)
|
||||||
|
```dart
|
||||||
|
Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
valueColor: AlwaysStoppedAnimation(posimaiBlue),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### オプションB: LinearProgressIndicator
|
||||||
|
```dart
|
||||||
|
LinearProgressIndicator(
|
||||||
|
valueColor: AlwaysStoppedAnimation(posimaiBlue),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### オプションC: カスタムアニメーション
|
||||||
|
```
|
||||||
|
🍶 徳利がくるくる回る
|
||||||
|
```
|
||||||
|
|
||||||
|
**あなたの選択**: [ A / B / C ]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 推奨設定(まとめ)
|
||||||
|
|
||||||
|
もし迷ったら、この設定で進めてください:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
カラースキーム: オプションA(柔らかいパステル)
|
||||||
|
カードデザイン: オプションA(縦型カード)
|
||||||
|
アニメーション: オプションA(Hero + Fade)
|
||||||
|
ボトムナビゲーション: オプションA(Material Icons)
|
||||||
|
FABの動作: オプションA(タップでカメラ、長押しでギャラリー)
|
||||||
|
評価表示: オプションA(星アイコン)
|
||||||
|
検索バー: オプションA(角丸ピル型)
|
||||||
|
写真比率: オプションA(正方形 1:1)
|
||||||
|
タグ表示: オプションA(角丸チップ)
|
||||||
|
ローディング: オプションA(CircularProgressIndicator)
|
||||||
|
```
|
||||||
|
|
||||||
|
**この設定の印象**: Instagram的、洗練、女性受け良、シンプル、今風
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 参考アプリ
|
||||||
|
|
||||||
|
### 同じ方向性のアプリ
|
||||||
|
|
||||||
|
1. **Instagram** - 写真重視、ミニマル
|
||||||
|
2. **Pinterest** - グリッド表示、コレクション感
|
||||||
|
3. **Apple Music** - 角丸カード、余白たっぷり
|
||||||
|
4. **Spotify** - ダークモード、アルバムアート重視
|
||||||
|
5. **Google Keep** - シンプル、カラフルなタグ
|
||||||
|
|
||||||
|
### 避けるべき方向性
|
||||||
|
|
||||||
|
1. **古いSkeuomorphic** - リアルな質感
|
||||||
|
2. **ゴチャゴチャしたUI** - 情報過多
|
||||||
|
3. **派手なアニメーション** - ゲーム的
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 決定シート
|
||||||
|
|
||||||
|
以下をコピーして、あなたの選択を記入してください:
|
||||||
|
|
||||||
|
```
|
||||||
|
=== 新生ぽんるーむ UI/UX決定シート ===
|
||||||
|
|
||||||
|
1. カラースキーム: [ A / B / C ]
|
||||||
|
2. カードデザイン: [ A / B / C ]
|
||||||
|
3. アニメーション: [ A / B / C ]
|
||||||
|
4. ボトムナビゲーション: [ A / B / C ]
|
||||||
|
5. FABの動作: [ A / B / C ]
|
||||||
|
6. 評価表示: [ A / B / C ]
|
||||||
|
7. 検索バー: [ A / B ]
|
||||||
|
8. 写真比率: [ A / B / C ]
|
||||||
|
9. タグ表示: [ A / B ]
|
||||||
|
10. ローディング: [ A / B / C ]
|
||||||
|
|
||||||
|
その他の要望:
|
||||||
|
|
||||||
|
|
||||||
|
署名: posimai
|
||||||
|
日付: 2025-12-29
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**このシートを埋めて、Antigravityに渡してください!**
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
Resolving dependencies...
|
||||||
|
Downloading packages...
|
||||||
|
_fe_analyzer_shared 67.0.0 (92.0.0 available)
|
||||||
|
analyzer 6.4.1 (9.0.0 available)
|
||||||
|
analyzer_plugin 0.11.3 (0.13.11 available)
|
||||||
|
build 2.4.1 (4.0.3 available)
|
||||||
|
build_config 1.1.2 (1.2.0 available)
|
||||||
|
build_resolvers 2.4.2 (3.0.4 available)
|
||||||
|
build_runner 2.4.13 (2.10.4 available)
|
||||||
|
build_runner_core 7.3.2 (9.3.2 available)
|
||||||
|
characters 1.4.0 (1.4.1 available)
|
||||||
|
custom_lint 0.5.11 (0.8.1 available)
|
||||||
|
custom_lint_core 0.5.14 (0.8.2 available)
|
||||||
|
dart_style 2.3.6 (3.1.3 available)
|
||||||
|
freezed_annotation 2.4.4 (3.1.0 available)
|
||||||
|
image 4.5.4 (4.7.2 available)
|
||||||
|
matcher 0.12.17 (0.12.18 available)
|
||||||
|
material_color_utilities 0.11.1 (0.13.0 available)
|
||||||
|
package_info_plus 8.3.1 (9.0.0 available)
|
||||||
|
riverpod_analyzer_utils 1.0.0-dev.1 (1.0.0-dev.8 available)
|
||||||
|
riverpod_annotation 3.0.0-dev.3 (4.0.0 available)
|
||||||
|
riverpod_generator 3.0.0-dev.11 (4.0.0+1 available)
|
||||||
|
rxdart 0.27.7 (0.28.0 available)
|
||||||
|
shelf_web_socket 2.0.1 (3.0.0 available)
|
||||||
|
source_gen 1.5.0 (4.1.1 available)
|
||||||
|
source_helper 1.3.5 (1.3.9 available)
|
||||||
|
test 1.26.3 (1.28.0 available)
|
||||||
|
test_api 0.7.7 (0.7.8 available)
|
||||||
|
test_core 0.6.12 (0.6.14 available)
|
||||||
|
Got dependencies!
|
||||||
|
27 packages have newer versions incompatible with dependency constraints.
|
||||||
|
Try `flutter pub outdated` for more information.
|
||||||
|
Analyzing ponshu_room_lite...
|
||||||
|
|
||||||
|
info - The import of 'models/schema/display_data.dart' is unnecessary because all of the used elements are also provided by the import of 'models/sake_item.dart' - lib\main.dart:11:8 - unnecessary_import
|
||||||
|
info - The import of 'models/schema/hidden_specs.dart' is unnecessary because all of the used elements are also provided by the import of 'models/sake_item.dart' - lib\main.dart:12:8 - unnecessary_import
|
||||||
|
info - The import of 'models/schema/user_data.dart' is unnecessary because all of the used elements are also provided by the import of 'models/sake_item.dart' - lib\main.dart:13:8 - unnecessary_import
|
||||||
|
info - The import of 'models/schema/gamification.dart' is unnecessary because all of the used elements are also provided by the import of 'models/sake_item.dart' - lib\main.dart:14:8 - unnecessary_import
|
||||||
|
info - The import of 'models/schema/metadata.dart' is unnecessary because all of the used elements are also provided by the import of 'models/sake_item.dart' - lib\main.dart:15:8 - unnecessary_import
|
||||||
|
warning - Unused import: 'package:hive_flutter/hive_flutter.dart' - lib\providers\menu_providers.dart:2:8 - unused_import
|
||||||
|
warning - Unused import: '../models/sake_item.dart' - lib\providers\menu_providers.dart:3:8 - unused_import
|
||||||
|
info - The private field _capturedImages could be 'final' - lib\screens\camera_screen.dart:59:16 - prefer_final_fields
|
||||||
|
info - Don't invoke 'print' in production code - lib\screens\camera_screen.dart:133:7 - avoid_print
|
||||||
|
warning - Unused import: '../widgets/quota_warning_dialog.dart' - lib\screens\home_screen.dart:19:8 - unused_import
|
||||||
|
info - Don't use 'BuildContext's across async gaps - lib\screens\home_screen.dart:45:34 - use_build_context_synchronously
|
||||||
|
warning - The value of the local variable 'selectedTag' isn't used - lib\screens\home_screen.dart:54:11 - unused_local_variable
|
||||||
|
info - 'withOpacity' is deprecated and shouldn't be used. Use .withValues() to avoid precision loss - lib\screens\home_screen.dart:145:55 - deprecated_member_use
|
||||||
|
info - 'withOpacity' is deprecated and shouldn't be used. Use .withValues() to avoid precision loss - lib\screens\home_screen.dart:468:54 - deprecated_member_use
|
||||||
|
info - Unnecessary use of multiple underscores - lib\screens\menu_pricing_screen.dart:76:18 - unnecessary_underscores
|
||||||
|
info - 'withOpacity' is deprecated and shouldn't be used. Use .withValues() to avoid precision loss - lib\screens\menu_pricing_screen.dart:114:38 - deprecated_member_use
|
||||||
|
info - 'withOpacity' is deprecated and shouldn't be used. Use .withValues() to avoid precision loss - lib\screens\menu_pricing_screen.dart:115:39 - deprecated_member_use
|
||||||
|
info - Unnecessary use of multiple underscores - lib\screens\menu_pricing_screen.dart:160:43 - unnecessary_underscores
|
||||||
|
info - 'withOpacity' is deprecated and shouldn't be used. Use .withValues() to avoid precision loss - lib\screens\menu_pricing_screen.dart:176:47 - deprecated_member_use
|
||||||
|
info - 'withOpacity' is deprecated and shouldn't be used. Use .withValues() to avoid precision loss - lib\screens\menu_pricing_screen.dart:260:36 - deprecated_member_use
|
||||||
|
info - 'withOpacity' is deprecated and shouldn't be used. Use .withValues() to avoid precision loss - lib\screens\menu_pricing_screen.dart:261:34 - deprecated_member_use
|
||||||
|
warning - Unused import: '../services/pdf_service.dart' - lib\screens\menu_settings_screen.dart:8:8 - unused_import
|
||||||
|
warning - Unused import: 'package:printing/printing.dart' - lib\screens\menu_settings_screen.dart:12:8 - unused_import
|
||||||
|
info - Unnecessary use of multiple underscores - lib\screens\menu_settings_screen.dart:157:18 - unnecessary_underscores
|
||||||
|
info - Unnecessary use of 'toList' in a spread - lib\screens\menu_settings_screen.dart:194:26 - unnecessary_to_list_in_spreads
|
||||||
|
info - 'withOpacity' is deprecated and shouldn't be used. Use .withValues() to avoid precision loss - lib\screens\menu_settings_screen.dart:227:86 - deprecated_member_use
|
||||||
|
info - 'withOpacity' is deprecated and shouldn't be used. Use .withValues() to avoid precision loss - lib\screens\menu_settings_screen.dart:417:48 - deprecated_member_use
|
||||||
|
info - Don't use 'BuildContext's across async gaps - lib\screens\sake_detail_screen.dart:451:27 - use_build_context_synchronously
|
||||||
|
info - Unnecessary braces in a string interpolation - lib\screens\sake_detail_screen.dart:668:26 - unnecessary_brace_in_string_interps
|
||||||
|
warning - The value of the local variable 'price' isn't used - lib\screens\sake_detail_screen.dart:711:17 - unused_local_variable
|
||||||
|
info - Unnecessary use of 'toList' in a spread - lib\screens\sake_detail_screen.dart:866:31 - unnecessary_to_list_in_spreads
|
||||||
|
info - Don't use 'BuildContext's across async gaps, guarded by an unrelated 'mounted' check - lib\screens\sake_detail_screen.dart:986:23 - use_build_context_synchronously
|
||||||
|
info - Don't use 'BuildContext's across async gaps, guarded by an unrelated 'mounted' check - lib\screens\sake_detail_screen.dart:987:30 - use_build_context_synchronously
|
||||||
|
info - 'activeColor' is deprecated and shouldn't be used. Use activeThumbColor instead. This feature was deprecated after v3.31.0-2.0.pre - lib\screens\soul_screen.dart:128:22 - deprecated_member_use
|
||||||
|
info - Unnecessary braces in a string interpolation - lib\services\image_compression_service.dart:39:63 - unnecessary_brace_in_string_interps
|
||||||
|
info - Unnecessary braces in a string interpolation - lib\services\image_compression_service.dart:54:55 - unnecessary_brace_in_string_interps
|
||||||
|
info - Unnecessary braces in a string interpolation - lib\services\image_compression_service.dart:54:88 - unnecessary_brace_in_string_interps
|
||||||
|
info - Don't invoke 'print' in production code - lib\services\migration_service.dart:11:5 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - lib\services\migration_service.dart:32:9 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - lib\services\migration_service.dart:51:9 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - lib\services\migration_service.dart:53:9 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - lib\services\migration_service.dart:60:7 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - lib\services\migration_service.dart:80:11 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - lib\services\migration_service.dart:86:7 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - lib\services\migration_service.dart:88:7 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - lib\services\pdf_service.dart:37:14 - avoid_print
|
||||||
|
warning - The declaration '_getChildAspectRatio' isn't referenced - lib\services\pdf_service.dart:355:17 - unused_element
|
||||||
|
info - 'scale' is deprecated and shouldn't be used. Use scaleByVector3, scaleByVector4, or scaleByDouble instead - lib\widgets\sake_3d_carousel.dart:82:11 - deprecated_member_use
|
||||||
|
info - Can't use a relative path to import a library in 'lib' - tool\check_models.dart:3:8 - avoid_relative_lib_imports
|
||||||
|
info - Don't invoke 'print' in production code - tool\check_models.dart:6:3 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - tool\check_models.dart:10:5 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - tool\check_models.dart:26:7 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - tool\check_models.dart:32:14 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - tool\check_models.dart:33:14 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - tool\check_models.dart:34:14 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - tool\check_models.dart:35:14 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - tool\check_models.dart:39:9 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - tool\check_models.dart:42:7 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - tool\check_models.dart:44:7 - avoid_print
|
||||||
|
info - Don't invoke 'print' in production code - tool\check_models.dart:48:5 - avoid_print
|
||||||
|
|
||||||
|
60 issues found. (ran in 3.4s)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/to/reference-keystore
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
id("kotlin-android")
|
||||||
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
id("com.google.gms.google-services")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.posimai.ponshu_room_lite"
|
||||||
|
compileSdk = 36
|
||||||
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId = "com.posimai.ponshu_room_lite"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
|
minSdk = 24
|
||||||
|
targetSdk = 34
|
||||||
|
versionCode = flutter.versionCode
|
||||||
|
versionName = flutter.versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// 日本語OCR認識のためのML Kitライブラリ(型番スキャンアプリと同じ設定)
|
||||||
|
implementation("com.google.mlkit:text-recognition-japanese:16.0.1")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "1086358251978",
|
||||||
|
"project_id": "gen-lang-client-0086450305",
|
||||||
|
"storage_bucket": "gen-lang-client-0086450305.firebasestorage.app"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:1086358251978:android:997279498c984e4bae4733",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.posimai.ponshu_room_lite"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyDesRm4WlV3Aa31bY4vkJr8MeoKdYH-038"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Google ML Kit Text Recognition
|
||||||
|
-keep class com.google.mlkit.vision.text.** { *; }
|
||||||
|
-dontwarn com.google.mlkit.vision.text.**
|
||||||
|
|
||||||
|
# Keep all classes referenced by ML Kit
|
||||||
|
-keep class com.google.android.gms.** { *; }
|
||||||
|
-dontwarn com.google.android.gms.**
|
||||||
|
|
||||||
|
# ML Kit Japanese OCR model classes - IMPORTANT: do not remove
|
||||||
|
-keep class com.google.mlkit.vision.text.japanese.** { *; }
|
||||||
|
-keep class com.google.mlkit.vision.text.chinese.** { *; }
|
||||||
|
-keep class com.google.mlkit.vision.text.devanagari.** { *; }
|
||||||
|
-keep class com.google.mlkit.vision.text.korean.** { *; }
|
||||||
|
|
||||||
|
-dontwarn com.google.mlkit.vision.text.chinese.**
|
||||||
|
-dontwarn com.google.mlkit.vision.text.devanagari.**
|
||||||
|
-dontwarn com.google.mlkit.vision.text.japanese.**
|
||||||
|
-dontwarn com.google.mlkit.vision.text.korean.**
|
||||||
|
|
||||||
|
# TensorFlow Lite (ML Kit dependency)
|
||||||
|
-keep class org.tensorflow.lite.** { *; }
|
||||||
|
-dontwarn org.tensorflow.lite.**
|
||||||
|
|
||||||
|
# Native libraries
|
||||||
|
-keepclasseswithmembernames class * {
|
||||||
|
native <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ML Kit internal classes
|
||||||
|
-keep class com.google.mlkit.common.** { *; }
|
||||||
|
-keep class com.google.mlkit.** { *; }
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Network permissions for Gemini API -->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
|
|
||||||
|
<!-- Camera and device permissions -->
|
||||||
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" tools:replace="android:maxSdkVersion" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" tools:replace="android:maxSdkVersion" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:label="ponshu_room_lite"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/launcher_icon">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
<!-- Required to query activities that can process text, see:
|
||||||
|
https://developer.android.com/training/package-visibility and
|
||||||
|
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||||
|
|
||||||
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.posimai.ponshu_room_lite
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity()
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val newBuildDir: Directory =
|
||||||
|
rootProject.layout.buildDirectory
|
||||||
|
.dir("../../build")
|
||||||
|
.get()
|
||||||
|
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||||
|
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(":app")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<Delete>("clean") {
|
||||||
|
delete(rootProject.layout.buildDirectory)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||||
|
android.useAndroidX=true
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
pluginManagement {
|
||||||
|
val flutterSdkPath =
|
||||||
|
run {
|
||||||
|
val properties = java.util.Properties()
|
||||||
|
file("local.properties").inputStream().use { properties.load(it) }
|
||||||
|
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||||
|
flutterSdkPath
|
||||||
|
}
|
||||||
|
|
||||||
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
|
id("com.android.application") version "8.11.1" apply false
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
||||||
|
id("com.google.gms.google-services") version "4.4.0" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include(":app")
|
||||||
|
After Width: | Height: | Size: 691 KiB |
|
After Width: | Height: | Size: 322 KiB |
|
|
@ -0,0 +1,34 @@
|
||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>13.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
#include "Generated.xcconfig"
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
#include "Generated.xcconfig"
|
||||||
|
|
@ -0,0 +1,616 @@
|
||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||||
|
remoteInfo = Runner;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||||
|
);
|
||||||
|
path = RunnerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = RunnerTests;
|
||||||
|
productName = RunnerTests;
|
||||||
|
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
LastUpgradeCheck = 1510;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
331C8080294A63A400263BE5 = {
|
||||||
|
CreatedOnToolsVersion = 14.0;
|
||||||
|
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
331C807F294A63A400263BE5 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
331C807D294A63A400263BE5 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||||
|
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.posimai.ponshuRoomLite;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.posimai.ponshuRoomLite.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.posimai.ponshuRoomLite.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.posimai.ponshuRoomLite.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.posimai.ponshuRoomLite;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.posimai.ponshuRoomLite;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
331C8088294A63A400263BE5 /* Debug */,
|
||||||
|
331C8089294A63A400263BE5 /* Release */,
|
||||||
|
331C808A294A63A400263BE5 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1510"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||||
|
BuildableName = "RunnerTests.xctest"
|
||||||
|
BlueprintName = "RunnerTests"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
enableGPUValidationMode = "1"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "Icon-App-1024x1024@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 894 KiB |
|
After Width: | Height: | Size: 1012 B |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Launch Screen Assets
|
||||||
|
|
||||||
|
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||||
|
|
||||||
|
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Flutter View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Ponshu Room Lite</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>ponshu_room_lite</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>日本酒のラベルを撮影するためにカメラを使用します</string>
|
||||||
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
|
<string>撮影した日本酒の写真をギャラリーに保存します</string>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
#import "GeneratedPluginRegistrant.h"
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class RunnerTests: XCTestCase {
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
// If you add code to the Runner application, consider adding tests here.
|
||||||
|
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart'; // Localization
|
||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
import 'models/sake_item.dart';
|
||||||
|
import 'models/user_profile.dart';
|
||||||
|
import 'models/menu_settings.dart';
|
||||||
|
import 'providers/theme_provider.dart';
|
||||||
|
import 'screens/main_screen.dart';
|
||||||
|
|
||||||
|
import 'models/schema/display_data.dart';
|
||||||
|
import 'models/schema/hidden_specs.dart';
|
||||||
|
import 'models/schema/user_data.dart';
|
||||||
|
import 'models/schema/gamification.dart';
|
||||||
|
import 'models/schema/metadata.dart';
|
||||||
|
import 'models/schema/item_type.dart';
|
||||||
|
import 'services/migration_service.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// Initialize Hive
|
||||||
|
await Hive.initFlutter();
|
||||||
|
|
||||||
|
// Register Adapters
|
||||||
|
Hive.registerAdapter(SakeItemAdapter());
|
||||||
|
Hive.registerAdapter(UserProfileAdapter());
|
||||||
|
Hive.registerAdapter(MenuSettingsAdapter());
|
||||||
|
// Phase 0 New Adapters
|
||||||
|
Hive.registerAdapter(DisplayDataAdapter());
|
||||||
|
Hive.registerAdapter(HiddenSpecsAdapter());
|
||||||
|
Hive.registerAdapter(UserDataAdapter());
|
||||||
|
Hive.registerAdapter(GamificationAdapter());
|
||||||
|
Hive.registerAdapter(MetadataAdapter());
|
||||||
|
Hive.registerAdapter(ItemTypeAdapter());
|
||||||
|
|
||||||
|
// Run Phase 0 Migration (Backup & Convert)
|
||||||
|
await MigrationService.runMigration();
|
||||||
|
|
||||||
|
// Open Boxes
|
||||||
|
final userProfileBox = await Hive.openBox<UserProfile>('user_profile');
|
||||||
|
await Hive.openBox<SakeItem>('sake_items'); // Already opened by Migration, but safe to call again
|
||||||
|
await Hive.openBox('settings'); // Generic box for app settings (sort order)
|
||||||
|
await Hive.openBox<MenuSettings>('menu_settings'); // Menu display settings
|
||||||
|
|
||||||
|
runApp(
|
||||||
|
ProviderScope(
|
||||||
|
overrides: [
|
||||||
|
userProfileBoxProvider.overrideWithValue(userProfileBox),
|
||||||
|
],
|
||||||
|
child: const MyApp(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyApp extends ConsumerWidget {
|
||||||
|
const MyApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final lightTheme = ref.watch(lightThemeProvider);
|
||||||
|
final darkTheme = ref.watch(darkThemeProvider);
|
||||||
|
final themeMode = ref.watch(themeModeProvider);
|
||||||
|
|
||||||
|
return MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
title: 'Ponshu Room Lite',
|
||||||
|
theme: lightTheme,
|
||||||
|
darkTheme: darkTheme,
|
||||||
|
themeMode: themeMode,
|
||||||
|
|
||||||
|
// Localization Fix for DatePicker & Menu
|
||||||
|
localizationsDelegates: const [
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
supportedLocales: const [
|
||||||
|
Locale('ja', 'JP'),
|
||||||
|
],
|
||||||
|
|
||||||
|
home: const MainScreen(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
class JapanMapData {
|
||||||
|
// 0: Empty/Ocean
|
||||||
|
// 1-47: Prefecture IDs (JIS Code)
|
||||||
|
|
||||||
|
// High-Resolution 8-bit Map (26 cols x 32 rows)
|
||||||
|
// Designed to show Hokkaido size, Honshu curve, and close Kyushu/Shikoku
|
||||||
|
static final List<List<int>> gridLayout = [
|
||||||
|
// Hokkaido (Top) - Huge & Diamond shape approx
|
||||||
|
// Hokkaido (Top) - Refined Diamond Shape
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], // Tip
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0], // Widen
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0], // Widest top
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], // Mid
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], // Narrowing
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], // Oshinima Peninsula
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Tsugaru Strait
|
||||||
|
|
||||||
|
// Tohoku (The vertical stick)
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0], // Aomori
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 3, 3, 0, 0, 0, 0, 0, 0, 0], // Akita, Iwate
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 4, 4, 0, 0, 0, 0, 0, 0, 0], // Yamagata, Miyagi
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0], // Niigata, Fukushima
|
||||||
|
|
||||||
|
// Kanto & Chubu (The bulging turn)
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 10, 9, 9, 8, 8, 0, 0, 0, 0, 0, 0], // Niigata, Gunma, Tochigi, Ibaraki
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 16, 20, 20, 11, 11, 12, 12, 0, 0, 0, 0, 0, 0], // Ishikawa, Toyama, Nagano, Saitama, Chiba
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 21, 20, 19, 13, 13, 12, 0, 0, 0, 0, 0, 0, 0], // Fukui, Gifu, Nagano, Yamanashi, Tokyo, Chiba
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 25, 21, 23, 22, 14, 14, 0, 0, 0, 0, 0, 0, 0], // Fukui, Shiga, Gifu, Aichi, Shizuoka, Kanagawa
|
||||||
|
|
||||||
|
// Kansai & Chugoku (Stretching West)
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 32, 31, 28, 26, 25, 24, 23, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Shimane, Tottori, Hyogo, Kyoto, Shiga, Mie, Aichi, Shizuoka
|
||||||
|
[0, 0, 0, 0, 0, 35, 34, 33, 28, 27, 29, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Yamaguchi, Hiroshima, Okayama, Hyogo, Osaka, Nara, Mie
|
||||||
|
|
||||||
|
// Shikoku (Nestled under) & Wakayama
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 37, 36, 0, 30, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Kagawa, Tokushima, Wakayama
|
||||||
|
[0, 0, 0, 0, 0, 0, 38, 38, 39, 39, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Ehime, Kochi
|
||||||
|
|
||||||
|
// Kyushu (Connecting West)
|
||||||
|
[0, 0, 0, 41, 40, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Saga, Fukuoka, Oita
|
||||||
|
[0, 42, 42, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Nagasaki, Kumamoto, Oita
|
||||||
|
[0, 42, 43, 43, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Nagasaki, Kumamoto, Miyazaki
|
||||||
|
[0, 0, 46, 46, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Kagoshima, Miyazaki
|
||||||
|
[0, 0, 46, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Kagoshima
|
||||||
|
|
||||||
|
// Gap
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[47, 47, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Okinawa
|
||||||
|
[0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Okinawa
|
||||||
|
];
|
||||||
|
|
||||||
|
static const Map<int, String> prefectureNames = {
|
||||||
|
1: '北海道',
|
||||||
|
2: '青森県',
|
||||||
|
3: '岩手県',
|
||||||
|
4: '宮城県',
|
||||||
|
5: '秋田県',
|
||||||
|
6: '山形県',
|
||||||
|
7: '福島県',
|
||||||
|
8: '茨城県',
|
||||||
|
9: '栃木県',
|
||||||
|
10: '群馬県',
|
||||||
|
11: '埼玉県',
|
||||||
|
12: '千葉県',
|
||||||
|
13: '東京都',
|
||||||
|
14: '神奈川県',
|
||||||
|
15: '新潟県',
|
||||||
|
16: '富山県',
|
||||||
|
17: '石川県',
|
||||||
|
18: '福井県',
|
||||||
|
19: '山梨県',
|
||||||
|
20: '長野県',
|
||||||
|
21: '岐阜県',
|
||||||
|
22: '静岡県',
|
||||||
|
23: '愛知県',
|
||||||
|
24: '三重県',
|
||||||
|
25: '滋賀県',
|
||||||
|
26: '京都府',
|
||||||
|
27: '大阪府',
|
||||||
|
28: '兵庫県',
|
||||||
|
29: '奈良県',
|
||||||
|
30: '和歌山県',
|
||||||
|
31: '鳥取県',
|
||||||
|
32: '島根県',
|
||||||
|
33: '岡山県',
|
||||||
|
34: '広島県',
|
||||||
|
35: '山口県',
|
||||||
|
36: '徳島県',
|
||||||
|
37: '香川県',
|
||||||
|
38: '愛媛県',
|
||||||
|
39: '高知県',
|
||||||
|
40: '福岡県',
|
||||||
|
41: '佐賀県',
|
||||||
|
42: '長崎県',
|
||||||
|
43: '熊本県',
|
||||||
|
44: '大分県',
|
||||||
|
45: '宮崎県',
|
||||||
|
46: '鹿児島県',
|
||||||
|
47: '沖縄県',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Region grouping for visual rhythm (coloring)
|
||||||
|
// 1: Hokkaido, 2: Tohoku, 3: Kanto, 4: Chubu, 5: Kansai, 6: Chugoku, 7: Shikoku, 8: Kyushu/Okinawa
|
||||||
|
static const Map<int, String> regionNames = {
|
||||||
|
1: '北海道',
|
||||||
|
2: '東北',
|
||||||
|
3: '関東',
|
||||||
|
4: '中部',
|
||||||
|
5: '近畿',
|
||||||
|
6: '中国',
|
||||||
|
7: '四国',
|
||||||
|
8: '九州・沖縄',
|
||||||
|
};
|
||||||
|
|
||||||
|
static int getRegionId(int prefId) {
|
||||||
|
if (prefId == 1) return 1;
|
||||||
|
if (prefId >= 2 && prefId <= 7) return 2;
|
||||||
|
if (prefId >= 8 && prefId <= 14) return 3;
|
||||||
|
if (prefId >= 15 && prefId <= 23) return 4;
|
||||||
|
if (prefId >= 24 && prefId <= 30) return 5;
|
||||||
|
if (prefId >= 31 && prefId <= 35) return 6;
|
||||||
|
if (prefId >= 36 && prefId <= 39) return 7;
|
||||||
|
if (prefId >= 40 && prefId <= 47) return 8;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
part 'menu_settings.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 3)
|
||||||
|
class MenuSettings extends HiveObject {
|
||||||
|
@HiveField(0)
|
||||||
|
String title;
|
||||||
|
|
||||||
|
@HiveField(1)
|
||||||
|
bool includePhoto;
|
||||||
|
|
||||||
|
@HiveField(2)
|
||||||
|
bool includePoem;
|
||||||
|
|
||||||
|
@HiveField(3)
|
||||||
|
bool includeChart;
|
||||||
|
|
||||||
|
@HiveField(4)
|
||||||
|
bool includePrice;
|
||||||
|
|
||||||
|
@HiveField(5)
|
||||||
|
bool includeDate;
|
||||||
|
|
||||||
|
@HiveField(8)
|
||||||
|
bool? includeQr; // Nullable for compatibility
|
||||||
|
|
||||||
|
@HiveField(6)
|
||||||
|
String pdfSize; // 'a4', 'a5', 'b5'
|
||||||
|
|
||||||
|
@HiveField(7)
|
||||||
|
bool isMonochrome;
|
||||||
|
|
||||||
|
MenuSettings({
|
||||||
|
this.title = '',
|
||||||
|
this.includePhoto = true,
|
||||||
|
this.includePoem = true,
|
||||||
|
this.includeChart = true,
|
||||||
|
this.includePrice = true,
|
||||||
|
this.includeDate = true,
|
||||||
|
this.includeQr = false,
|
||||||
|
this.pdfSize = 'a4',
|
||||||
|
this.isMonochrome = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
MenuSettings copyWith({
|
||||||
|
String? title,
|
||||||
|
bool? includePhoto,
|
||||||
|
bool? includePoem,
|
||||||
|
bool? includeChart,
|
||||||
|
bool? includePrice,
|
||||||
|
bool? includeDate,
|
||||||
|
bool? includeQr,
|
||||||
|
String? pdfSize,
|
||||||
|
bool? isMonochrome,
|
||||||
|
}) {
|
||||||
|
return MenuSettings(
|
||||||
|
title: title ?? this.title,
|
||||||
|
includePhoto: includePhoto ?? this.includePhoto,
|
||||||
|
includePoem: includePoem ?? this.includePoem,
|
||||||
|
includeChart: includeChart ?? this.includeChart,
|
||||||
|
includePrice: includePrice ?? this.includePrice,
|
||||||
|
includeDate: includeDate ?? this.includeDate,
|
||||||
|
includeQr: includeQr ?? this.includeQr,
|
||||||
|
pdfSize: pdfSize ?? this.pdfSize,
|
||||||
|
isMonochrome: isMonochrome ?? this.isMonochrome,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'menu_settings.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class MenuSettingsAdapter extends TypeAdapter<MenuSettings> {
|
||||||
|
@override
|
||||||
|
final int typeId = 3;
|
||||||
|
|
||||||
|
@override
|
||||||
|
MenuSettings read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return MenuSettings(
|
||||||
|
title: fields[0] as String,
|
||||||
|
includePhoto: fields[1] as bool,
|
||||||
|
includePoem: fields[2] as bool,
|
||||||
|
includeChart: fields[3] as bool,
|
||||||
|
includePrice: fields[4] as bool,
|
||||||
|
includeDate: fields[5] as bool,
|
||||||
|
includeQr: fields[8] as bool?,
|
||||||
|
pdfSize: fields[6] as String,
|
||||||
|
isMonochrome: fields[7] as bool,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, MenuSettings obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(9)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.title)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.includePhoto)
|
||||||
|
..writeByte(2)
|
||||||
|
..write(obj.includePoem)
|
||||||
|
..writeByte(3)
|
||||||
|
..write(obj.includeChart)
|
||||||
|
..writeByte(4)
|
||||||
|
..write(obj.includePrice)
|
||||||
|
..writeByte(5)
|
||||||
|
..write(obj.includeDate)
|
||||||
|
..writeByte(8)
|
||||||
|
..write(obj.includeQr)
|
||||||
|
..writeByte(6)
|
||||||
|
..write(obj.pdfSize)
|
||||||
|
..writeByte(7)
|
||||||
|
..write(obj.isMonochrome);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is MenuSettingsAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,282 @@
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'schema/display_data.dart';
|
||||||
|
import 'schema/hidden_specs.dart';
|
||||||
|
import 'schema/user_data.dart';
|
||||||
|
import 'schema/gamification.dart';
|
||||||
|
import 'schema/metadata.dart';
|
||||||
|
import 'schema/item_type.dart';
|
||||||
|
|
||||||
|
export 'schema/display_data.dart';
|
||||||
|
export 'schema/hidden_specs.dart';
|
||||||
|
export 'schema/user_data.dart';
|
||||||
|
export 'schema/gamification.dart';
|
||||||
|
export 'schema/metadata.dart';
|
||||||
|
export 'schema/item_type.dart';
|
||||||
|
|
||||||
|
part 'sake_item.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 0)
|
||||||
|
class SakeItem extends HiveObject {
|
||||||
|
// --- Root Identity ---
|
||||||
|
@HiveField(0)
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
// --- New Hierarchical Data (Fields 20+) ---
|
||||||
|
@HiveField(20)
|
||||||
|
DisplayData? _displayData;
|
||||||
|
|
||||||
|
@HiveField(21)
|
||||||
|
HiddenSpecs? _hiddenSpecs;
|
||||||
|
|
||||||
|
@HiveField(22)
|
||||||
|
UserData? _userData;
|
||||||
|
|
||||||
|
@HiveField(23)
|
||||||
|
Gamification? _gamification;
|
||||||
|
|
||||||
|
@HiveField(24)
|
||||||
|
Metadata? _metadata;
|
||||||
|
|
||||||
|
@HiveField(25)
|
||||||
|
ItemType? _itemType;
|
||||||
|
|
||||||
|
// --- Legacy Fields (Deprecated: Kept for Migration) ---
|
||||||
|
@HiveField(1)
|
||||||
|
final String? legacyName;
|
||||||
|
|
||||||
|
@HiveField(2)
|
||||||
|
final String? legacyBrand;
|
||||||
|
|
||||||
|
@HiveField(3)
|
||||||
|
final String? legacyPrefecture;
|
||||||
|
|
||||||
|
@HiveField(4)
|
||||||
|
final String? legacyDescription;
|
||||||
|
|
||||||
|
@HiveField(5)
|
||||||
|
final String? legacyCatchCopy;
|
||||||
|
|
||||||
|
@HiveField(6)
|
||||||
|
final List<String>? legacyImagePaths;
|
||||||
|
|
||||||
|
@HiveField(7)
|
||||||
|
final double? legacySweetnessScore;
|
||||||
|
|
||||||
|
@HiveField(8)
|
||||||
|
final double? legacyBodyScore;
|
||||||
|
|
||||||
|
@HiveField(9)
|
||||||
|
final DateTime? legacyCreatedAt;
|
||||||
|
|
||||||
|
@HiveField(10)
|
||||||
|
final int? legacyConfidenceScore;
|
||||||
|
|
||||||
|
@HiveField(11)
|
||||||
|
final List<String>? legacyFlavorTags;
|
||||||
|
|
||||||
|
@HiveField(12)
|
||||||
|
final bool? legacyIsFavorite;
|
||||||
|
|
||||||
|
@HiveField(13)
|
||||||
|
final Map<String, int>? legacyTasteStats;
|
||||||
|
|
||||||
|
@HiveField(14)
|
||||||
|
final bool? legacyIsUserEdited;
|
||||||
|
|
||||||
|
@HiveField(15)
|
||||||
|
final int? legacyCostPrice;
|
||||||
|
|
||||||
|
@HiveField(16)
|
||||||
|
final int? legacyManualPrice;
|
||||||
|
|
||||||
|
@HiveField(17)
|
||||||
|
final double? legacyMarkup;
|
||||||
|
|
||||||
|
@HiveField(18)
|
||||||
|
final Map<String, int>? legacyPriceVariants;
|
||||||
|
|
||||||
|
SakeItem({
|
||||||
|
required this.id,
|
||||||
|
DisplayData? displayData,
|
||||||
|
HiddenSpecs? hiddenSpecs,
|
||||||
|
UserData? userData,
|
||||||
|
Gamification? gamification,
|
||||||
|
Metadata? metadata,
|
||||||
|
ItemType? itemType,
|
||||||
|
// Legacy params for migration compatibility (optional)
|
||||||
|
this.legacyName,
|
||||||
|
this.legacyBrand,
|
||||||
|
this.legacyPrefecture,
|
||||||
|
this.legacyDescription,
|
||||||
|
this.legacyCatchCopy,
|
||||||
|
this.legacyImagePaths,
|
||||||
|
this.legacySweetnessScore,
|
||||||
|
this.legacyBodyScore,
|
||||||
|
this.legacyCreatedAt,
|
||||||
|
this.legacyConfidenceScore,
|
||||||
|
this.legacyFlavorTags,
|
||||||
|
this.legacyIsFavorite,
|
||||||
|
this.legacyTasteStats,
|
||||||
|
this.legacyIsUserEdited,
|
||||||
|
this.legacyCostPrice,
|
||||||
|
this.legacyManualPrice,
|
||||||
|
this.legacyMarkup,
|
||||||
|
this.legacyPriceVariants,
|
||||||
|
}) {
|
||||||
|
_displayData = displayData;
|
||||||
|
_hiddenSpecs = hiddenSpecs;
|
||||||
|
_userData = userData;
|
||||||
|
_gamification = gamification;
|
||||||
|
_metadata = metadata;
|
||||||
|
_itemType = itemType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Smart Getters (Auto-Migration / Fallback) ---
|
||||||
|
DisplayData get displayData {
|
||||||
|
if (_displayData != null) return _displayData!;
|
||||||
|
// Fallback: Construct from Legacy
|
||||||
|
return DisplayData(
|
||||||
|
name: legacyName ?? 'Unknown',
|
||||||
|
brewery: legacyBrand ?? 'Unknown',
|
||||||
|
prefecture: legacyPrefecture ?? 'Unknown',
|
||||||
|
catchCopy: legacyCatchCopy,
|
||||||
|
imagePaths: legacyImagePaths ?? [],
|
||||||
|
rating: null, // Legacy didn't have rating explicit in display
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow setting for UI updates
|
||||||
|
set displayData(DisplayData val) {
|
||||||
|
_displayData = val;
|
||||||
|
save(); // Auto-save on set? Or just update memory. HiveObject usually requires save().
|
||||||
|
// Better to just update memory here.
|
||||||
|
}
|
||||||
|
|
||||||
|
HiddenSpecs get hiddenSpecs {
|
||||||
|
if (_hiddenSpecs != null) return _hiddenSpecs!;
|
||||||
|
return HiddenSpecs(
|
||||||
|
description: legacyDescription,
|
||||||
|
tasteStats: legacyTasteStats ?? {},
|
||||||
|
flavorTags: legacyFlavorTags ?? [],
|
||||||
|
sweetnessScore: legacySweetnessScore,
|
||||||
|
bodyScore: legacyBodyScore,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserData get userData {
|
||||||
|
if (_userData != null) return _userData!;
|
||||||
|
return UserData(
|
||||||
|
isFavorite: legacyIsFavorite ?? false,
|
||||||
|
isUserEdited: legacyIsUserEdited ?? false,
|
||||||
|
price: legacyManualPrice,
|
||||||
|
costPrice: legacyCostPrice,
|
||||||
|
markup: legacyMarkup ?? 3.0,
|
||||||
|
priceVariants: legacyPriceVariants,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Gamification get gamification {
|
||||||
|
if (_gamification != null) return _gamification!;
|
||||||
|
return Gamification(
|
||||||
|
ponPoints: 0, // Legacy didn't track
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Metadata get metadata {
|
||||||
|
if (_metadata != null) return _metadata!;
|
||||||
|
return Metadata(
|
||||||
|
createdAt: legacyCreatedAt ?? DateTime.now(),
|
||||||
|
aiConfidence: legacyConfidenceScore,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemType get itemType {
|
||||||
|
// Default to 'sake' for existing data
|
||||||
|
return _itemType ?? ItemType.sake;
|
||||||
|
}
|
||||||
|
|
||||||
|
set itemType(ItemType val) {
|
||||||
|
_itemType = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Migration Method ---
|
||||||
|
// Returns true if migration was performed (i.e., was legacy)
|
||||||
|
bool ensureMigrated() {
|
||||||
|
if (_displayData != null) return false; // Already new structure
|
||||||
|
|
||||||
|
// Create New Objects from Legacy Fields
|
||||||
|
_displayData = displayData; // Uses getter logic
|
||||||
|
_hiddenSpecs = hiddenSpecs;
|
||||||
|
_userData = userData;
|
||||||
|
_gamification = gamification;
|
||||||
|
_metadata = metadata;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CopyWith for Immutable Updates (Backend Compatible) ---
|
||||||
|
SakeItem copyWith({
|
||||||
|
String? name,
|
||||||
|
String? brand, // Maps to Brewery
|
||||||
|
String? prefecture,
|
||||||
|
String? description,
|
||||||
|
String? catchCopy,
|
||||||
|
List<String>? imagePaths,
|
||||||
|
double? sweetnessScore,
|
||||||
|
double? bodyScore,
|
||||||
|
int? confidenceScore, // Maps to aiConfidence
|
||||||
|
List<String>? flavorTags,
|
||||||
|
bool? isFavorite,
|
||||||
|
Map<String, int>? tasteStats,
|
||||||
|
bool? isUserEdited,
|
||||||
|
String? memo,
|
||||||
|
int? costPrice,
|
||||||
|
int? manualPrice, // Maps to price
|
||||||
|
double? markup,
|
||||||
|
Map<String, int>? priceVariants,
|
||||||
|
ItemType? itemType,
|
||||||
|
}) {
|
||||||
|
// Ensure we have current data structures
|
||||||
|
final currentDisplay = displayData;
|
||||||
|
final currentHidden = hiddenSpecs;
|
||||||
|
final currentUser = userData;
|
||||||
|
final currentMeta = metadata;
|
||||||
|
final currentGame = gamification;
|
||||||
|
|
||||||
|
return SakeItem(
|
||||||
|
id: id,
|
||||||
|
displayData: currentDisplay.copyWith(
|
||||||
|
name: name,
|
||||||
|
brewery: brand,
|
||||||
|
prefecture: prefecture,
|
||||||
|
catchCopy: catchCopy,
|
||||||
|
imagePaths: imagePaths,
|
||||||
|
),
|
||||||
|
hiddenSpecs: currentHidden.copyWith(
|
||||||
|
description: description,
|
||||||
|
tasteStats: tasteStats,
|
||||||
|
flavorTags: flavorTags,
|
||||||
|
sweetnessScore: sweetnessScore,
|
||||||
|
bodyScore: bodyScore,
|
||||||
|
),
|
||||||
|
userData: currentUser.copyWith(
|
||||||
|
isFavorite: isFavorite,
|
||||||
|
isUserEdited: isUserEdited,
|
||||||
|
memo: memo,
|
||||||
|
price: manualPrice,
|
||||||
|
costPrice: costPrice,
|
||||||
|
markup: markup,
|
||||||
|
priceVariants: priceVariants,
|
||||||
|
),
|
||||||
|
gamification: currentGame,
|
||||||
|
metadata: currentMeta.copyWith(
|
||||||
|
aiConfidence: confidenceScore,
|
||||||
|
),
|
||||||
|
itemType: itemType ?? this.itemType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Compact JSON for QR ecosystem
|
||||||
|
String toQrJson() {
|
||||||
|
return '{"id":"$id","n":"${displayData.name}","b":"${displayData.brewery}","p":"${displayData.prefecture ?? ""}","s":${hiddenSpecs.sweetnessScore ?? 0},"y":${hiddenSpecs.bodyScore ?? 0},"a":${hiddenSpecs.alcoholContent ?? 0}}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'sake_item.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class SakeItemAdapter extends TypeAdapter<SakeItem> {
|
||||||
|
@override
|
||||||
|
final int typeId = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
SakeItem read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return SakeItem(
|
||||||
|
id: fields[0] as String,
|
||||||
|
legacyName: fields[1] as String?,
|
||||||
|
legacyBrand: fields[2] as String?,
|
||||||
|
legacyPrefecture: fields[3] as String?,
|
||||||
|
legacyDescription: fields[4] as String?,
|
||||||
|
legacyCatchCopy: fields[5] as String?,
|
||||||
|
legacyImagePaths: (fields[6] as List?)?.cast<String>(),
|
||||||
|
legacySweetnessScore: fields[7] as double?,
|
||||||
|
legacyBodyScore: fields[8] as double?,
|
||||||
|
legacyCreatedAt: fields[9] as DateTime?,
|
||||||
|
legacyConfidenceScore: fields[10] as int?,
|
||||||
|
legacyFlavorTags: (fields[11] as List?)?.cast<String>(),
|
||||||
|
legacyIsFavorite: fields[12] as bool?,
|
||||||
|
legacyTasteStats: (fields[13] as Map?)?.cast<String, int>(),
|
||||||
|
legacyIsUserEdited: fields[14] as bool?,
|
||||||
|
legacyCostPrice: fields[15] as int?,
|
||||||
|
legacyManualPrice: fields[16] as int?,
|
||||||
|
legacyMarkup: fields[17] as double?,
|
||||||
|
legacyPriceVariants: (fields[18] as Map?)?.cast<String, int>(),
|
||||||
|
)
|
||||||
|
.._displayData = fields[20] as DisplayData?
|
||||||
|
.._hiddenSpecs = fields[21] as HiddenSpecs?
|
||||||
|
.._userData = fields[22] as UserData?
|
||||||
|
.._gamification = fields[23] as Gamification?
|
||||||
|
.._metadata = fields[24] as Metadata?
|
||||||
|
.._itemType = fields[25] as ItemType?;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, SakeItem obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(25)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.id)
|
||||||
|
..writeByte(20)
|
||||||
|
..write(obj._displayData)
|
||||||
|
..writeByte(21)
|
||||||
|
..write(obj._hiddenSpecs)
|
||||||
|
..writeByte(22)
|
||||||
|
..write(obj._userData)
|
||||||
|
..writeByte(23)
|
||||||
|
..write(obj._gamification)
|
||||||
|
..writeByte(24)
|
||||||
|
..write(obj._metadata)
|
||||||
|
..writeByte(25)
|
||||||
|
..write(obj._itemType)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.legacyName)
|
||||||
|
..writeByte(2)
|
||||||
|
..write(obj.legacyBrand)
|
||||||
|
..writeByte(3)
|
||||||
|
..write(obj.legacyPrefecture)
|
||||||
|
..writeByte(4)
|
||||||
|
..write(obj.legacyDescription)
|
||||||
|
..writeByte(5)
|
||||||
|
..write(obj.legacyCatchCopy)
|
||||||
|
..writeByte(6)
|
||||||
|
..write(obj.legacyImagePaths)
|
||||||
|
..writeByte(7)
|
||||||
|
..write(obj.legacySweetnessScore)
|
||||||
|
..writeByte(8)
|
||||||
|
..write(obj.legacyBodyScore)
|
||||||
|
..writeByte(9)
|
||||||
|
..write(obj.legacyCreatedAt)
|
||||||
|
..writeByte(10)
|
||||||
|
..write(obj.legacyConfidenceScore)
|
||||||
|
..writeByte(11)
|
||||||
|
..write(obj.legacyFlavorTags)
|
||||||
|
..writeByte(12)
|
||||||
|
..write(obj.legacyIsFavorite)
|
||||||
|
..writeByte(13)
|
||||||
|
..write(obj.legacyTasteStats)
|
||||||
|
..writeByte(14)
|
||||||
|
..write(obj.legacyIsUserEdited)
|
||||||
|
..writeByte(15)
|
||||||
|
..write(obj.legacyCostPrice)
|
||||||
|
..writeByte(16)
|
||||||
|
..write(obj.legacyManualPrice)
|
||||||
|
..writeByte(17)
|
||||||
|
..write(obj.legacyMarkup)
|
||||||
|
..writeByte(18)
|
||||||
|
..write(obj.legacyPriceVariants);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is SakeItemAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
part 'display_data.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 11)
|
||||||
|
class DisplayData extends HiveObject {
|
||||||
|
@HiveField(0)
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
@HiveField(1)
|
||||||
|
final String brewery;
|
||||||
|
|
||||||
|
@HiveField(2)
|
||||||
|
final String prefecture;
|
||||||
|
|
||||||
|
@HiveField(3)
|
||||||
|
final String? catchCopy;
|
||||||
|
|
||||||
|
@HiveField(4)
|
||||||
|
final List<String> imagePaths;
|
||||||
|
|
||||||
|
@HiveField(5)
|
||||||
|
final double? rating;
|
||||||
|
|
||||||
|
DisplayData({
|
||||||
|
required this.name,
|
||||||
|
required this.brewery,
|
||||||
|
required this.prefecture,
|
||||||
|
this.catchCopy,
|
||||||
|
required this.imagePaths,
|
||||||
|
this.rating,
|
||||||
|
});
|
||||||
|
|
||||||
|
DisplayData copyWith({
|
||||||
|
String? name,
|
||||||
|
String? brewery,
|
||||||
|
String? prefecture,
|
||||||
|
String? catchCopy,
|
||||||
|
List<String>? imagePaths,
|
||||||
|
double? rating,
|
||||||
|
}) {
|
||||||
|
return DisplayData(
|
||||||
|
name: name ?? this.name,
|
||||||
|
brewery: brewery ?? this.brewery,
|
||||||
|
prefecture: prefecture ?? this.prefecture,
|
||||||
|
catchCopy: catchCopy ?? this.catchCopy,
|
||||||
|
imagePaths: imagePaths ?? this.imagePaths,
|
||||||
|
rating: rating ?? this.rating,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'display_data.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class DisplayDataAdapter extends TypeAdapter<DisplayData> {
|
||||||
|
@override
|
||||||
|
final int typeId = 11;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DisplayData read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return DisplayData(
|
||||||
|
name: fields[0] as String,
|
||||||
|
brewery: fields[1] as String,
|
||||||
|
prefecture: fields[2] as String,
|
||||||
|
catchCopy: fields[3] as String?,
|
||||||
|
imagePaths: (fields[4] as List).cast<String>(),
|
||||||
|
rating: fields[5] as double?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, DisplayData obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(6)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.name)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.brewery)
|
||||||
|
..writeByte(2)
|
||||||
|
..write(obj.prefecture)
|
||||||
|
..writeByte(3)
|
||||||
|
..write(obj.catchCopy)
|
||||||
|
..writeByte(4)
|
||||||
|
..write(obj.imagePaths)
|
||||||
|
..writeByte(5)
|
||||||
|
..write(obj.rating);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is DisplayDataAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
part 'gamification.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 14)
|
||||||
|
class Gamification extends HiveObject {
|
||||||
|
@HiveField(0)
|
||||||
|
final int ponPoints;
|
||||||
|
|
||||||
|
@HiveField(1)
|
||||||
|
final String? sakeMbtiType;
|
||||||
|
|
||||||
|
@HiveField(2)
|
||||||
|
final String? rarityLevel;
|
||||||
|
|
||||||
|
Gamification({
|
||||||
|
this.ponPoints = 0,
|
||||||
|
this.sakeMbtiType,
|
||||||
|
this.rarityLevel,
|
||||||
|
});
|
||||||
|
|
||||||
|
Gamification copyWith({
|
||||||
|
int? ponPoints,
|
||||||
|
String? sakeMbtiType,
|
||||||
|
String? rarityLevel,
|
||||||
|
}) {
|
||||||
|
return Gamification(
|
||||||
|
ponPoints: ponPoints ?? this.ponPoints,
|
||||||
|
sakeMbtiType: sakeMbtiType ?? this.sakeMbtiType,
|
||||||
|
rarityLevel: rarityLevel ?? this.rarityLevel,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'gamification.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class GamificationAdapter extends TypeAdapter<Gamification> {
|
||||||
|
@override
|
||||||
|
final int typeId = 14;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Gamification read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return Gamification(
|
||||||
|
ponPoints: fields[0] as int,
|
||||||
|
sakeMbtiType: fields[1] as String?,
|
||||||
|
rarityLevel: fields[2] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, Gamification obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(3)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.ponPoints)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.sakeMbtiType)
|
||||||
|
..writeByte(2)
|
||||||
|
..write(obj.rarityLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is GamificationAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||