245 lines
8.3 KiB
Dart
245 lines
8.3 KiB
Dart
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import '../../providers/theme_provider.dart';
|
||
import 'package:google_fonts/google_fonts.dart';
|
||
import '../contextual_help_icon.dart';
|
||
import '../common/pressable.dart';
|
||
import '../../theme/app_colors.dart';
|
||
|
||
class LevelTitleCard extends ConsumerWidget {
|
||
const LevelTitleCard({super.key});
|
||
|
||
@override
|
||
Widget build(BuildContext context, WidgetRef ref) {
|
||
final userProfile = ref.watch(userProfileProvider);
|
||
final totalExp = userProfile.totalExp;
|
||
final appColors = Theme.of(context).extension<AppColors>()!;
|
||
|
||
final level = userProfile.level;
|
||
final title = userProfile.title;
|
||
final progress = userProfile.nextLevelProgress;
|
||
final expToNext = userProfile.expToNextLevel;
|
||
|
||
return Pressable(
|
||
child: Container(
|
||
width: double.infinity,
|
||
padding: const EdgeInsets.all(20),
|
||
decoration: BoxDecoration(
|
||
color: Theme.of(context).cardColor,
|
||
borderRadius: BorderRadius.circular(20),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withValues(alpha: 0.05),
|
||
blurRadius: 10,
|
||
offset: const Offset(0, 4),
|
||
),
|
||
],
|
||
border: Border.all(
|
||
color: Theme.of(context).dividerColor.withValues(alpha: 0.1),
|
||
),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
if (userProfile.nickname != null && userProfile.nickname!.isNotEmpty) ...[
|
||
Text(
|
||
userProfile.nickname!,
|
||
style: TextStyle(fontSize: 13, color: appColors.textSecondary, letterSpacing: 0.3),
|
||
),
|
||
const SizedBox(height: 6),
|
||
],
|
||
// 「現在の称号」ラベル行 + Lv.バッジを同行右寄せ
|
||
Row(
|
||
children: [
|
||
Text(
|
||
'現在の称号',
|
||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||
fontWeight: FontWeight.w500,
|
||
color: appColors.textSecondary,
|
||
letterSpacing: 0.5,
|
||
),
|
||
),
|
||
const SizedBox(width: 4),
|
||
ContextualHelpIcon(
|
||
title: 'レベルと称号について',
|
||
customContent: _buildLevelHelpContent(context),
|
||
),
|
||
const Spacer(),
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: appColors.brandPrimary.withValues(alpha: 0.1),
|
||
borderRadius: BorderRadius.circular(10),
|
||
border: Border.all(color: appColors.brandPrimary.withValues(alpha: 0.3)),
|
||
),
|
||
child: Text(
|
||
'Lv.$level',
|
||
style: TextStyle(
|
||
fontWeight: FontWeight.w900,
|
||
fontSize: 14,
|
||
color: appColors.brandPrimary,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 6),
|
||
// 称号名(1行・フォントサイズ縮小・overflow防止)
|
||
Text(
|
||
title,
|
||
style: GoogleFonts.zenOldMincho(
|
||
fontSize: 26,
|
||
fontWeight: FontWeight.bold,
|
||
color: appColors.brandPrimary,
|
||
height: 1.2,
|
||
),
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
const SizedBox(height: 20),
|
||
|
||
// Progress Bar (animated 0 → actual value on mount)
|
||
TweenAnimationBuilder<double>(
|
||
tween: Tween<double>(begin: 0.0, end: progress),
|
||
duration: const Duration(milliseconds: 900),
|
||
curve: Curves.easeOutCubic,
|
||
builder: (context, value, _) {
|
||
return ClipRRect(
|
||
borderRadius: BorderRadius.circular(4),
|
||
child: LinearProgressIndicator(
|
||
value: value,
|
||
minHeight: 8,
|
||
backgroundColor: appColors.divider,
|
||
valueColor: AlwaysStoppedAnimation<Color>(appColors.brandPrimary),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
const SizedBox(height: 8),
|
||
|
||
// EXP Text
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
'Total EXP: $totalExp',
|
||
style: TextStyle(fontSize: 12, color: appColors.textSecondary),
|
||
),
|
||
Text(
|
||
expToNext > 0 ? '次のレベルまで: ${expToNext}exp' : 'Max Level',
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
color: appColors.brandPrimary,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
), // Container
|
||
); // Pressable
|
||
}
|
||
|
||
static Widget _buildLevelHelpContent(BuildContext context) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'レベルの上げ方',
|
||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
'日本酒を1本登録するごとに 10 EXP 獲得できます。\nメニューを作成するとボーナスが入ることも!',
|
||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||
height: 1.6,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
'称号一覧',
|
||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
_buildLevelTable(context),
|
||
],
|
||
);
|
||
}
|
||
|
||
static Widget _buildLevelTable(BuildContext context) {
|
||
// Level data from LevelCalculator
|
||
final levels = [
|
||
{'level': 1, 'requiredExp': 0, 'title': '見習い'},
|
||
{'level': 2, 'requiredExp': 10, 'title': '歩き飲み'},
|
||
{'level': 5, 'requiredExp': 50, 'title': '嗜み人'},
|
||
{'level': 10, 'requiredExp': 100, 'title': '呑兵衛'},
|
||
{'level': 20, 'requiredExp': 200, 'title': '酒豪'},
|
||
{'level': 30, 'requiredExp': 300, 'title': '利き酒師'},
|
||
{'level': 50, 'requiredExp': 500, 'title': '日本酒伝道師'},
|
||
{'level': 100, 'requiredExp': 1000, 'title': 'ポンシュマスター'},
|
||
];
|
||
|
||
return Container(
|
||
decoration: BoxDecoration(
|
||
color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: SingleChildScrollView(
|
||
scrollDirection: Axis.horizontal,
|
||
child: DataTable(
|
||
headingRowHeight: 40,
|
||
dataRowMinHeight: 32,
|
||
dataRowMaxHeight: 32,
|
||
columns: [
|
||
DataColumn(
|
||
label: Text(
|
||
'Lv',
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
color: Theme.of(context).colorScheme.onSurface,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
DataColumn(
|
||
label: Text(
|
||
'称号',
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
color: Theme.of(context).colorScheme.onSurface,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
DataColumn(
|
||
label: Text(
|
||
'必要EXP',
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
color: Theme.of(context).colorScheme.onSurface,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
rows: levels.map((levelData) {
|
||
return DataRow(
|
||
cells: [
|
||
DataCell(Text((levelData['level'] as int).toString(), style: const TextStyle(fontSize: 12))),
|
||
DataCell(Text(levelData['title'] as String, style: const TextStyle(fontSize: 12))),
|
||
DataCell(Text((levelData['requiredExp'] as int).toString(), style: const TextStyle(fontSize: 12))),
|
||
],
|
||
);
|
||
}).toList(),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|