import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mileograph_flutter/objects/objects.dart'; import 'package:mileograph_flutter/services/data_service.dart'; import 'package:provider/provider.dart'; class ProfilePage extends StatefulWidget { const ProfilePage({super.key}); @override State createState() => _ProfilePageState(); } class _ProfilePageState extends State { bool _initialised = false; @override void didChangeDependencies() { super.didChangeDependencies(); if (_initialised) return; _initialised = true; _refreshAwards(); } Future _refreshAwards() { return context.read().fetchBadgeAwards(); } @override Widget build(BuildContext context) { final data = context.watch(); final awards = data.badgeAwards; final loading = data.isBadgeAwardsLoading; return Scaffold( appBar: AppBar( title: const Text('Badges'), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { final navigator = Navigator.of(context); if (navigator.canPop()) { navigator.pop(); } else { context.go('/'); } }, ), ), body: RefreshIndicator( onRefresh: _refreshAwards, child: ListView( padding: const EdgeInsets.all(16), children: [ if (loading && awards.isEmpty) const Center( child: Padding( padding: EdgeInsets.symmetric(vertical: 24.0), child: CircularProgressIndicator(), ), ) else if (awards.isEmpty) const Padding( padding: EdgeInsets.symmetric(vertical: 12.0), child: Text('No badges awarded yet.'), ) else ...awards.map((award) => _buildAwardCard(context, award)), ], ), ), ); } Widget _buildAwardCard(BuildContext context, BadgeAward award) { final badgeName = _formatBadgeName(award.badgeCode); final tier = award.badgeTier.isNotEmpty ? award.badgeTier[0].toUpperCase() + award.badgeTier.substring(1) : ''; final tierIcon = _buildTierIcon(award.badgeTier); final scope = _scopeToShow(award); return Card( child: Padding( padding: const EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ if (tierIcon != null) ...[ tierIcon, const SizedBox(width: 8), ], Expanded( child: Text( '$badgeName • $tier', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w700, ), ), ), if (award.awardedAt != null) Text( _formatAwardDate(award.awardedAt!), style: Theme.of(context).textTheme.bodySmall, ), ], ), if (scope != null && scope.isNotEmpty) ...[ const SizedBox(height: 6), Text( scope, style: Theme.of(context).textTheme.bodyMedium, ), ], if (award.loco != null) ...[ const SizedBox(height: 8), _buildLocoInfo(context, award.loco!), ], ], ), ), ); } Widget _buildLocoInfo(BuildContext context, LocoSummary loco) { final lines = []; final classNum = [ if (loco.locoClass.isNotEmpty) loco.locoClass, if (loco.number.isNotEmpty) loco.number, ].join(' '); if (classNum.isNotEmpty) lines.add(classNum); if ((loco.name ?? '').isNotEmpty) lines.add(loco.name!); if ((loco.livery ?? '').isNotEmpty) lines.add(loco.livery!); if ((loco.location ?? '').isNotEmpty) lines.add(loco.location!); return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Icon(Icons.train, size: 20), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: lines.map((line) { return Text( line, style: Theme.of(context).textTheme.bodyMedium, ); }).toList(), ), ), ], ); } String _formatBadgeName(String code) { if (code.isEmpty) return 'Badge'; const known = { 'class_clearance': 'Class Clearance', 'loco_clearance': 'Loco Clearance', }; final lower = code.toLowerCase(); if (known.containsKey(lower)) return known[lower]!; final parts = code.split(RegExp(r'[_\\s]+')).where((p) => p.isNotEmpty); return parts .map((p) => p[0].toUpperCase() + p.substring(1).toLowerCase()) .join(' '); } String _formatAwardDate(DateTime date) { final y = date.year.toString().padLeft(4, '0'); final m = date.month.toString().padLeft(2, '0'); final d = date.day.toString().padLeft(2, '0'); return '$y-$m-$d'; } Widget? _buildTierIcon(String tier) { final lower = tier.toLowerCase(); Color? color; switch (lower) { case 'bronze': color = const Color(0xFFCD7F32); break; case 'silver': color = const Color(0xFFC0C0C0); break; case 'gold': color = const Color(0xFFFFD700); break; } if (color == null) return null; return Icon(Icons.emoji_events, color: color); } String? _scopeToShow(BadgeAward award) { final scope = award.scopeValue?.trim() ?? ''; if (scope.isEmpty) return null; final code = award.badgeCode.toLowerCase(); if (code == 'loco_clearance') { // Hide numeric loco IDs; loco details are shown separately. if (int.tryParse(scope) != null) return null; } return scope; } }