import 'dart:math' as math; import 'package:flutter/material.dart'; class UbahnTape extends StatelessWidget { const UbahnTape({ super.key, this.lat, this.lon, this.rotationDeg = -6, this.maxLinesShown = 1, this.stations = kViennaStationsSample, }); final double? lat; final double? lon; final double rotationDeg; final int maxLinesShown; final List stations; @override Widget build(BuildContext context) { final lines = _resolveLines(); final primary = lines.isNotEmpty ? (kUbahnLineColors[lines.first] ?? _kNeutral) : _kNeutral; final bg = primary.withValues(alpha: 0.30); return Transform.rotate( angle: rotationDeg * math.pi / 180, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(3), boxShadow: const [ BoxShadow( color: Color(0x33000000), blurRadius: 4, offset: Offset(0, 2), ), ], border: Border.all( color: primary.withValues(alpha: 0.35), width: 1, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 14, height: 14, alignment: Alignment.center, decoration: BoxDecoration( color: _U_BLUE, borderRadius: BorderRadius.circular(3), ), child: const Text( 'U', style: TextStyle( fontWeight: FontWeight.w800, fontSize: 10, height: 1.0, color: Colors.white, ), ), ), const SizedBox(width: 6), if (lines.isNotEmpty) ...lines.take(maxLinesShown).expand((line) => [ _LineChip(line: line), const SizedBox(width: 4), ]), ], ), ), ); } List _resolveLines() { if (lat == null || lon == null) return const []; final nearest = _nearestStation(stations, lat!, lon!); return nearest?.lines ?? const []; } } class _LineChip extends StatelessWidget { const _LineChip({required this.line}); final String line; @override Widget build(BuildContext context) { final color = kUbahnLineColors[line] ?? _kNeutral; final on = _onColor(color); return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(999), ), child: Text( line, style: TextStyle( color: on, fontSize: 10, fontWeight: FontWeight.w700, height: 1.0, letterSpacing: 0.2, ), ), ); } } Color _onColor(Color bg) { return ThemeData.estimateBrightnessForColor(bg) == Brightness.dark ? Colors.white : const Color(0xFF111111); } const _U_BLUE = Color(0xFF1E88E5); // Vienna U sign-ish blue const _kNeutral = Color(0xFF9E9E9E); // Official-ish line colors const Map kUbahnLineColors = { 'U1': Color(0xFFE20613), // red 'U2': Color(0xFFA762A3), // purple 'U3': Color(0xFFF29400), // orange 'U4': Color(0xFF009640), // green 'U5': Color(0xFF63318F), // violet (future) 'U6': Color(0xFF8D5B2D), // brown }; class UbahnStation { const UbahnStation(this.name, this.lat, this.lon, this.lines); final String name; final double lat; final double lon; final List lines; // e.g., ['U1','U3'] } UbahnStation? _nearestStation( List stations, double lat, double lon, ) { if (stations.isEmpty) return null; UbahnStation best = stations.first; double bestD = _haversine(best.lat, best.lon, lat, lon); for (var i = 1; i < stations.length; i++) { final s = stations[i]; final d = _haversine(s.lat, s.lon, lat, lon); if (d < bestD) { best = s; bestD = d; } } return best; } double _haversine(double lat1, double lon1, double lat2, double lon2) { const R = 6371000.0; final dLat = (lat2 - lat1) * (math.pi / 180.0); final dLon = (lon2 - lon1) * (math.pi / 180.0); final a = math.sin(dLat / 2) * math.sin(dLat / 2) + math.cos(lat1 * (math.pi / 180.0)) * math.cos(lat2 * (math.pi / 180.0)) * math.sin(dLon / 2) * math.sin(dLon / 2); final c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)); return R * c; } // Compact central sample; swap in full dataset when ready const List kViennaStationsSample = [ UbahnStation('Stephansplatz', 48.2084, 16.3731, ['U1', 'U3']), UbahnStation('Karlsplatz', 48.2000, 16.3690, ['U1', 'U2', 'U4']), UbahnStation('Schwedenplatz', 48.2111, 16.3776, ['U1', 'U4']), UbahnStation('Praterstern', 48.2169, 16.3909, ['U1', 'U2']), UbahnStation('Schottenring', 48.2152, 16.3720, ['U2', 'U4']), UbahnStation('Volkstheater', 48.2078, 16.3604, ['U2', 'U3']), UbahnStation('Museumsquartier', 48.2026, 16.3614, ['U2']), UbahnStation('Westbahnhof', 48.1967, 16.3378, ['U3', 'U6']), UbahnStation('Wien Mitte/Landstraße', 48.2070, 16.3834, ['U3', 'U4']), UbahnStation('Spittelau', 48.2409, 16.3585, ['U4', 'U6']), UbahnStation('Längenfeldgasse', 48.1848, 16.3299, ['U4', 'U6']), UbahnStation('Erdberg', 48.1907, 16.4196, ['U3']), UbahnStation('Kaisermühlen VIC', 48.2348, 16.4130, ['U1']), UbahnStation('Floridsdorf', 48.2570, 16.4030, ['U6']), UbahnStation('Ottakring', 48.2120, 16.3080, ['U3']), ];