correcao de erros no codigo do mapa

This commit is contained in:
2026-03-05 16:39:10 +00:00
parent 04d8d34a77
commit 0069dba953
4 changed files with 87 additions and 39 deletions

View File

@@ -0,0 +1,46 @@
class AppStrings {
// Main Screen
static const String complete = "COMPLETO";
static const String steps = "PASSOS";
static const String bpm = "BPM";
static const String kcal = "K/CAL";
static const String mapPreview = "MAPA";
static const String settings = "Configurações";
static const String groups = "Grupos";
static const String history = "Histórico";
static const String notifications = "Notificações";
static const String profile = "Perfil";
// Bluetooth Screen
static const String bluetoothTitle = "DISPOSITIVOS";
static const String bluetoothConnect = "CONECTAR BLUETOOTH";
static const String statusSearching = "STATUS: BUSCANDO...";
static const String statusReady = "STATUS: PRONTO";
static const String statusConnected = "STATUS: CONECTADO";
static const String foundDevices = "Encontrados";
static const String oneDevice = "1 Dispositivo";
static const String permissionDenied = "Permissões de Bluetooth negadas.";
static const String turnOnBluetooth = "Ligue o Bluetooth para buscar dispositivos.";
static const String scanError = "Erro ao iniciar scan: ";
static const String stopScanError = "Erro ao parar scan: ";
static const String connectingTo = "Conectando a ";
static const String connectionSuccess = "Conectado com sucesso!";
static const String connectionError = "Erro ao conectar: ";
static const String unknownDevice = "Dispositivo Desconhecido";
// Map Screen
static const String mapTitleTracking = "TRACKING ATIVO";
static const String mapTitlePlanning = "PLANEJAR TRAJETO";
static const String mapTitleRunning = "TOUHOU VIVA";
static const String mapPace = "RITMO";
static const String mapRoute = "TRAJETO";
static const String kmhUnit = "KM/H";
static const String kmUnit = "KM";
static const String planningInstruction = "Toque para definir Início e Fim";
static const String btnStop = "PARAR";
static const String btnSimulate = "SIMULAR";
static const String btnStartRun = "INICIAR CORRIDA";
static const String btnStopRun = "PARAR CORRIDA";
static const String startPoint = "Partida";
static const String finishPoint = "Chegada";
}

View File

@@ -1,7 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'constants/app_colors.dart'; import 'constants/app_colors.dart';
import 'constants/app_strings.dart';
import 'screens/google_map_screen.dart'; import 'screens/google_map_screen.dart';
import 'screens/bluetooth_connection_screen.dart'; // Importando a nova tela import 'screens/bluetooth_connection_screen.dart';
void main() { void main() {
// Ponto de entrada do aplicativo. // Ponto de entrada do aplicativo.
@@ -99,7 +100,7 @@ class _RunningScreenState extends State<RunningScreen>
), ),
), ),
Text( Text(
"COMPLETO", AppStrings.complete,
style: TextStyle( style: TextStyle(
color: Colors.white.withOpacity(0.5), color: Colors.white.withOpacity(0.5),
fontSize: 11, fontSize: 11,
@@ -144,7 +145,7 @@ class _RunningScreenState extends State<RunningScreen>
border: Border.all(color: Colors.white.withOpacity(0.1)), border: Border.all(color: Colors.white.withOpacity(0.1)),
), ),
child: Text( child: Text(
"${currentDistance.toStringAsFixed(1)} KM | ${targetDistance.toStringAsFixed(1)} KM", "${currentDistance.toStringAsFixed(1)} ${AppStrings.kmUnit} | ${targetDistance.toStringAsFixed(1)} ${AppStrings.kmUnit}",
style: const TextStyle( style: const TextStyle(
color: AppColors.white, color: AppColors.white,
fontSize: 15, fontSize: 15,
@@ -184,11 +185,11 @@ class _RunningScreenState extends State<RunningScreen>
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
_buildStatItem(Icons.directions_run_rounded, "3219", "PASSOS"), _buildStatItem(Icons.directions_run_rounded, "3219", AppStrings.steps),
Divider(color: Colors.white.withOpacity(0.1), height: 1), Divider(color: Colors.white.withOpacity(0.1), height: 1),
_buildStatItem(Icons.favorite_rounded, "98", "BPM"), _buildStatItem(Icons.favorite_rounded, "98", AppStrings.bpm),
Divider(color: Colors.white.withOpacity(0.1), height: 1), Divider(color: Colors.white.withOpacity(0.1), height: 1),
_buildStatItem(Icons.local_fire_department_rounded, "480", "K/CAL"), _buildStatItem(Icons.local_fire_department_rounded, "480", AppStrings.kcal),
], ],
), ),
), ),
@@ -243,7 +244,7 @@ class _RunningScreenState extends State<RunningScreen>
top: 12, top: 12,
left: 12, left: 12,
child: Text( child: Text(
"MAPA", AppStrings.mapPreview,
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 10, fontSize: 10,
@@ -287,11 +288,11 @@ class _RunningScreenState extends State<RunningScreen>
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
_buildMenuButton(Icons.settings_outlined, 'Configurações'), _buildMenuButton(Icons.settings_outlined, AppStrings.settings),
_buildMenuButton(Icons.group_outlined, 'Grupos'), _buildMenuButton(Icons.group_outlined, AppStrings.groups),
_buildMenuButton(Icons.history_rounded, 'Histórico'), _buildMenuButton(Icons.history_rounded, AppStrings.history),
_buildMenuButton(Icons.notifications_none_rounded, 'Notificações', showBadge: true), _buildMenuButton(Icons.notifications_none_rounded, AppStrings.notifications, showBadge: true),
_buildMenuButton(Icons.person_outline_rounded, 'Perfil', isAvatar: true), _buildMenuButton(Icons.person_outline_rounded, AppStrings.profile, isAvatar: true),
], ],
), ),
), ),

View File

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import '../constants/app_colors.dart'; import '../constants/app_colors.dart';
import '../constants/app_strings.dart';
class BluetoothConnectionScreen extends StatefulWidget { class BluetoothConnectionScreen extends StatefulWidget {
const BluetoothConnectionScreen({super.key}); const BluetoothConnectionScreen({super.key});
@@ -63,7 +64,7 @@ class _BluetoothConnectionScreenState extends State<BluetoothConnectionScreen> {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('Permissões de Bluetooth negadas.'), content: Text(AppStrings.permissionDenied),
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
), ),
); );
@@ -80,7 +81,7 @@ class _BluetoothConnectionScreenState extends State<BluetoothConnectionScreen> {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('Ligue o Bluetooth para buscar dispositivos.'), content: Text(AppStrings.turnOnBluetooth),
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
), ),
); );
@@ -93,7 +94,7 @@ class _BluetoothConnectionScreenState extends State<BluetoothConnectionScreen> {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Erro ao iniciar scan: $e'), content: Text('${AppStrings.scanError}$e'),
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
), ),
); );
@@ -108,7 +109,7 @@ class _BluetoothConnectionScreenState extends State<BluetoothConnectionScreen> {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Erro ao parar scan: $e'), content: Text('${AppStrings.stopScanError}$e'),
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
), ),
); );
@@ -120,7 +121,7 @@ class _BluetoothConnectionScreenState extends State<BluetoothConnectionScreen> {
try { try {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Conectando a ${device.platformName}...'), content: Text('${AppStrings.connectingTo}${device.platformName.isEmpty ? AppStrings.unknownDevice : device.platformName}...'),
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
), ),
@@ -135,7 +136,7 @@ class _BluetoothConnectionScreenState extends State<BluetoothConnectionScreen> {
FlutterBluePlus.stopScan(); FlutterBluePlus.stopScan();
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('Conectado com sucesso!'), content: Text(AppStrings.connectionSuccess),
backgroundColor: Colors.green, backgroundColor: Colors.green,
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
), ),
@@ -145,7 +146,7 @@ class _BluetoothConnectionScreenState extends State<BluetoothConnectionScreen> {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Erro ao conectar: $e'), content: Text('${AppStrings.connectionError}$e'),
backgroundColor: Colors.red, backgroundColor: Colors.red,
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
), ),
@@ -169,7 +170,7 @@ class _BluetoothConnectionScreenState extends State<BluetoothConnectionScreen> {
backgroundColor: AppColors.background, backgroundColor: AppColors.background,
appBar: AppBar( appBar: AppBar(
title: const Text( title: const Text(
'DISPOSITIVOS', AppStrings.bluetoothTitle,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w900, fontWeight: FontWeight.w900,
letterSpacing: 2, letterSpacing: 2,
@@ -214,8 +215,8 @@ class _BluetoothConnectionScreenState extends State<BluetoothConnectionScreen> {
children: [ children: [
Text( Text(
_connectedDevice != null _connectedDevice != null
? 'STATUS: CONECTADO' ? AppStrings.statusConnected
: (_isScanning ? 'STATUS: BUSCANDO...' : 'STATUS: PRONTO'), : (_isScanning ? AppStrings.statusSearching : AppStrings.statusReady),
style: TextStyle( style: TextStyle(
color: _connectedDevice != null ? Colors.greenAccent : (_isScanning ? AppColors.coral : Colors.white54), color: _connectedDevice != null ? Colors.greenAccent : (_isScanning ? AppColors.coral : Colors.white54),
fontSize: 10, fontSize: 10,
@@ -226,8 +227,8 @@ class _BluetoothConnectionScreenState extends State<BluetoothConnectionScreen> {
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
_connectedDevice != null _connectedDevice != null
? '1 Dispositivo' ? AppStrings.oneDevice
: '${_scanResults.length} Encontrados', : '${_scanResults.length} ${AppStrings.foundDevices}',
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 22, fontSize: 22,
@@ -289,7 +290,7 @@ class _BluetoothConnectionScreenState extends State<BluetoothConnectionScreen> {
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final BluetoothDevice device = _connectedDevice ?? _scanResults[index].device; final BluetoothDevice device = _connectedDevice ?? _scanResults[index].device;
final name = device.platformName; final name = device.platformName.isEmpty ? AppStrings.unknownDevice : device.platformName;
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 18), padding: const EdgeInsets.only(bottom: 18),

View File

@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import '../constants/app_colors.dart'; import '../constants/app_colors.dart';
import '../constants/app_strings.dart';
class GoogleMapScreen extends StatefulWidget { class GoogleMapScreen extends StatefulWidget {
const GoogleMapScreen({super.key}); const GoogleMapScreen({super.key});
@@ -18,17 +19,18 @@ class _GoogleMapScreenState extends State<GoogleMapScreen> {
GoogleMapController? _mapController; GoogleMapController? _mapController;
StreamSubscription<Position>? _positionStreamSubscription; StreamSubscription<Position>? _positionStreamSubscription;
Timer? _simulationTimer; Timer? _simulationTimer;
// Controle de frequência de atualização para evitar sobrecarga e crashes // Controle de frequência de atualização para evitar sobrecarga e crashes
DateTime? _lastUpdate; DateTime? _lastUpdate;
final List<LatLng> _routePoints = []; final List<LatLng> _routePoints = [];
final Set<Polyline> _polylines = {}; final Set<Polyline> _polylines = {};
final Set<Marker> _markers = {}; final Set<Marker> _markers = {};
LatLng? _plannedStart; LatLng? _plannedStart;
LatLng? _plannedEnd; LatLng? _plannedEnd;
bool _isPlanningMode = false; bool _isPlanningMode = false;
bool _isRunning = false;
double _currentSpeed = 0.0; double _currentSpeed = 0.0;
double _totalDistance = 0.0; double _totalDistance = 0.0;
@@ -124,7 +126,7 @@ class _GoogleMapScreenState extends State<GoogleMapScreen> {
} }
} }
}, onError: (error) { }, onError: (error) {
debugPrint("Erro no GPS: $error"); debugPrint("${AppStrings.unknownDevice}: $error");
}); });
} }
@@ -137,7 +139,7 @@ class _GoogleMapScreenState extends State<GoogleMapScreen> {
_currentSpeed = speed >= 0 ? speed : 0; _currentSpeed = speed >= 0 ? speed : 0;
_routePoints.add(newPoint); _routePoints.add(newPoint);
_currentPosition = newPoint; _currentPosition = newPoint;
_updateMarkers(); // Agora só atualiza a seta seguidora _updateMarkers();
if (_routePoints.length > 1) { if (_routePoints.length > 1) {
_polylines.removeWhere((p) => p.polylineId.value == 'route' || p.polylineId.value == 'route_glow'); _polylines.removeWhere((p) => p.polylineId.value == 'route' || p.polylineId.value == 'route_glow');
@@ -166,9 +168,7 @@ class _GoogleMapScreenState extends State<GoogleMapScreen> {
} }
void _updateMarkers() { void _updateMarkers() {
// Remove APENAS o marcador da seta seguidora para evitar duplicidade com o ponto de partida fixo
_markers.removeWhere((m) => m.markerId.value == 'follower'); _markers.removeWhere((m) => m.markerId.value == 'follower');
_markers.add(Marker( _markers.add(Marker(
markerId: const MarkerId('follower'), markerId: const MarkerId('follower'),
position: _currentPosition, position: _currentPosition,
@@ -192,17 +192,17 @@ class _GoogleMapScreenState extends State<GoogleMapScreen> {
setState(() { setState(() {
if (_plannedStart == null) { if (_plannedStart == null) {
_plannedStart = point; _plannedStart = point;
_markers.add(Marker(markerId: const MarkerId('planned_start'), position: point, icon: _startIcon ?? BitmapDescriptor.defaultMarker, zIndex: 5)); _markers.add(Marker(markerId: const MarkerId('planned_start'), position: point, icon: _startIcon ?? BitmapDescriptor.defaultMarker, zIndex: 5, infoWindow: const InfoWindow(title: AppStrings.startPoint)));
} else if (_plannedEnd == null) { } else if (_plannedEnd == null) {
_plannedEnd = point; _plannedEnd = point;
_markers.add(Marker(markerId: const MarkerId('planned_end'), position: point, icon: _finishIcon ?? BitmapDescriptor.defaultMarker, zIndex: 5)); _markers.add(Marker(markerId: const MarkerId('planned_end'), position: point, icon: _finishIcon ?? BitmapDescriptor.defaultMarker, zIndex: 5, infoWindow: const InfoWindow(title: AppStrings.finishPoint)));
_polylines.add(Polyline(polylineId: const PolylineId('planned_route'), points: [_plannedStart!, _plannedEnd!], color: Colors.white.withOpacity(0.1), width: 2, zIndex: 1)); _polylines.add(Polyline(polylineId: const PolylineId('planned_route'), points: [_plannedStart!, _plannedEnd!], color: Colors.white.withOpacity(0.1), width: 2, zIndex: 1));
} else { } else {
_plannedStart = point; _plannedStart = point;
_plannedEnd = null; _plannedEnd = null;
_markers.removeWhere((m) => m.markerId.value.startsWith('planned')); _markers.removeWhere((m) => m.markerId.value.startsWith('planned'));
_polylines.removeWhere((p) => p.polylineId.value == 'planned_route'); _polylines.removeWhere((p) => p.polylineId.value == 'planned_route');
_markers.add(Marker(markerId: const MarkerId('planned_start'), position: point, icon: _startIcon ?? BitmapDescriptor.defaultMarker, zIndex: 5)); _markers.add(Marker(markerId: const MarkerId('planned_start'), position: point, icon: _startIcon ?? BitmapDescriptor.defaultMarker, zIndex: 5, infoWindow: const InfoWindow(title: AppStrings.startPoint)));
} }
}); });
} }
@@ -220,7 +220,7 @@ class _GoogleMapScreenState extends State<GoogleMapScreen> {
_totalDistance = 0.0; _totalDistance = 0.0;
_currentPosition = _plannedStart ?? _currentPosition; _currentPosition = _plannedStart ?? _currentPosition;
_routePoints.add(_currentPosition); _routePoints.add(_currentPosition);
_updateMarkers(); // Reposiciona a seta no início _updateMarkers();
}); });
_simulationTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) { _simulationTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
@@ -248,10 +248,10 @@ class _GoogleMapScreenState extends State<GoogleMapScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: AppColors.background, backgroundColor: AppColors.background,
appBar: AppBar(title: Text(_isPlanningMode ? 'PLANEJAR ROTA' : 'CORRIDA VIVA', style: const TextStyle(fontWeight: FontWeight.w900, color: Colors.white, letterSpacing: 2)), centerTitle: true, backgroundColor: Colors.transparent, elevation: 0, leading: IconButton(icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white), onPressed: () => Navigator.pop(context)), actions: [IconButton(icon: Icon(_isPlanningMode ? Icons.check_circle_rounded : Icons.add_location_alt_rounded, color: AppColors.coral, size: 30), onPressed: () => setState(() => _isPlanningMode = !_isPlanningMode))]), appBar: AppBar(title: Text(_isPlanningMode ? AppStrings.mapTitlePlanning : AppStrings.mapTitleRunning, style: const TextStyle(fontWeight: FontWeight.w900, color: Colors.white, letterSpacing: 2)), centerTitle: true, backgroundColor: Colors.transparent, elevation: 0, leading: IconButton(icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white), onPressed: () => Navigator.pop(context)), actions: [IconButton(icon: Icon(_isPlanningMode ? Icons.check_circle_rounded : Icons.add_location_alt_rounded, color: AppColors.coral, size: 30), onPressed: () => setState(() => _isPlanningMode = !_isPlanningMode))]),
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
body: Stack(children: [Center(child: Container(width: MediaQuery.of(context).size.width * 0.94, height: MediaQuery.of(context).size.height * 0.7, decoration: BoxDecoration(color: AppColors.backgroundGrey, borderRadius: BorderRadius.circular(55), border: Border.all(color: Colors.white.withOpacity(0.1), width: 3), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.7), blurRadius: 50, offset: const Offset(0, 30))]), child: ClipRRect(borderRadius: BorderRadius.circular(52), child: _isLoading ? const Center(child: CircularProgressIndicator(color: AppColors.coral)) : GoogleMap(initialCameraPosition: CameraPosition(target: _currentPosition, zoom: 17.5), onMapCreated: (controller) => _mapController = controller, onTap: _onMapTap, markers: _markers, polylines: _polylines, zoomControlsEnabled: false, myLocationButtonEnabled: false, compassEnabled: false, mapToolbarEnabled: false)))), Positioned(top: 115, left: 45, right: 45, child: Container(padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 25), decoration: BoxDecoration(color: AppColors.background.withOpacity(0.95), borderRadius: BorderRadius.circular(25), border: Border.all(color: Colors.white10), boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 10)]), child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [_buildStat("RITMO", "${(_currentSpeed * 3.6).toStringAsFixed(1)}", "KM/H"), Container(width: 1, height: 35, color: Colors.white10), _buildStat("TRAJETO", (_totalDistance / 1000).toStringAsFixed(2), "KM")]))), if (_isPlanningMode) Positioned(bottom: 140, left: 60, right: 60, child: Container(padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: Colors.black.withOpacity(0.85), borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.coral.withOpacity(0.5))), child: const Text("Toque para definir Início e Fim", textAlign: TextAlign.center, style: TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold))))]), body: Stack(children: [Center(child: Container(width: MediaQuery.of(context).size.width * 0.94, height: MediaQuery.of(context).size.height * 0.7, decoration: BoxDecoration(color: AppColors.backgroundGrey, borderRadius: BorderRadius.circular(55), border: Border.all(color: Colors.white.withOpacity(0.1), width: 3), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.7), blurRadius: 50, offset: const Offset(0, 30))]), child: ClipRRect(borderRadius: BorderRadius.circular(52), child: _isLoading ? const Center(child: CircularProgressIndicator(color: AppColors.coral)) : GoogleMap(initialCameraPosition: CameraPosition(target: _currentPosition, zoom: 17.5), onMapCreated: (controller) => _mapController = controller, onTap: _onMapTap, markers: _markers, polylines: _polylines, zoomControlsEnabled: false, myLocationButtonEnabled: false, compassEnabled: false, mapToolbarEnabled: false)))), Positioned(top: 115, left: 45, right: 45, child: Container(padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 25), decoration: BoxDecoration(color: AppColors.background.withOpacity(0.95), borderRadius: BorderRadius.circular(25), border: Border.all(color: Colors.white10), boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 10)]), child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [_buildStat(AppStrings.mapPace, "${(_currentSpeed * 3.6).toStringAsFixed(1)}", AppStrings.kmhUnit), Container(width: 1, height: 35, color: Colors.white10), _buildStat(AppStrings.mapRoute, (_totalDistance / 1000).toStringAsFixed(2), AppStrings.kmUnit)]))), if (_isPlanningMode) Positioned(bottom: 140, left: 60, right: 60, child: Container(padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: Colors.black.withOpacity(0.85), borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.coral.withOpacity(0.5))), child: const Text(AppStrings.planningInstruction, textAlign: TextAlign.center, style: TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold))))]),
floatingActionButton: FloatingActionButton.extended(onPressed: _toggleSimulation, label: Text(_isSimulating ? "PARAR" : "INICIAR CORRIDA", style: const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.5)), icon: Icon(_isSimulating ? Icons.stop_rounded : Icons.play_arrow_rounded, size: 32), backgroundColor: _isSimulating ? AppColors.coral : Colors.white, foregroundColor: _isSimulating ? Colors.white : AppColors.background, elevation: 15), floatingActionButton: FloatingActionButton.extended(onPressed: _toggleSimulation, label: Text(_isSimulating ? AppStrings.btnStop : AppStrings.btnStartRun, style: const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.5)), icon: Icon(_isSimulating ? Icons.stop_rounded : Icons.play_arrow_rounded, size: 32), backgroundColor: _isSimulating ? AppColors.coral : Colors.white, foregroundColor: _isSimulating ? Colors.white : AppColors.background, elevation: 15),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
); );
} }