ponshu-room-lite/lib/widgets/gamification/badge_case.dart

209 lines
9.2 KiB
Dart
Raw Normal View History

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../providers/theme_provider.dart';
import '../../providers/ui_experiment_provider.dart';
import '../contextual_help_icon.dart';
import '../../theme/app_colors.dart';
class BadgeCase extends ConsumerWidget {
const BadgeCase({super.key});
static const List<Map<String, dynamic>> _badges = [
// Activity Badges (登録数)
{'id': 'first_step', 'name': '初めての一歩', 'emoji': '🍶', 'icon': LucideIcons.footprints, 'desc': '最初の1本を登録'},
{'id': 'collector_10', 'name': '愛好家', 'emoji': '🎉', 'icon': LucideIcons.star, 'desc': '10本以上登録'},
{'id': 'collector_50', 'name': 'コレクター', 'emoji': '📚', 'icon': LucideIcons.award, 'desc': '50本以上登録'},
{'id': 'collector_100', 'name': 'レジェンド', 'emoji': '👑', 'icon': LucideIcons.crown, 'desc': '100本以上登録'},
// Regional Badges (地域制覇)
{'id': 'regional_tohoku', 'name': '東北制覇', 'emoji': '👹', 'icon': LucideIcons.snowflake, 'desc': '東北6県の日本酒を登録'},
{'id': 'regional_kanto', 'name': '関東制覇', 'emoji': '🗻', 'icon': LucideIcons.building2, 'desc': '関東7都県の日本酒を登録'},
{'id': 'regional_kansai', 'name': '関西制覇', 'emoji': '🏯', 'icon': LucideIcons.landmark, 'desc': '関西6府県の日本酒を登録'},
// Flavor Badges (味覚)
{'id': 'flavor_dry', 'name': '辛口党', 'emoji': '🌶️', 'icon': LucideIcons.flame, 'desc': '辛口(+5以上)を10本登録'},
{'id': 'flavor_sweet', 'name': '甘口党', 'emoji': '🍯', 'icon': LucideIcons.candy, 'desc': '甘口(-3以下)を10本登録'},
{'id': 'flavor_aromatic', 'name': '香りの貴族', 'emoji': '🌸', 'icon': LucideIcons.flower, 'desc': '華やか(80以上)を10本登録'},
];
@override
Widget build(BuildContext context, WidgetRef ref) {
final userProfile = ref.watch(userProfileProvider);
final unlocked = userProfile.unlockedBadges.toSet();
final useIcons = ref.watch(uiExperimentProvider).useBadgeIcons;
final appColors = Theme.of(context).extension<AppColors>()!;
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Theme.of(context).dividerColor.withValues(alpha: 0.1),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
children: [
Text(
'バッジケース',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 4),
ContextualHelpIcon(
title: 'バッジについて',
customContent: _buildHelpContent(context, useIcons),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: appColors.brandPrimary.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: appColors.brandPrimary.withValues(alpha: 0.4),
),
),
child: Text(
'${unlocked.length} / ${_badges.length}',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: appColors.brandPrimary,
),
),
),
],
),
const SizedBox(height: 16),
Wrap(
spacing: 12,
runSpacing: 12,
children: _badges.map((badge) {
final isUnlocked = unlocked.contains(badge['id']);
return Tooltip(
message: '${badge['name']}\n${badge['desc']}',
triggerMode: TooltipTriggerMode.tap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: 64,
height: 80,
child: Column(
children: [
Container(
width: 50,
height: 50,
alignment: Alignment.center,
decoration: BoxDecoration(
color: isUnlocked
? appColors.brandPrimary.withValues(alpha: 0.2)
: appColors.surfaceSubtle,
shape: BoxShape.circle,
border: Border.all(
color: isUnlocked
? appColors.brandPrimary
: appColors.divider,
width: 2.5,
),
boxShadow: isUnlocked ? [
BoxShadow(
color: appColors.brandPrimary.withValues(alpha: 0.3),
blurRadius: 10,
spreadRadius: 1,
)
] : [],
),
child: isUnlocked
? (useIcons
? Icon(badge['icon'], color: appColors.brandPrimary, size: 24)
: Text(badge['emoji'], style: const TextStyle(fontSize: 24)))
: Icon(LucideIcons.lock, color: appColors.iconSubtle, size: 20),
),
const SizedBox(height: 4),
Text(
badge['name'],
style: TextStyle(
fontSize: 10,
fontWeight: isUnlocked ? FontWeight.bold : FontWeight.normal,
color: isUnlocked
? appColors.textPrimary
: appColors.textTertiary,
),
overflow: TextOverflow.ellipsis,
),
],
),
),
);
}).toList(),
),
],
),
);
}
Widget _buildHelpContent(BuildContext context, bool useIcons) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('バッジは特定の条件を達成すると獲得できます。\n\n【活動バッジ】'),
..._buildBadgeHelpRows(context, useIcons, ['first_step', 'collector_10', 'collector_50', 'collector_100']),
const SizedBox(height: 12),
const Text('【地域バッジ】'),
..._buildBadgeHelpRows(context, useIcons, ['regional_tohoku', 'regional_kanto', 'regional_kansai']),
const SizedBox(height: 12),
const Text('【味覚バッジ】'),
..._buildBadgeHelpRows(context, useIcons, ['flavor_dry', 'flavor_sweet', 'flavor_aromatic']),
const SizedBox(height: 16),
const Text('バッジを集めて、日本酒マスターを目指しましょう!'),
],
);
}
List<Widget> _buildBadgeHelpRows(BuildContext context, bool useIcons, List<String> ids) {
final appColors = Theme.of(context).extension<AppColors>()!;
// If badge doesn't exist, skip it safely
final validIds = ids.where((id) => _badges.any((b) => b['id'] == id));
return validIds.map((id) {
final badge = _badges.firstWhere((b) => b['id'] == id);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
SizedBox(
width: 24,
child: useIcons
? Icon(badge['icon'], size: 16, color: appColors.brandPrimary)
: Text(badge['emoji'], style: const TextStyle(fontSize: 16))
),
const SizedBox(width: 8),
Expanded(
child: RichText(
text: TextSpan(
style: Theme.of(context).textTheme.bodyMedium,
children: [
TextSpan(text: '${badge['name']}: ', style: const TextStyle(fontWeight: FontWeight.bold)),
TextSpan(text: badge['desc']),
],
),
),
),
],
),
);
}).toList();
}
}