Files
DiasAbertos_RunVisionPro/lib/screens/google_maps_screen.dart
Carlos Correia ebca3cfdce TUDO
2026-03-04 15:54:24 +00:00

892 lines
32 KiB
Dart

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<GoogleMapScreen> createState() => _GoogleMapScreenState();
}
class _GoogleMapScreenState extends State<GoogleMapScreen> {
late GoogleMapController mapController;
final Set<Marker> _markers = {};
Set<Polyline> _polylines = {};
LatLng? _currentLocation;
LatLng? _destination;
bool _isGettingLocation = true;
bool _isRequestingPermission = true;
final TextEditingController _searchController = TextEditingController();
List<Map<String, dynamic>> _placeSuggestions = [];
bool _isSearching = false;
@override
void initState() {
super.initState();
_requestLocationPermission();
}
Future<void> _requestLocationPermission() async {
try {
print('🔐 Solicitando permissões de localização...');
// Verificar status atual das permissões
Map<Permission, PermissionStatus> 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<void> _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<void> _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<void> _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'
'&region=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<void> _selectPlace(Map<String, dynamic> 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<void> _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<LatLng> 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),
],
),
],
),
),
);
}
}