import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../providers/ui_experiment_provider.dart'; import '../providers/theme_provider.dart'; import '../services/analysis_cache_service.dart'; import '../widgets/settings/language_selector.dart'; // Language (Hidden) import 'package:hive_flutter/hive_flutter.dart'; import '../models/sake_item.dart'; import '../providers/sake_list_provider.dart'; import '../services/gemini_service.dart'; class DevMenuScreen extends ConsumerWidget { const DevMenuScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final experiment = ref.watch(uiExperimentProvider); final colorVariant = ref.watch(colorVariantProvider); return Scaffold( appBar: AppBar( title: const Text('🔬 開発者メニュー'), ), body: ListView( children: [ // Hidden Language Selector const LanguageSelector(), const Divider(), // ===== A/B Test: Color Theme ===== const ListTile( leading: Icon(LucideIcons.palette), title: Text('カラーテーマ'), subtitle: Text('2つのテーマを切り替えて比較'), ), Card( margin: const EdgeInsets.all(16), child: Column( children: [ RadioListTile( secondary: const Text('🎨', style: TextStyle(fontSize: 24)), title: const Text('Theme A: 和紙×墨×琥珀'), subtitle: const Text('日本酒の世界観を反映した洗練された配色'), value: 'washi_sumi_kohaku', groupValue: colorVariant, onChanged: (value) { if (value != null) { ref.read(userProfileProvider.notifier).setColorVariant(value); } }, ), const Divider(), RadioListTile( secondary: const Text('🔵', style: TextStyle(fontSize: 24)), title: const Text('Theme B: Current'), subtitle: const Text('既存のPosimai Blueベースのテーマ'), value: 'current', groupValue: colorVariant, onChanged: (value) { if (value != null) { ref.read(userProfileProvider.notifier).setColorVariant(value); } }, ), ], ), ), const Divider(), const ListTile( leading: Icon(LucideIcons.flaskConical), title: Text('UI実験'), subtitle: Text('新しいデザインのテスト'), ), Card( margin: const EdgeInsets.all(16), child: Column( children: [ SwitchListTile( secondary: const Text('📱', style: TextStyle(fontSize: 24)), title: const Text('グリッド3列表示'), subtitle: Text('瓶型の縦長カード (現在: ${experiment.gridColumns}列)'), value: experiment.gridColumns == 3, onChanged: (val) => ref.read(uiExperimentProvider.notifier) .setGridColumns(val ? 3 : 2), ), const Divider(), SwitchListTile( secondary: const Text('✨', style: TextStyle(fontSize: 24)), title: const Text('FABバウンス'), subtitle: Text('ぷるんとした動き (現在: ${experiment.fabAnimation == 'bounce' ? 'ON' : 'OFF'})'), value: experiment.fabAnimation == 'bounce', onChanged: (val) => ref.read(uiExperimentProvider.notifier) .setFabAnimation(val ? 'bounce' : 'rotate'), ), const Divider(), SwitchListTile( secondary: const Text('🎨', style: TextStyle(fontSize: 24)), title: const Text('地図お試しカラー'), subtitle: Text('地方ごとに色分け (現在: ${experiment.isMapColorful ? 'ON' : 'OFF'})'), value: experiment.isMapColorful, onChanged: (val) => ref.read(uiExperimentProvider.notifier) .setMapColorful(val), ), const Divider(), SwitchListTile( secondary: const Text('📝', style: TextStyle(fontSize: 24)), title: const Text('一覧テキスト表示'), subtitle: Text('写真のみ表示モード (現在: ${experiment.showGridText ? 'ON' : 'OFF'})'), value: experiment.showGridText, onChanged: (val) => ref.read(uiExperimentProvider.notifier) .setShowGridText(val), ), const Divider(), SwitchListTile( secondary: const Text('🏅', style: TextStyle(fontSize: 24)), title: const Text('バッジアイコン化'), subtitle: Text('絵文字の代わりにアイコンを使用 (現在: ${experiment.useBadgeIcons ? 'ON' : 'OFF'})'), value: experiment.useBadgeIcons, onChanged: (val) => ref.read(uiExperimentProvider.notifier) .setUseBadgeIcons(val), ), ], ), ), const Divider(), const Divider(), // Batch Repair Section ListTile( leading: const Icon(LucideIcons.hammer, color: Colors.blue), title: const Text('データ修復 (再解析)'), subtitle: const Text('データの欠損があるアイテムをAIで再解析します'), onTap: () => _runBatchAnalysis(context, ref), ), ListTile( leading: const Icon(LucideIcons.database, color: Colors.orange), title: const Text('AI解析キャッシュをクリア'), subtitle: FutureBuilder( future: AnalysisCacheService.getCacheSize(), builder: (context, snapshot) { final count = snapshot.data ?? 0; return Text('$count件のキャッシュを削除します'); }, ), onTap: () async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('確認'), content: const Text( 'AI解析キャッシュをクリアしますか?\n' '\n' '同じ日本酒を再解析する場合、APIを再度呼び出します。', ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('キャンセル'), ), TextButton( onPressed: () => Navigator.pop(context, true), child: const Text('クリア'), ), ], ), ); if (confirmed == true) { await AnalysisCacheService.clearAll(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('✅ キャッシュをクリアしました')), ); } } }, ), ], ), ); } Future _runBatchAnalysis(BuildContext context, WidgetRef ref) async { final allItems = ref.read(rawSakeListItemsProvider).asData?.value ?? []; if (allItems.isEmpty) return; // Filter items that need repair (e.g., empty stats) final targets = allItems.where((item) { final stats = item.hiddenSpecs.sakeTasteStats; final isStatsEmpty = stats.aroma == 0 && stats.bitterness == 0 && stats.sweetness == 0 && stats.acidity == 0 && stats.body == 0; // Also check for mismatch/missing new keys if possible, but empty check is safest proxy for now. return isStatsEmpty || item.metadata.aiConfidence == null; }).toList(); if (targets.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('修復が必要なデータは見つかりませんでした')), ); return; } final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('データ修復の実行'), content: Text( '${targets.length}件のデータの再解析を行います。\n' '時間がかかりますが、実行しますか?\n(API制限に注意してください)', ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('キャンセル'), ), FilledButton( onPressed: () => Navigator.pop(context, true), child: const Text('実行'), ), ], ), ); if (confirmed != true) return; // Show Progress Dialog if (!context.mounted) return; showDialog( context: context, barrierDismissible: false, builder: (ctx) => const Center(child: CircularProgressIndicator()), ); int successCount = 0; int failCount = 0; final box = Hive.box('sake_items'); try { final gemini = GeminiService(); for (final item in targets) { if (item.displayData.imagePaths.isEmpty) continue; try { // Force Refresh Analysis final result = await gemini.analyzeSakeLabel(item.displayData.imagePaths, forceRefresh: true); final updated = item.copyWith( name: result.name ?? item.displayData.name, brand: result.brand ?? item.displayData.brewery, prefecture: result.prefecture ?? item.displayData.prefecture, description: result.description ?? item.hiddenSpecs.description, catchCopy: result.catchCopy ?? item.displayData.catchCopy, confidenceScore: result.confidenceScore, flavorTags: result.flavorTags.isNotEmpty ? result.flavorTags : item.hiddenSpecs.flavorTags, tasteStats: result.tasteStats.isNotEmpty ? result.tasteStats : item.hiddenSpecs.tasteStats, // New Fields specificDesignation: result.type ?? item.hiddenSpecs.type, alcoholContent: result.alcoholContent ?? item.hiddenSpecs.alcoholContent, polishingRatio: result.polishingRatio ?? item.hiddenSpecs.polishingRatio, sakeMeterValue: result.sakeMeterValue ?? item.hiddenSpecs.sakeMeterValue, riceVariety: result.riceVariety ?? item.hiddenSpecs.riceVariety, yeast: result.yeast ?? item.hiddenSpecs.yeast, manufacturingYearMonth: result.manufacturingYearMonth ?? item.hiddenSpecs.manufacturingYearMonth, ); await box.put(item.key, updated); successCount++; // Wait to prevent rate limit await Future.delayed(const Duration(seconds: 2)); } catch (e) { debugPrint('Failed to analyze ${item.displayData.name}: $e'); failCount++; } } } finally { if (context.mounted) { Navigator.pop(context); // Close Progress ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('完了: 成功 $successCount件 / 失敗 $failCount件')), ); } } } }