import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mileograph_flutter/objects/objects.dart'; class LegCard extends StatelessWidget { const LegCard({ super.key, required this.leg, this.showEditButton = true, }); final Leg leg; final bool showEditButton; @override Widget build(BuildContext context) { final routeSegments = _parseRouteSegments(leg.route); final textTheme = Theme.of(context).textTheme; return Card( child: ExpansionTile( tilePadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), leading: const Icon(Icons.train), title: Text('${leg.start} → ${leg.end}'), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(_formatDateTime(leg.beginTime)), if (leg.headcode.isNotEmpty) Text( 'Headcode: ${leg.headcode}', style: textTheme.labelSmall, ), if (leg.network.isNotEmpty) Text( leg.network, style: textTheme.labelSmall, ), ], ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${leg.mileage.toStringAsFixed(1)} mi', style: textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w700, ), ), if (leg.tripId != 0) ...[ const SizedBox(height: 2), Text( 'Trip #${leg.tripId}', style: textTheme.labelSmall, maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ], ), if (showEditButton) ...[ const SizedBox(width: 8), IconButton( tooltip: 'Edit entry', icon: const Icon(Icons.edit), visualDensity: VisualDensity.compact, padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 32, minHeight: 32), onPressed: () => context.push('/legs/edit/${leg.id}'), ), ], ], ), children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (leg.notes.isNotEmpty) ...[ Text('Notes', style: textTheme.titleSmall), const SizedBox(height: 4), Text(leg.notes), const SizedBox(height: 12), ], if (leg.locos.isNotEmpty) ...[ Text('Locos', style: textTheme.titleSmall), const SizedBox(height: 6), Wrap( spacing: 8, runSpacing: 8, children: _buildLocoChips(context, leg), ), const SizedBox(height: 12), ], if (routeSegments.isNotEmpty) ...[ Text('Route', style: textTheme.titleSmall), const SizedBox(height: 6), _buildRouteList(routeSegments), ], ], ), ), ], ), ); } String _formatDate(DateTime? date) { if (date == null) return ''; return '${date.year.toString().padLeft(4, '0')}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; } String _formatDateTime(DateTime date) { final dateStr = _formatDate(date); final timeStr = '${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; return '$dateStr · $timeStr'; } List _buildLocoChips(BuildContext context, Leg leg) { final theme = Theme.of(context); return leg.locos .map( (loco) => Chip( label: Text('${loco.locoClass} ${loco.number}'), avatar: const Icon(Icons.directions_railway, size: 16), backgroundColor: theme.colorScheme.surfaceContainerHighest, ), ) .toList(); } Widget _buildRouteList(List segments) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: segments .map( (segment) => Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Row( children: [ const Icon(Icons.circle, size: 10), const SizedBox(width: 8), Expanded(child: Text(segment)), ], ), ), ) .toList(), ); } List _parseRouteSegments(String route) { final trimmed = route.trim(); if (trimmed.isEmpty) return []; try { final decoded = jsonDecode(trimmed); if (decoded is List) { return decoded.map((e) => e.toString()).toList(); } } catch (_) {} if (trimmed.startsWith('[') && trimmed.endsWith(']')) { try { final replaced = trimmed.replaceAll("'", '"'); final decoded = jsonDecode(replaced); if (decoded is List) { return decoded.map((e) => e.toString()).toList(); } } catch (_) {} } if (trimmed.contains('->')) { return trimmed .split('->') .map((e) => e.trim()) .where((e) => e.isNotEmpty) .toList(); } if (trimmed.contains(',')) { return trimmed .split(',') .map((e) => e.trim()) .where((e) => e.isNotEmpty) .toList(); } return [trimmed]; } }