892 lines
32 KiB
Dart
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'
|
|
'®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<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),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|