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;
|
||||
}
|
||||