fix: Add missing badge check after AI sake registration

## Bug Fix
- Badge "初めての一歩" was not unlocking when adding first sake item
- Root cause: GamificationService.checkAndUnlockBadges() was never called in camera_screen.dart

## Changes
- Import gamification_service.dart
- Call checkAndUnlockBadges(ref) after adding sake item to Hive
- Display newly unlocked badges in success SnackBar
- Extend SnackBar duration when badges are unlocked (4s -> 6s)

## User Impact
- Users will now see badge unlock notifications: "バッジ獲得: 初めての一歩 🍶"
- All badge unlock conditions (first_step, regional, collector, flavor) now work correctly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ponshu Developer 2026-01-31 08:22:23 +09:00
parent b600579123
commit 5410c78c6c
1 changed files with 45 additions and 22 deletions

View File

@ -11,6 +11,7 @@ import 'package:gal/gal.dart';
import '../services/gemini_service.dart';
import '../services/image_compression_service.dart'; // Phase 4 Added
import '../services/gamification_service.dart'; // Badge check
import '../widgets/analyzing_dialog.dart';
import '../models/sake_item.dart';
import '../theme/app_theme.dart';
@ -386,9 +387,12 @@ class _CameraScreenState extends ConsumerState<CameraScreen> with SingleTickerPr
// Award EXP
final userProfileState = ref.read(userProfileProvider);
final prevLevel = userProfileState.level;
await ref.read(userProfileProvider.notifier).updateTotalExp(userProfileState.totalExp + 10);
// Check and unlock badges
final newBadges = await GamificationService.checkAndUnlockBadges(ref);
// Refetch updated state for level comparison
final updatedProfile = ref.read(userProfileProvider);
final newLevel = updatedProfile.level;
@ -408,28 +412,49 @@ class _CameraScreenState extends ConsumerState<CameraScreen> with SingleTickerPr
// Close Camera Screen (Return to Home)
Navigator.of(context).pop();
// Success Message (with EXP/Level Up info)
// Success Message (with EXP/Level Up/Badge info)
final List<Widget> messageWidgets = [
Text('${sakeItem.displayData.name} を登録しました!'),
const SizedBox(height: 4),
Row(
children: [
const Icon(LucideIcons.sparkles, color: Colors.yellow, size: 16),
const SizedBox(width: 8),
Text(
'経験値 +10 GET! ${isLevelUp ? " (Level UP!)" : ""}',
style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.yellowAccent),
),
],
),
];
// Add badge notifications
if (newBadges.isNotEmpty) {
messageWidgets.add(const SizedBox(height: 8));
for (var badge in newBadges) {
messageWidgets.add(
Row(
children: [
Text(badge.icon, style: const TextStyle(fontSize: 16)),
const SizedBox(width: 8),
Text(
'バッジ獲得: ${badge.name}',
style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.greenAccent),
),
],
),
);
}
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${sakeItem.displayData.name} を登録しました!'),
const SizedBox(height: 4),
Row(
children: [
const Icon(LucideIcons.sparkles, color: Colors.yellow, size: 16),
const SizedBox(width: 8),
Text(
'経験値 +10 GET! ${isLevelUp ? " (Level UP!)" : ""}',
style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.yellowAccent),
),
],
),
],
children: messageWidgets,
),
duration: const Duration(seconds: 4), // Longer display for level up
duration: Duration(seconds: newBadges.isNotEmpty ? 6 : 4), // Longer for badges
),
);
@ -793,12 +818,10 @@ class _ExposureSliderPainter extends CustomPainter {
// Draw knob
canvas.drawCircle(Offset(trackX, knobY), 6, knobPaint);
}
}
@override
} @override
bool shouldRepaint(_ExposureSliderPainter oldDelegate) {
return oldDelegate.currentValue != currentValue ||
oldDelegate.minValue != minValue ||
oldDelegate.maxValue != maxValue;
}
}
}