diff --git a/lib/providers/professionals_provider.dart b/lib/providers/professionals_provider.dart index 6e9282b..b5977db 100644 --- a/lib/providers/professionals_provider.dart +++ b/lib/providers/professionals_provider.dart @@ -31,6 +31,9 @@ class ProfessionalsProvider extends ChangeNotifier { } } + double? get patientLat => _locationLat; + double? get patientLng => _locationLng; + void setLocationContext({required String? city, required double lat, required double lng}) { if (city != null && city.isNotEmpty) _locationCity = city; _locationLat = lat; diff --git a/lib/ui/views/professionals_view.dart b/lib/ui/views/professionals_view.dart index b8491ec..8de559b 100644 --- a/lib/ui/views/professionals_view.dart +++ b/lib/ui/views/professionals_view.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'dart:math' as math; import 'package:flutter/material.dart'; +import 'package:prosapp_web_app/models/location_preferences.dart'; import 'package:prosapp_web_app/models/usuario_profesional.dart'; import 'package:prosapp_web_app/providers/professionals_provider.dart'; import 'package:prosapp_web_app/providers/theme_provider.dart'; @@ -127,6 +129,8 @@ class _ProfessionalsViewState extends State { cardBg: cardBg, border: border, compact: cols == 1, + patientLat: provider.patientLat, + patientLng: provider.patientLng, onTap: () async { final result = await NavigationService .navigateToFuture( @@ -152,12 +156,31 @@ class _ProfessionalsViewState extends State { // ── Card ───────────────────────────────────────────────────────────────────── +double? _haversineKm(double? lat1, double? lng1, double? lat2, double? lng2) { + if (lat1 == null || lng1 == null || lat2 == null || lng2 == null) return null; + if (lat2 == 0.0 && lng2 == 0.0) return null; + const r = 6371.0; + final dLat = (lat2 - lat1) * math.pi / 180; + final dLng = (lng2 - lng1) * math.pi / 180; + final a = math.sin(dLat / 2) * math.sin(dLat / 2) + + math.cos(lat1 * math.pi / 180) * math.cos(lat2 * math.pi / 180) * + math.sin(dLng / 2) * math.sin(dLng / 2); + return r * 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)); +} + +String _formatDistance(double km) { + if (km < 1) return '${(km * 1000).round()} m'; + return '${km.toStringAsFixed(1)} km'; +} + class _ProfCard extends StatelessWidget { final UsuarioProfesional data; final bool isDark; final Color cardBg; final Color border; final bool compact; + final double? patientLat; + final double? patientLng; final VoidCallback onTap; const _ProfCard({ @@ -167,6 +190,8 @@ class _ProfCard extends StatelessWidget { required this.border, required this.compact, required this.onTap, + this.patientLat, + this.patientLng, }); @override @@ -179,6 +204,12 @@ class _ProfCard extends StatelessWidget { final score = data.averageScore; final pm = data.professionalInfo.paymentMethods; + final profLat = data.professionalInfo.latitude; + final profLng = data.professionalInfo.longitude; + final distKm = _haversineKm(patientLat, patientLng, profLat, profLng); + final hasOffice = data.professionalInfo.locationPreferences != LocationPreferences.delivery; + final address = data.professionalInfo.address; + if (compact) { // Fila horizontal en pantallas pequeñas return Material( @@ -227,7 +258,27 @@ class _ProfCard extends StatelessWidget { style: TextStyle( fontSize: 11, color: textSecondary)), ]), + if (distKm != null) ...[ + const SizedBox(width: 8), + Icon(Icons.near_me_outlined, size: 11, color: const Color(0xFF42A4EF)), + const SizedBox(width: 2), + Text(_formatDistance(distKm), + style: const TextStyle(fontSize: 11, color: Color(0xFF42A4EF), fontWeight: FontWeight.w600)), + ], ]), + if (hasOffice && address.isNotEmpty) ...[ + const SizedBox(height: 3), + Row(children: [ + Icon(Icons.store_outlined, size: 11, color: textSecondary), + const SizedBox(width: 2), + Flexible( + child: Text(address, + style: TextStyle(fontSize: 11, color: textSecondary), + maxLines: 1, + overflow: TextOverflow.ellipsis), + ), + ]), + ], ], ), ), @@ -378,6 +429,44 @@ class _ProfCard extends StatelessWidget { ), ], + // Distancia al consultorio + if (distKm != null) ...[ + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.near_me_outlined, size: 11, color: Color(0xFF42A4EF)), + const SizedBox(width: 3), + Text(_formatDistance(distKm), + style: const TextStyle( + fontSize: 11, + color: Color(0xFF42A4EF), + fontWeight: FontWeight.w600)), + ], + ), + ], + + // Dirección del consultorio + if (hasOffice && address.isNotEmpty) ...[ + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.store_outlined, size: 11, color: textSecondary), + const SizedBox(width: 3), + Flexible( + child: Text( + address, + style: TextStyle(fontSize: 11, color: textSecondary), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + const Spacer(), // Métodos de pago