Paso 1: Agrega el campo en AppSettings y su persistencia.
class AppSettings {
final bool newFeatureEnabled;
// ...
const AppSettings({
this.newFeatureEnabled = false,
// ...
});
// Añade en copyWith, persist, load, y claves de SharedPreferences
}Paso 2: Expón el setter en SettingsNotifier:
void setNewFeatureEnabled(bool v) => _update(state.copyWith(newFeatureEnabled: v));Paso 3: Añade el control en la UI (ejemplo en SettingsPagePlaceholder):
_buildSwitchTile(
'Nueva función',
'Activa la nueva funcionalidad',
Icons.new_releases,
settings.newFeatureEnabled,
notifier.setNewFeatureEnabled,
)Paso 1: Agrega la opción en AppSettings.routePreference y en la UI (dropdown).
Paso 2: Actualiza el mapeo en RoutingEngine para soportar la nueva preferencia.
switch (preference) {
case 'avoidFerries':
params['exclude'] = 'ferry';
break;
// ...
}Paso 3: Añade la lógica en la UI para mostrar el nuevo tipo de ruta.
Paso 1: Modifica el modelo en features/vehicles/domain/vehicle_profile.dart:
@freezed
class VehicleProfile with _$VehicleProfile {
const factory VehicleProfile({
required String id,
required String name,
double? dragCoefficient,
// ...
}) = _VehicleProfile;
}Paso 2: Ejecuta flutter pub run build_runner build para regenerar modelos.
Paso 3: Añade el campo en la UI de gestión de vehículos.
Paso 1: Crea lib/l10n/app_fr.arb (por ejemplo para francés).
Paso 2: Añade las traducciones necesarias.
Paso 3: Ejecuta flutter gen-l10n para generar el soporte.
Paso 4: Añade Locale('fr') a supportedLocales en main.dart.
Paso 1: Crea un archivo en test/ (ej: test/new_logic_test.dart).
Paso 2: Escribe el test:
import 'package:flutter_test/flutter_test.dart';
import 'package:ev_map/lib/core/new_logic.dart';
void main() {
test('Nueva lógica funciona', () {
expect(miFuncion(2), 4);
});
}Paso 1: Extiende RouteAlternative en route_models.dart si necesitas nuevos campos.
Paso 2: Actualiza la lógica de generación en el engine y la UI para mostrar el nuevo dato.
Estos ejemplos cubren los casos más comunes de extensión. Para cualquier cambio mayor, sigue el patrón de separación por dominio y mantén la documentación actualizada en el README.
lib/
main.dart # App entrypoint, navigation, localization
core/ # AppSettings, logger, offline map service, color utils
features/
routing/ # Routing engine, models, UI, Mapbox integration
data/ # MapboxDirectionsRepository
domain/ # RoutePlan models (freezed)
presentation/ # Route planning UI & providers
vehicles/ # Vehicle profiles, EV curves (freezed)
l10n/ # Localization ARB files, generated localizations
navigation/ # Map globals
search/ # Search delegate
widgets/ # Reusable UI widgets (search pill)
test/
... # Unit and integration tests
- Riverpod for all app state (settings, routing, offline, etc).
- Settings persisted via SharedPreferences (
core/app_settings.dart). - Routing state in
features/routing/routing_notifier.dart.
- ARB files:
lib/l10n/app_en.arb,app_es.arb. - Add new language: create
app_xx.arb, runflutter gen-l10n. - All UI strings use
AppLocalizations(seemain.dart).
- Abstracted via
RoutingEngineinterface. - Implementations:
MockRoutingEngine(offline/synthetic),MapboxDirectionsRoutingEngine(real API). - Preference mapping: fastest/shortest/eco/avoidTolls/avoidHighways.
- Geometry decoded with custom polyline6 logic.
- Vehicle profiles and EV curves in
features/vehicles/domain/(freezed models). - Extend by adding new fields to
vehicle_profile.dartand regenerate with build_runner.
- Managed via
core/offline_map_service.dart. - Settings in AppSettings (offlineEnabled, offlineMaxCacheMb).
- All tests in
test/folder. - Run with
flutter test. - Si los tests se cuelgan: verifica inicialización de plugins, añade
TestWidgetsFlutterBinding.ensureInitialized()al inicio del test. - Para lógica Dart pura, extrae funciones fuera de clases de widgets.
- Para añadir settings: actualiza
AppSettings,SettingsNotifier, y la UI enSettingsPagePlaceholder. - Para nuevas funciones de rutas: extiende
RoutingEngine, actualiza la UI enRoutePlanningPage. - Para analíticas de vehículos: actualiza modelos en
features/vehicles/domain/, añade UI en presentación de vehículos.
- Si falla la localización: revisa los archivos ARB y ejecuta
flutter gen-l10n. - Si falla el routing: revisa .env para el token de Mapbox, verifica red, el fallback a mock engine es automático.
- Si los tests se cuelgan: aísla con un test trivial, revisa dependencias de plugins, asegura inicialización correcta del binding.
- Clona el repo, crea
.envcon el token de Mapbox. - Ejecuta
flutter pub get. - Explora
main.dartpara el entrypoint y navegación. - Revisa
core/app_settings.dartpara settings y persistencia. - Lógica de rutas: comienza con
features/routing/routing_notifier.dartyrouting_engine.dart. - UI: ve a
features/routing/presentation/route_planning_page.dartyModernMapPageenmain.dart. - Para localización, revisa
lib/l10n/y su uso en widgets de UI. - Para vehículos, revisa
features/vehicles/domain/vehicle_profile.dart. - Para mapas offline, revisa
core/offline_map_service.dart.
- Riverpod: https://riverpod.dev/
- Mapbox Flutter: https://docs.mapbox.com/mapbox-gl-js/api/
- Flutter localization: https://docs.flutter.dev/accessibility-and-localization/internationalization
- Freezed models: https://pub.dev/packages/freezed
Abre issues o pull requests para bugs, mejoras o nuevas funcionalidades. Documenta cualquier decisión arquitectónica nueva en este README para futuros mantenedores.
A comprehensive Flutter application for electric vehicle navigation, route planning, and charging station management, built with modern UI/UX based on professional navigation app standards.
- Real-time Navigation: Turn-by-turn directions with visual indicators
- Trip Information Cards: Distance remaining, arrival time, and battery predictions
- Interactive Controls: Route termination, fullscreen mode, and audio navigation
- Professional Dark Theme: Modern dark UI optimized for in-vehicle use
- Destination Search: Smart search functionality for route planning
- Charging Stop Integration: Automatic charging station recommendations
- Battery Level Monitoring: Real-time battery predictions for each route segment
- Route History: Organized history of previous trips with details
- EV Battery Monitoring: Visual battery level indicators with color coding
- Vehicle Profiles: Support for multiple electric vehicles
- Range Calculations: Accurate range estimates based on vehicle specifications
- Charging Status: Real-time charging information and predictions
- Theme Customization: Dark/light theme toggle
- Notification Settings: Customizable alerts for charging and navigation
- Location Services: Precise location tracking with permission management
- User Preferences: Personalized settings for optimal user experience
Robust, smooth camera behavior designed to avoid jank and accidental recenters while preserving user control.
Core Behaviors:
- Dual Mode Tracking: Automatic live tracking (
_isTrackingLocation) pauses instantly when the user interacts (pointer down) and resumes only after a controlled recenter. - Interaction Detection: Low-level
Listener(pointer events) replaces gesture stacks to eliminate gesture conflicts and ensure precise start/end interaction timing. - Deferred Auto Recenter: A timer (2.5s) starts when the user stops touching the map; on expiry it animates back to the last known position with a cinematic
easeTo(tag:AutoRecenter). - Single Animation Guard:
_animateCamera()centralizes all camera motions and enforces a concurrency lock via_cameraAnimatingto prevent overlappingeaseTocalls (root cause of frame-by-frame stutter). - Throttled Live Tracking: While tracking is active, location stream updates are rate-limited (≥900 ms,
_locationUpdateMinIntervalMs) and skipped if an animation is in progress—removing micro step animations. - Distance-Based Center Detection:
_checkIfCameraCentered()compares camera center vs user location (≤50 m threshold) to decide if the floating recenter button should show or hide. - Floating Recenter Button: Appears only when user moved the map away; on press it (1) cancels any pending auto timer, (2) temporarily disables tracking, (3) performs a smooth
easeTo(tag:BtnRecenter), (4) re-enables tracking and hides itself. - Fallback Safety: If the main auto recenter animation throws, a fallback animation (
AutoRecenterFallback) runs with simpler parameters. - State Isolation: No animation directly manipulates unrelated UI—camera logic lives in a contained flow making refactors safer.
- Extensible Tags: All camera motions emit start/end logs with a semantic tag to simplify profiling and regression diagnosis.
Design Invariants (Do NOT break):
- Never call
mapboxMap.easeTodirectly—always use_animateCamera()to preserve concurrency guarantees and logging. - Always set
_isTrackingLocation = falsebefore a manual or button-initiated recenter to avoid the live stream fighting the animation. - Respect the 50 m centering heuristic unless UX requirements change; adjust both logic and docs together.
- Keep throttling interval aligned with (or slightly below) the shortest tracking animation duration to avoid overlap.
Modification Guidelines:
- To add new camera behaviors (e.g., route preview), create a new tag (e.g.,
RoutePreview) and invoke_animateCamera()—do not duplicate animation code. - If changing animation durations, evaluate whether
_locationUpdateMinIntervalMsmust also change. - If adjusting interaction logic, ensure pointer down cancels timers and pointer up restarts them; otherwise auto recenter may trigger prematurely.
Resumen (Español):
La cámara implementa un sistema inteligente con: detección directa de interacción táctil, temporizador diferido para recentrado automático, botón flotante contextual, guardia de concurrencia para animaciones, y limitación de frecuencia de actualizaciones de ubicación para evitar movimientos a saltos. Toda animación pasa por _animateCamera() que serializa transiciones y emite logs para depuración.
This section exists to prevent future regressions in smoothness and interaction feel—update it with any behavioral change.
- Flutter SDK: 3.32.6+ for cross-platform development
- Mapbox Integration: Professional mapping and navigation services
- Riverpod State Management: Robust state management with error handling
- Material Design 3: Modern UI components and theming
ModernEvMapApp() // Main app with bottom navigation
├── ModernMapPage() // Navigation interface with real-time info
├── ModernRoutesPage() // Route history and trip planning
├── ModernVehiclesPage() // EV management and battery monitoring
└── ModernProfilePage() // Settings and user preferences- Bottom Navigation: 4-tab interface (Map, Routes, Vehicles, Profile)
- Route Search: Integrated search with charging station recommendations
- Interactive Elements: Responsive buttons with user feedback
- State Persistence: Maintains app state across sessions
- Primary Background:
#1A1A1A(Professional dark) - Secondary Background:
#2A2A2A(Card surfaces) - Accent Color:
#007AFF(Apple-style blue) - Success Indicators: Green for battery/charging status
- Warning Indicators: Orange/red for low battery alerts
- Dark-First Design: Optimized for in-vehicle use and night driving
- High Contrast: Excellent readability in all lighting conditions
- Touch-Friendly: Large interactive elements for easy mobile use
- Information Hierarchy: Clear visual hierarchy for navigation data
- Flutter SDK 3.32.6+
- Android SDK with API level 35+
- Mapbox account and API token
- Create
.envfile in project root:
MAPBOX_PUBLIC_TOKEN=your_mapbox_token_here
API_BASE_URL=https://api.mapbox.com- Install dependencies:
flutter pub get- Run on device:
flutter run -d your_device_id- Samsung Galaxy S24 Ultra: Full functionality confirmed
- Android 15 (API 35): Primary target platform
- iOS Support: Compatible with iOS 12.0+
- NDK 27.0.12077973: Optimized native performance
- Gradle Build: Streamlined build process
- Memory Management: Efficient state management with Riverpod
Based on professional navigation app references, the UI includes:
- Gradient header with turn-by-turn directions
- Information cards showing distance, time, and battery
- Action buttons for route control
- Real-time trip monitoring
- Smart destination search
- Visual route overview with charging stops
- Battery level predictions at each stop
- Interactive trip planning tools
- Visual battery level indicators
- Vehicle specifications display
- Range calculations and predictions
- Multiple vehicle support
- Modern switch controls
- Organized settings categories
- Clear descriptions for each option
- Responsive feedback for user actions
- Modern bottom navigation interface
- Professional dark theme implementation
- Interactive navigation controls
- Route planning with charging integration
- Vehicle management dashboard
- Settings and preferences panel
- Real-time battery monitoring
- Mapbox API integration
- Error handling and fallbacks
The app abstracts routing behind a RoutingEngine interface with two implementations:
MockRoutingEngine: Provides synthetic geometries and deterministic alternative statistics for offline/demo usage.MapboxDirectionsRoutingEngine: Real integration with Mapbox Directions (driving-trafficprofile) returning polyline6 geometries and enriched metadata.
Configure tokens in a root .env file (already loaded in main()):
MAPBOX_DIRECTIONS_TOKEN=pk.your_mapbox_token
# Fallback names also recognized:
MAPBOX_ACCESS_TOKEN=pk.alt_tokenIf neither token is present the app automatically falls back to the mock engine—no crashes, the UI still shows alternatives (synthetic).
User route preference (stored in AppSettings.routePreference) maps to Mapbox query parameters:
fastest/shortest/eco(default): noexcludeavoidTolls:exclude=tollavoidHighways:exclude=motorway
- Requests use
geometries=polyline6andoverview=full. - Decoded via a custom
decodePolyline6helper; malformed input truncates gracefully instead of throwing.
When requestSteps=true, the engine parses leg steps to infer:
hasTolls: Matches keywords (toll,peaje).usesHighway: Regex on step name/ref (autopista,motorway,highway,A\d,M\d,I-\d, etc.). If steps were not requested, a conservative placeholder heuristic assigns tolls/highway flags based on index for diversity.
energyKwh = distanceKm * consumptionKwhPerKm with speed-based multiplier:
-
100 km/h → +12%
- 60–100 km/h → +5% Future improvements: incorporate elevation gain (Mapbox Terrain), temperature, vehicle-specific drag coefficients and battery SOC.
Common Mapbox code responses are mapped to localized messages:
NoRoute→ "No se encontró ruta."NoSegment→ "No hay segmento de carretera cercano al punto."InvalidInput→ "Entrada inválida..."Unauthorized→ Token issues. Any error (network, parsing, unexpected structure) triggers synthetic fallback routes so the UI remains usable.RoutingState.errorMessagesurfaces feedback through a transient in-map card.
Added tests validating:
- Polyline6 decoding sanity.
- Preference →
excludequery mapping. - Fallback geometry preservation on simulated network failure.
To run tests:
flutter test- To add richer annotations (e.g., congestion, elevation), request
annotations=congestion,maxspeedand extend parsing. - For EV-optimized routing (charging stops), consider Mapbox EV Routing or integrate an external planner; adapt engine to post-process via intermediate waypoints.
- Keep tokens out of version control (.gitignore
.env). - Rotate compromised tokens promptly; unauthorized errors will surface in-app.
- Button press feedback with snackbars
- Navigation between app sections
- Route search functionality
- Settings toggles and controls
- User preference management
The modern UI implementation is based on professional navigation app standards and provides a comprehensive electric vehicle navigation experience. All interactive elements are functional with user feedback, and the app maintains a professional appearance suitable for in-vehicle use.
For technical support or feature requests, the codebase is well-documented with clear component separation and maintainable architecture.