chore: guard DevMenu in release build, clean up Phase/TODO comments
- DevMenu: kReleaseModeのときonTap=nullでリリースビルドから完全無効化 - Phase N マーカーを全ファイルから削除(実装済みのため歴史的コメントを除去) - analysis_cache_service TODOを具体的な記述に改善 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
426697403e
commit
b6163e8efe
|
|
@ -40,7 +40,7 @@ class SakeItem extends HiveObject {
|
||||||
@HiveField(25)
|
@HiveField(25)
|
||||||
ItemType? _itemType;
|
ItemType? _itemType;
|
||||||
|
|
||||||
// --- Phase 1: Draft Mode Fields (Fields 26-27) ---
|
// --- Draft Mode Fields (Fields 26-27) ---
|
||||||
@HiveField(26)
|
@HiveField(26)
|
||||||
bool? _isPendingAnalysis; // true = AI解析待ち, false/null = 通常登録済み
|
bool? _isPendingAnalysis; // true = AI解析待ち, false/null = 通常登録済み
|
||||||
|
|
||||||
|
|
@ -110,7 +110,7 @@ class SakeItem extends HiveObject {
|
||||||
Gamification? gamification,
|
Gamification? gamification,
|
||||||
Metadata? metadata,
|
Metadata? metadata,
|
||||||
ItemType? itemType,
|
ItemType? itemType,
|
||||||
// Phase 1: Draft Mode params
|
// Draft Mode params
|
||||||
bool? isPendingAnalysis,
|
bool? isPendingAnalysis,
|
||||||
String? draftPhotoPath,
|
String? draftPhotoPath,
|
||||||
// Legacy params for migration compatibility (optional)
|
// Legacy params for migration compatibility (optional)
|
||||||
|
|
@ -216,7 +216,7 @@ class SakeItem extends HiveObject {
|
||||||
_itemType = val;
|
_itemType = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 1: Draft Mode Getters/Setters
|
// Draft Mode Getters/Setters
|
||||||
bool get isPendingAnalysis => _isPendingAnalysis ?? false;
|
bool get isPendingAnalysis => _isPendingAnalysis ?? false;
|
||||||
|
|
||||||
set isPendingAnalysis(bool val) {
|
set isPendingAnalysis(bool val) {
|
||||||
|
|
@ -274,7 +274,7 @@ class SakeItem extends HiveObject {
|
||||||
String? riceVariety,
|
String? riceVariety,
|
||||||
String? yeast,
|
String? yeast,
|
||||||
String? manufacturingYearMonth,
|
String? manufacturingYearMonth,
|
||||||
// Phase 1: Draft Mode
|
// Draft Mode
|
||||||
bool? isPendingAnalysis,
|
bool? isPendingAnalysis,
|
||||||
String? draftPhotoPath,
|
String? draftPhotoPath,
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -322,7 +322,7 @@ class SakeItem extends HiveObject {
|
||||||
aiConfidence: confidenceScore,
|
aiConfidence: confidenceScore,
|
||||||
),
|
),
|
||||||
itemType: itemType ?? this.itemType,
|
itemType: itemType ?? this.itemType,
|
||||||
// Phase 1: Draft Mode
|
// Draft Mode
|
||||||
isPendingAnalysis: isPendingAnalysis ?? this.isPendingAnalysis,
|
isPendingAnalysis: isPendingAnalysis ?? this.isPendingAnalysis,
|
||||||
draftPhotoPath: draftPhotoPath ?? this.draftPhotoPath,
|
draftPhotoPath: draftPhotoPath ?? this.draftPhotoPath,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class DisplayModeNotifier extends Notifier<String> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String build() {
|
String build() {
|
||||||
// Phase 1 Optimization: Access box directly
|
// SplashScreenで開済みのboxに直接アクセス
|
||||||
_box = Hive.box<UserProfile>('user_profile');
|
_box = Hive.box<UserProfile>('user_profile');
|
||||||
_profile = _box.get('current_user') ?? UserProfile(createdAt: DateTime.now());
|
_profile = _box.get('current_user') ?? UserProfile(createdAt: DateTime.now());
|
||||||
return _profile.displayMode;
|
return _profile.displayMode;
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class MenuOrderedIdsNotifier extends Notifier<List<String>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. PDF Settings Providers (Phase 4)
|
// 4. PDF Settings Providers
|
||||||
// Note: We use Notifiers for complex logic, but simple StateProviders (Riverpod 2.0 style) are fine here.
|
// Note: We use Notifiers for complex logic, but simple StateProviders (Riverpod 2.0 style) are fine here.
|
||||||
// Actually, Riverpod recommended is Notifier, but StateProvider is still available.
|
// Actually, Riverpod recommended is Notifier, but StateProvider is still available.
|
||||||
// Let's use simple class-based Notifiers for 2.0 strictness if needed, or simple State for brevity.
|
// Let's use simple class-based Notifiers for 2.0 strictness if needed, or simple State for brevity.
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class UserProfileNotifier extends Notifier<UserProfile> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
UserProfile build() {
|
UserProfile build() {
|
||||||
// Phase 1 Optimization: Access box directly as it's guaranteed to be open by SplashScreen
|
// SplashScreenで開済みのboxに直接アクセス
|
||||||
_box = Hive.box<UserProfile>('user_profile');
|
_box = Hive.box<UserProfile>('user_profile');
|
||||||
|
|
||||||
// Return existing profile or create default
|
// Return existing profile or create default
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ class _CameraScreenState extends ConsumerState<CameraScreen> with SingleTickerPr
|
||||||
|
|
||||||
final imagePath = compressedPath;
|
final imagePath = compressedPath;
|
||||||
|
|
||||||
// Save to Gallery (Public) - Phase 4: Data Safety
|
// Save to Gallery (Public)
|
||||||
try {
|
try {
|
||||||
await Gal.putImage(imagePath);
|
await Gal.putImage(imagePath);
|
||||||
debugPrint('Saved to Gallery: $imagePath');
|
debugPrint('Saved to Gallery: $imagePath');
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,6 @@ class DevMenuScreen extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
// NOTE: 言語・テーマ選択は設定画面(SoulScreen)に移動済み
|
|
||||||
// このメニューはUI実験・デバッグ機能のみ
|
|
||||||
|
|
||||||
const ListTile(
|
const ListTile(
|
||||||
leading: Icon(LucideIcons.flaskConical),
|
leading: Icon(LucideIcons.flaskConical),
|
||||||
title: Text('UI実験'),
|
title: Text('UI実験'),
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ class HomeScreen extends ConsumerWidget {
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Phase 1: 未解析Draft通知バナー
|
// 未解析Draft通知バナー
|
||||||
const PendingAnalysisBanner(),
|
const PendingAnalysisBanner(),
|
||||||
|
|
||||||
if (!isMenuMode && hasItems)
|
if (!isMenuMode && hasItems)
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class PdfPreviewScreen extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
// Phase 4: Watch new PDF Settings
|
// PDF Settings を監視
|
||||||
final isPortrait = ref.watch(pdfIsPortraitProvider);
|
final isPortrait = ref.watch(pdfIsPortraitProvider);
|
||||||
final density = ref.watch(pdfDensityProvider);
|
final density = ref.watch(pdfDensityProvider);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ class _SakeDetailScreenState extends ConsumerState<SakeDetailScreen> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appColors = Theme.of(context).extension<AppColors>()!;
|
final appColors = Theme.of(context).extension<AppColors>()!;
|
||||||
|
|
||||||
// スマートレコメンド (Phase 1-8 Enhanced)
|
// スマートレコメンド
|
||||||
final allSakeAsync = ref.watch(allSakeItemsProvider);
|
final allSakeAsync = ref.watch(allSakeItemsProvider);
|
||||||
final allSake = allSakeAsync.asData?.value ?? [];
|
final allSake = allSakeAsync.asData?.value ?? [];
|
||||||
|
|
||||||
|
|
@ -209,7 +209,7 @@ class _SakeDetailScreenState extends ConsumerState<SakeDetailScreen> {
|
||||||
|
|
||||||
const SizedBox(height: 48),
|
const SizedBox(height: 48),
|
||||||
|
|
||||||
// Related Items 3D Carousel (Phase 1-8 Enhanced)
|
// Related Items 3D Carousel
|
||||||
if (_sake.itemType != ItemType.set) ...[ // Hide for Set Items
|
if (_sake.itemType != ItemType.set) ...[ // Hide for Set Items
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -273,7 +273,7 @@ class _SakeDetailScreenState extends ConsumerState<SakeDetailScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Phase 2-3: Business Pricing Section (Extracted)
|
// Business Pricing Section
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: SakePricingSection(
|
child: SakePricingSection(
|
||||||
sake: _sake,
|
sake: _sake,
|
||||||
|
|
|
||||||
|
|
@ -128,9 +128,7 @@ class AnalysisCacheService {
|
||||||
/// - 30日経過したキャッシュは削除
|
/// - 30日経過したキャッシュは削除
|
||||||
/// - 日本酒の仕様変更(リニューアル)に対応
|
/// - 日本酒の仕様変更(リニューアル)に対応
|
||||||
static Future<void> cleanupExpired() async {
|
static Future<void> cleanupExpired() async {
|
||||||
// TODO: 実装(Phase 3)
|
// TODO: キャッシュにタイムスタンプを追加し、30日以上古いエントリを削除する
|
||||||
// - キャッシュにタイムスタンプを追加
|
|
||||||
// - 30日以上古いエントリを削除
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import 'gemini_service.dart';
|
||||||
|
|
||||||
/// Draft(解析待ちアイテム)管理サービス
|
/// Draft(解析待ちアイテム)管理サービス
|
||||||
///
|
///
|
||||||
/// Phase 1緊急対応: オフライン時に撮影した写真を一時保存し、
|
/// オフライン時に撮影した写真を一時保存し、
|
||||||
/// オンライン復帰時に自動解析する機能を提供します。
|
/// オンライン復帰時に自動解析する機能を提供します。
|
||||||
///
|
///
|
||||||
/// 使用フロー:
|
/// 使用フロー:
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class GeminiService {
|
||||||
|
|
||||||
/// 画像リストから日本酒ラベルを解析
|
/// 画像リストから日本酒ラベルを解析
|
||||||
Future<SakeAnalysisResult> analyzeSakeLabel(List<String> imagePaths, {bool forceRefresh = false}) async {
|
Future<SakeAnalysisResult> analyzeSakeLabel(List<String> imagePaths, {bool forceRefresh = false}) async {
|
||||||
// Force use of client-side prompt to ensure Schema consistency (Phase 1 Fix)
|
// クライアント側プロンプトでスキーマの一貫性を保証
|
||||||
const prompt = '''
|
const prompt = '''
|
||||||
あなたは日本酒の専門家(ソムリエ)です。
|
あなたは日本酒の専門家(ソムリエ)です。
|
||||||
添付の画像(日本酒のラベル)を分析し、以下のJSON形式で情報を抽出してください。
|
添付の画像(日本酒のラベル)を分析し、以下のJSON形式で情報を抽出してください。
|
||||||
|
|
@ -151,7 +151,7 @@ $extractedText
|
||||||
}
|
}
|
||||||
_lastApiCallTime = DateTime.now();
|
_lastApiCallTime = DateTime.now();
|
||||||
|
|
||||||
// 2. 画像をBase64変換 (Phase 4: Images already compressed at capture)
|
// 2. 画像をBase64変換(撮影時に圧縮済み)
|
||||||
List<String> base64Images = [];
|
List<String> base64Images = [];
|
||||||
for (final path in imagePaths) {
|
for (final path in imagePaths) {
|
||||||
// Read already-compressed images directly (compressed at capture time)
|
// Read already-compressed images directly (compressed at capture time)
|
||||||
|
|
@ -203,7 +203,7 @@ $extractedText
|
||||||
|
|
||||||
final result = SakeAnalysisResult.fromJson(data);
|
final result = SakeAnalysisResult.fromJson(data);
|
||||||
|
|
||||||
// Phase 3 Validation: Check for Schema Compliance
|
// スキーマ準拠チェック
|
||||||
if (result.tasteStats.isEmpty ||
|
if (result.tasteStats.isEmpty ||
|
||||||
result.tasteStats.values.every((v) => v == 0)) {
|
result.tasteStats.values.every((v) => v == 0)) {
|
||||||
debugPrint('⚠️ WARNING: AI returned empty or zero taste stats. This item will not form a valid chart.');
|
debugPrint('⚠️ WARNING: AI returned empty or zero taste stats. This item will not form a valid chart.');
|
||||||
|
|
@ -299,7 +299,7 @@ $extractedText
|
||||||
// Prepare Content
|
// Prepare Content
|
||||||
final contentParts = <Part>[TextPart(promptText)];
|
final contentParts = <Part>[TextPart(promptText)];
|
||||||
for (var path in imagePaths) {
|
for (var path in imagePaths) {
|
||||||
// Phase 4: Images already compressed at capture time
|
// 撮影時に圧縮済み
|
||||||
final bytes = await File(path).readAsBytes();
|
final bytes = await File(path).readAsBytes();
|
||||||
contentParts.add(DataPart('image/jpeg', bytes));
|
contentParts.add(DataPart('image/jpeg', bytes));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
/// ネットワーク接続状態を管理するサービス
|
/// ネットワーク接続状態を管理するサービス
|
||||||
///
|
///
|
||||||
/// Phase 1緊急対応: オフライン対応のための基盤サービス
|
|
||||||
/// - オンライン/オフライン判定
|
/// - オンライン/オフライン判定
|
||||||
/// - 接続状態変化の監視
|
/// - 接続状態変化の監視
|
||||||
class NetworkService {
|
class NetworkService {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import '../screens/pending_analysis_screen.dart';
|
||||||
|
|
||||||
/// 未解析Draft(解析待ちアイテム)通知バナー
|
/// 未解析Draft(解析待ちアイテム)通知バナー
|
||||||
///
|
///
|
||||||
/// Phase 1: オフライン対応機能の一部
|
|
||||||
/// ホーム画面上部に表示され、未解析のDraftがある場合にのみ表示されます。
|
/// ホーム画面上部に表示され、未解析のDraftがある場合にのみ表示されます。
|
||||||
///
|
///
|
||||||
/// タップすると [PendingAnalysisScreen] へ遷移し、一括解析が可能です。
|
/// タップすると [PendingAnalysisScreen] へ遷移し、一括解析が可能です。
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class _SakeDetailSpecsState extends State<SakeDetailSpecs> {
|
||||||
late final TextEditingController _manufacturingController;
|
late final TextEditingController _manufacturingController;
|
||||||
|
|
||||||
// Unused in UI currently but reserved
|
// Unused in UI currently but reserved
|
||||||
// TODO: Phase X で甘味度・ボディスコアの編集UIを追加する予定
|
// TODO: 甘味度・ボディスコアの編集UIを追加する予定
|
||||||
/*
|
/*
|
||||||
late final TextEditingController _sweetnessController;
|
late final TextEditingController _sweetnessController;
|
||||||
late final TextEditingController _bodyController;
|
late final TextEditingController _bodyController;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:lucide_icons/lucide_icons.dart';
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
|
@ -72,7 +73,7 @@ class _OtherSettingsSectionState extends ConsumerState<OtherSettingsSection> {
|
||||||
leading: Icon(LucideIcons.info, color: appColors.iconDefault),
|
leading: Icon(LucideIcons.info, color: appColors.iconDefault),
|
||||||
title: Text('アプリバージョン', style: TextStyle(color: appColors.textPrimary)),
|
title: Text('アプリバージョン', style: TextStyle(color: appColors.textPrimary)),
|
||||||
subtitle: Text(_appVersion, style: TextStyle(color: appColors.textSecondary)),
|
subtitle: Text(_appVersion, style: TextStyle(color: appColors.textSecondary)),
|
||||||
onTap: () {
|
onTap: kReleaseMode ? null : () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_devTapCount++;
|
_devTapCount++;
|
||||||
if (_devTapCount >= AppConstants.devModeTapCount) {
|
if (_devTapCount >= AppConstants.devModeTapCount) {
|
||||||
|
|
@ -83,8 +84,7 @@ class _OtherSettingsSectionState extends ConsumerState<OtherSettingsSection> {
|
||||||
MaterialPageRoute(builder: (context) => const DevMenuScreen()),
|
MaterialPageRoute(builder: (context) => const DevMenuScreen()),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Optional: Small feedback
|
HapticFeedback.lightImpact();
|
||||||
HapticFeedback.lightImpact();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue