| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_map/flutter_map.dart';
- import 'package:latlong2/latlong.dart';
- import '../../core/i18n/app_localizations.dart';
- import '../../core/theme/app_colors_extension.dart';
- class TripRouteCard extends StatelessWidget {
- final String originLabel;
- final String destLabel;
- final double? originLat;
- final double? originLng;
- final double? destLat;
- final double? destLng;
- const TripRouteCard({
- super.key,
- required this.originLabel,
- required this.destLabel,
- this.originLat,
- this.originLng,
- this.destLat,
- this.destLng,
- });
- bool get _hasCoords =>
- originLat != null &&
- originLng != null &&
- destLat != null &&
- destLng != null &&
- (originLat! != 0 || originLng! != 0) &&
- (destLat! != 0 || destLng! != 0);
- LatLng get _origin => LatLng(originLat!, originLng!);
- LatLng get _dest => LatLng(destLat!, destLng!);
- @override
- Widget build(BuildContext context) {
- final colors = Theme.of(context).extension<AppColorsExtension>()!;
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- _buildTextCard(context, colors),
- if (_hasCoords) ...[const SizedBox(height: 20), _buildMapCard(colors)],
- ],
- );
- }
- Widget _buildTextCard(BuildContext context, AppColorsExtension colors) {
- return Column(
- children: [
- GestureDetector(
- onLongPress: () => _copyToClipboard(context, originLabel),
- child: _bubble(
- originLabel,
- Icons.trip_origin,
- colors.primary,
- colors,
- ),
- ),
- Padding(
- padding: const EdgeInsets.only(left: 9),
- child: Column(
- children: List.generate(
- 3,
- (_) => Padding(
- padding: const EdgeInsets.symmetric(vertical: 3),
- child: Container(
- width: 2,
- height: 2,
- decoration: BoxDecoration(
- color: colors.textPlaceholder,
- shape: BoxShape.circle,
- ),
- ),
- ),
- ),
- ),
- ),
- GestureDetector(
- onLongPress: () => _copyToClipboard(context, destLabel),
- child: _bubble(destLabel, Icons.location_on, colors.danger, colors),
- ),
- ],
- );
- }
- void _copyToClipboard(BuildContext context, String text) {
- final l10n = AppLocalizations.of(context);
- Clipboard.setData(ClipboardData(text: text));
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Center(child: Text(l10n.get('copiedToClipboard'))),
- duration: const Duration(seconds: 1),
- behavior: SnackBarBehavior.floating,
- width: 120,
- ),
- );
- }
- Widget _bubble(
- String label,
- IconData icon,
- Color color,
- AppColorsExtension colors,
- ) {
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
- decoration: BoxDecoration(
- color: color.withValues(alpha: 0.08),
- borderRadius: BorderRadius.circular(8),
- ),
- child: Row(
- children: [
- Icon(icon, size: 18, color: color),
- const SizedBox(width: 10),
- Expanded(
- child: Text(
- label,
- maxLines: 2,
- overflow: TextOverflow.ellipsis,
- style: TextStyle(
- fontSize: 14,
- fontWeight: FontWeight.w500,
- color: colors.textPrimary,
- ),
- ),
- ),
- ],
- ),
- );
- }
- Widget _buildMapCard(AppColorsExtension colors) {
- final dist = const Distance().as(LengthUnit.Kilometer, _origin, _dest);
- final mid = LatLng(
- (_origin.latitude + _dest.latitude) / 2,
- (_origin.longitude + _dest.longitude) / 2,
- );
- final distText = dist >= 1
- ? '${dist.toStringAsFixed(1)}km'
- : '${(dist * 1000).toInt()}m';
- return Container(
- height: 200,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(12),
- border: Border.all(color: colors.border, width: 0.5),
- ),
- clipBehavior: Clip.antiAlias,
- child: FlutterMap(
- options: MapOptions(
- initialCameraFit: CameraFit.bounds(
- bounds: LatLngBounds.fromPoints([_origin, _dest]),
- padding: const EdgeInsets.all(40),
- ),
- ),
- children: [
- TileLayer(
- urlTemplate:
- 'https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
- subdomains: const ['1', '2', '3', '4'],
- userAgentPackageName: 'com.amtxts.tboss_oa_module',
- ),
- PolylineLayer(
- polylines: [
- Polyline(
- points: [_origin, _dest],
- color: colors.primary.withValues(alpha: 0.8),
- strokeWidth: 3,
- ),
- ],
- ),
- MarkerLayer(
- markers: [
- Marker(
- point: _origin,
- width: 180,
- height: 32,
- child: _mapLabel(
- originLabel,
- Icons.trip_origin,
- colors.primary,
- colors.textPrimary,
- ),
- ),
- Marker(
- point: _dest,
- width: 180,
- height: 32,
- child: _mapLabel(
- destLabel,
- Icons.location_on,
- colors.danger,
- colors.textPrimary,
- ),
- ),
- Marker(
- point: mid,
- width: 80,
- height: 24,
- child: _distBadge(distText, colors),
- ),
- ],
- ),
- ],
- ),
- );
- }
- Widget _distBadge(String text, AppColorsExtension colors) {
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
- alignment: Alignment.center,
- decoration: BoxDecoration(
- color: colors.primary.withValues(alpha: 0.85),
- borderRadius: BorderRadius.circular(10),
- ),
- child: Text(
- text,
- textAlign: TextAlign.center,
- style: const TextStyle(
- fontSize: 10,
- color: Colors.white,
- fontWeight: FontWeight.w600,
- ),
- ),
- );
- }
- Widget _mapLabel(
- String text,
- IconData icon,
- Color iconColor,
- Color textColor,
- ) {
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(4),
- boxShadow: [
- BoxShadow(color: Colors.black.withValues(alpha: 0.2), blurRadius: 4),
- ],
- ),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Icon(icon, size: 12, color: iconColor),
- const SizedBox(width: 2),
- Flexible(
- child: Text(
- text,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: TextStyle(fontSize: 10, color: textColor),
- ),
- ),
- ],
- ),
- );
- }
- }
|