import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:geolocator/geolocator.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import '../constants/app_colors.dart'; class GoogleMapScreen extends StatefulWidget { const GoogleMapScreen({super.key}); @override State createState() => _GoogleMapScreenState(); } class _GoogleMapScreenState extends State { late GoogleMapController mapController; final Set _markers = {}; Set _polylines = {}; LatLng? _currentLocation; LatLng? _destination; bool _isGettingLocation = true; bool _isRequestingPermission = true; final TextEditingController _searchController = TextEditingController(); List> _placeSuggestions = []; bool _isSearching = false; @override void initState() { super.initState(); _requestLocationPermission(); } Future _requestLocationPermission() async { try { print('šŸ” Solicitando permissƵes de localização...'); // Verificar status atual das permissƵes Map statuses = await [ Permission.location, Permission.locationWhenInUse, Permission.locationAlways, ].request(); bool locationGranted = (statuses[Permission.location]?.isGranted == true) || (statuses[Permission.locationWhenInUse]?.isGranted == true); if (locationGranted) { print('āœ… PermissƵes de localização concedidas'); setState(() { _isRequestingPermission = false; }); _getCurrentLocation(); } else { print('āŒ PermissƵes de localização negadas'); setState(() { _isRequestingPermission = false; }); _showLocationPermissionDialog(); } } catch (e) { print('āŒ Erro ao solicitar permissƵes: $e'); _showSnackBar('Erro ao solicitar permissƵes de localização', Colors.red); } } void _showLocationPermissionDialog() { showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return AlertDialog( backgroundColor: AppColors.backgroundGrey, title: const Text( 'PermissĆ£o de Localização', style: TextStyle( color: AppColors.white, fontWeight: FontWeight.bold, ), ), content: const Text( 'Este aplicativo precisa acessar sua localização para mostrar rotas de caminhada e calcular distĆ¢ncias. Por favor, habilite a localização nas configuraƧƵes do seu dispositivo.', style: TextStyle(color: AppColors.white), ), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); _openAppSettings(); }, child: const Text( 'Abrir ConfiguraƧƵes', style: TextStyle(color: AppColors.coral), ), ), TextButton( onPressed: () { Navigator.of(context).pop(); Navigator.of(context).pop(); // Volta para a tela anterior }, child: const Text( 'Cancelar', style: TextStyle(color: AppColors.white), ), ), ], ); }, ); } Future _openAppSettings() async { try { await openAppSettings(); } catch (e) { print('āŒ Erro ao abrir configuraƧƵes: $e'); _showSnackBar('NĆ£o foi possĆ­vel abrir as configuraƧƵes', Colors.red); } } Future _getCurrentLocation() async { try { print('šŸ” Iniciando busca de localização...'); // Verificar permissƵes de localização LocationPermission permission = await Geolocator.checkPermission(); print('šŸ“ PermissĆ£o atual: $permission'); if (permission == LocationPermission.denied) { print('āŒ PermissĆ£o negada, solicitando...'); permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { _showSnackBar( 'PermissĆ£o de localização negada. Habilite nas configuraƧƵes.', AppColors.coral, ); return; } } if (permission == LocationPermission.deniedForever) { _showSnackBar( 'PermissĆ£o de localização permanentemente negada. Habilite nas configuraƧƵes do app.', AppColors.coral, ); return; } // Obter localização atual print('šŸ›°ļø Buscando localização atual...'); Position position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high, timeLimit: const Duration(seconds: 10), ); print( 'āœ… Localização encontrada: ${position.latitude}, ${position.longitude}', ); setState(() { _currentLocation = LatLng(position.latitude, position.longitude); _isGettingLocation = false; }); // Adicionar marcador da localização atual _addCurrentLocationMarker(); // Mover cĆ¢mera para localização atual if (mounted && _currentLocation != null) { mapController.animateCamera( CameraUpdate.newCameraPosition( CameraPosition(target: _currentLocation!, zoom: 15.0), ), ); } } catch (e) { print('āŒ Erro ao obter localização: $e'); setState(() { _isGettingLocation = false; }); _showSnackBar('Erro ao obter localização: ${e.toString()}', Colors.red); } } void _addCurrentLocationMarker() { if (_currentLocation != null) { print('šŸ“ Adicionando marcador de localização atual'); // Remover marcador anterior se existir _markers.removeWhere( (marker) => marker.markerId.value == 'current_location', ); // Adicionar novo marcador _markers.add( Marker( markerId: const MarkerId('current_location'), position: _currentLocation!, infoWindow: const InfoWindow(title: 'Sua Localização'), icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue), ), ); setState(() {}); } } Future _searchPlaces(String query) async { if (query.isEmpty) { setState(() { _placeSuggestions = []; }); return; } setState(() { _isSearching = true; }); try { // Usar Google Places API para busca real baseada na localização atual final String url = 'https://maps.googleapis.com/maps/api/place/autocomplete/json' '?input=${Uri.encodeComponent(query)}' '&location=${_currentLocation?.latitude ?? -23.5505},${_currentLocation?.longitude ?? -46.6333}' '&radius=30000' // 30km de raio '&language=pt_BR' '®ion=br' '&components=country:br' '&strictbounds=true' // ForƧa busca dentro da Ć”rea '&key=AIzaSyCk84rxmF044cxKLABf55rEKHDqOcyoV5k'; print('šŸ” Buscando lugares: $url'); print( 'šŸ“ Baseado na localização: ${_currentLocation?.latitude ?? -23.5505}, ${_currentLocation?.longitude ?? -46.6333}', ); final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { final data = json.decode(response.body); if (data['status'] == 'OK') { final predictions = data['predictions'] as List; setState(() { _placeSuggestions = predictions.map((prediction) { return { 'description': prediction['description'], 'place_id': prediction['place_id'], 'structured_formatting': prediction['structured_formatting'], }; }).toList(); }); print( 'āœ… ${_placeSuggestions.length} lugares encontrados próximos Ć  sua localização', ); } else { print( 'āŒ Erro na API: ${data['status']} - ${data['error_message'] ?? ''}', ); } } else { print('āŒ Erro HTTP: ${response.statusCode}'); } } catch (e) { print('āŒ Erro na busca: $e'); } finally { setState(() { _isSearching = false; }); } } Future _selectPlace(Map place) async { try { print('šŸ“ Selecionado: ${place['description']}'); // Obter detalhes do lugar final String detailsUrl = 'https://maps.googleapis.com/maps/api/place/details/json' '?place_id=${place['place_id']}' '&fields=geometry' '&key=AIzaSyCk84rxmF044cxKLABf55rEKHDqOcyoV5k'; final response = await http.get(Uri.parse(detailsUrl)); if (response.statusCode == 200) { final data = json.decode(response.body); if (data['status'] == 'OK') { final location = data['result']['geometry']['location']; final destination = LatLng(location['lat'], location['lng']); setState(() { _destination = destination; _placeSuggestions = []; // Limpar sugestƵes }); _addDestinationMarker(destination, place['description']); await _getDirections(); } } } catch (e) { print('āŒ Erro ao selecionar lugar: $e'); _showSnackBar('Erro ao selecionar destino', Colors.red); } } void _addDestinationMarker(LatLng location, String name) { print('šŸ“ Adicionando marcador de destino: $name'); setState(() { // Remover marcador de destino anterior se existir _markers.removeWhere((marker) => marker.markerId.value == 'destination'); // Adicionar novo marcador de destino _markers.add( Marker( markerId: const MarkerId('destination'), position: location, infoWindow: InfoWindow(title: name), icon: BitmapDescriptor.defaultMarkerWithHue( BitmapDescriptor.hueAzure, ), ), ); }); print('āœ… Marcador de destino adicionado: $name'); } Future _getDirections() async { if (_currentLocation == null || _destination == null) { print('āŒ Localização ou destino nulo'); return; } try { print('šŸ›£ļø Buscando rota real com Google Directions API...'); // Usar Google Directions API para obter rota realista final String url = 'https://maps.googleapis.com/maps/api/directions/json' '?origin=${_currentLocation!.latitude},${_currentLocation!.longitude}' '&destination=${_destination!.latitude},${_destination!.longitude}' '&mode=walking' '&language=pt_BR' '&key=AIzaSyCk84rxmF044cxKLABf55rEKHDqOcyoV5k'; print('🌐 URL da requisição: $url'); final response = await http.get(Uri.parse(url)); print('šŸ“” Status code: ${response.statusCode}'); if (response.statusCode == 200) { final data = json.decode(response.body); if (data['status'] == 'OK' && data['routes'].isNotEmpty) { final route = data['routes'][0]; final legs = route['legs'][0]; final steps = legs['steps']; print('āœ… Rota encontrada com ${steps.length} passos'); // Extrair pontos da rota final List routePoints = []; // Adicionar ponto inicial routePoints.add(_currentLocation!); // Processar cada passo da rota for (var step in steps) { final startLocation = step['start_location']; final endLocation = step['end_location']; if (startLocation != null) { routePoints.add( LatLng(startLocation['lat'], startLocation['lng']), ); } if (endLocation != null) { routePoints.add(LatLng(endLocation['lat'], endLocation['lng'])); } } // Adicionar ponto final routePoints.add(_destination!); print('šŸ“ Total de pontos na rota: ${routePoints.length}'); setState(() { _polylines.clear(); _polylines.add( Polyline( polylineId: const PolylineId('route'), color: AppColors.backgroundGrey, width: 5, points: routePoints, ), ); }); // Ajustar cĆ¢mera para mostrar toda a rota _adjustCameraToShowRoute(); // Calcular distĆ¢ncia e tempo final distance = legs['distance']['text'] ?? 'Calculando...'; final duration = legs['duration']['text'] ?? 'Calculando...'; print('šŸ“Š DistĆ¢ncia: $distance, Tempo: $duration'); _showSnackBar( 'Rota de caminhada calculada!\nDistĆ¢ncia: $distance\nTempo: $duration', Colors.green, ); } else { print( 'āŒ Erro na resposta: ${data['status']} - ${data['error_message']}', ); _showSnackBar( 'NĆ£o foi possĆ­vel calcular a rota. Tente novamente.', AppColors.coral, ); } } else { print('āŒ Erro HTTP: ${response.statusCode}'); _showSnackBar( 'Erro ao conectar com o servidor. Verifique sua internet.', Colors.red, ); } } catch (e) { print('āŒ Erro ao calcular rota: $e'); _showSnackBar('Erro ao calcular rota: ${e.toString()}', Colors.red); } } void _adjustCameraToShowRoute() { if (_currentLocation == null || _destination == null) return; final double padding = 0.01; // Padding para mostrar os marcadores final bounds = LatLngBounds( southwest: LatLng( (_currentLocation!.latitude < _destination!.latitude ? _currentLocation!.latitude : _destination!.latitude) - padding, (_currentLocation!.longitude < _destination!.longitude ? _currentLocation!.longitude : _destination!.longitude) - padding, ), northeast: LatLng( (_currentLocation!.latitude > _destination!.latitude ? _currentLocation!.latitude : _destination!.latitude) + padding, (_currentLocation!.longitude > _destination!.longitude ? _currentLocation!.longitude : _destination!.longitude) + padding, ), ); print('šŸ“· Ajustando cĆ¢mera para mostrar rota completa'); mapController.animateCamera(CameraUpdate.newLatLngBounds(bounds, 100)); } void _onMapCreated(GoogleMapController controller) { print('šŸ—ŗļø Mapa criado'); mapController = controller; } void _showSnackBar(String message, Color color) { print('šŸ’¬ Mostrando mensagem: $message'); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: color, duration: const Duration(seconds: 3), behavior: SnackBarBehavior.floating, ), ); } void _clearRoute() { print('🧹 Limpando rota...'); setState(() { _destination = null; _polylines.clear(); _markers.removeWhere((marker) => marker.markerId.value == 'destination'); }); _showSnackBar('Rota limpa. Busque um novo destino.', AppColors.coral); } void _centerOnCurrentLocation() { if (_currentLocation != null) { print('šŸŽÆ Centralizando na localização atual'); mapController.animateCamera( CameraUpdate.newCameraPosition( CameraPosition(target: _currentLocation!, zoom: 15.0), ), ); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, body: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ AppColors.backgroundGrey.withOpacity(0.3), AppColors.background, ], ), ), child: Stack( children: [ // Back button Positioned( top: 50, left: 20, child: Container( decoration: BoxDecoration( color: AppColors.backgroundGrey.withOpacity(0.3), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white.withOpacity(0.1)), ), child: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { Navigator.pop(context); }, tooltip: 'Voltar', ), ), ), Column( children: [ const SizedBox(height: 100), // Search section Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Buscar Destino:', style: TextStyle( color: AppColors.white, fontSize: 14, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 12), // Search bar Container( decoration: BoxDecoration( color: AppColors.backgroundGrey.withOpacity(0.2), borderRadius: BorderRadius.circular(15), border: Border.all( color: AppColors.backgroundGrey.withOpacity(0.3), ), ), child: TextField( controller: _searchController, keyboardType: TextInputType.text, textInputAction: TextInputAction.search, enableSuggestions: true, autocorrect: true, decoration: InputDecoration( hintText: 'Ex: Parque Ibirapuera, Avenida Paulista...', hintStyle: TextStyle( color: AppColors.white.withOpacity(0.5), ), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), prefixIcon: const Icon( Icons.search, color: AppColors.white, ), suffixIcon: _isSearching ? const Padding( padding: EdgeInsets.all(12), child: SizedBox( width: 20, height: 20, child: CircularProgressIndicator( color: AppColors.backgroundGrey, strokeWidth: 2, ), ), ) : IconButton( icon: const Icon( Icons.send, color: AppColors.backgroundGrey, ), onPressed: () => _searchPlaces(_searchController.text), ), ), style: const TextStyle(color: AppColors.white), onChanged: (value) { if (value.length >= 3) { _searchPlaces(value); } else { setState(() { _placeSuggestions = []; }); } }, onSubmitted: (value) { if (_placeSuggestions.isNotEmpty) { _selectPlace(_placeSuggestions.first); } }, ), ), // Suggestions dropdown if (_placeSuggestions.isNotEmpty) Container( margin: const EdgeInsets.only(top: 8), decoration: BoxDecoration( color: AppColors.backgroundGrey.withOpacity(0.2), borderRadius: BorderRadius.circular(15), border: Border.all( color: AppColors.backgroundGrey.withOpacity(0.3), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: _placeSuggestions.take(5).map((place) { return InkWell( onTap: () => _selectPlace(place), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), child: Row( children: [ const Icon( Icons.place, color: AppColors.backgroundGrey, size: 20, ), const SizedBox(width: 12), Expanded( child: Text( place['description'], style: const TextStyle( color: AppColors.white, fontSize: 14, ), ), ), ], ), ), ); }).toList(), ), ), ], ), ), const SizedBox(height: 20), // Status bar Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Row( children: [ Text( _isRequestingPermission ? "Solicitando permissƵes..." : _isGettingLocation ? "Obtendo localização..." : _destination != null ? "Rota ativa" : "Defina um destino", style: const TextStyle( color: AppColors.white, fontSize: 14, fontWeight: FontWeight.w500, ), ), const Spacer(), if (_isRequestingPermission) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: AppColors.backgroundGrey.withOpacity(0.2), borderRadius: BorderRadius.circular(10), ), child: const Text( "PERMISSƃO", style: TextStyle( color: AppColors.backgroundGrey, fontSize: 10, fontWeight: FontWeight.bold, ), ), ) else if (_destination != null) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2, ), decoration: BoxDecoration( color: AppColors.backgroundGrey.withOpacity(0.2), borderRadius: BorderRadius.circular(10), ), child: const Text( "ATIVO", style: TextStyle( color: AppColors.backgroundGrey, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ], ), ), const SizedBox(height: 15), // Map Expanded( child: Container( margin: const EdgeInsets.symmetric(horizontal: 20), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), border: Border.all( color: AppColors.backgroundGrey.withOpacity(0.3), ), ), child: ClipRRect( borderRadius: BorderRadius.circular(20), child: Stack( children: [ GoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: CameraPosition( target: _currentLocation ?? const LatLng(-23.5505, -46.6333), zoom: 15.0, ), markers: _markers, polylines: _polylines, mapType: MapType.normal, myLocationEnabled: true, myLocationButtonEnabled: false, zoomControlsEnabled: false, ), // Loading indicator if (_isRequestingPermission || _isGettingLocation) Container( color: Colors.black54, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const CircularProgressIndicator( color: AppColors.backgroundGrey, strokeWidth: 3, ), const SizedBox(height: 16), Text( _isRequestingPermission ? 'Solicitando permissƵes de localização...' : 'Obtendo sua localização...', style: const TextStyle( color: Colors.white, fontSize: 16, ), ), ], ), ), ), // Map controls Positioned( top: 20, right: 20, child: Column( children: [ Container( decoration: BoxDecoration( color: AppColors.backgroundGrey.withOpacity( 0.8, ), borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.white.withOpacity(0.1), ), ), child: Column( children: [ IconButton( icon: const Icon( Icons.add, color: Colors.white, ), onPressed: () { mapController.animateCamera( CameraUpdate.zoomIn(), ); }, tooltip: 'Aumentar zoom', ), IconButton( icon: const Icon( Icons.remove, color: Colors.white, ), onPressed: () { mapController.animateCamera( CameraUpdate.zoomOut(), ); }, tooltip: 'Diminuir zoom', ), IconButton( icon: const Icon( Icons.my_location, color: Colors.white, ), onPressed: _centerOnCurrentLocation, tooltip: 'Minha localização', ), IconButton( icon: const Icon( Icons.clear, color: Colors.white, ), onPressed: _clearRoute, tooltip: 'Limpar rota', ), ], ), ), ], ), ), ], ), ), ), ), const SizedBox(height: 20), ], ), ], ), ), ); } }