ParkTrack (Smart Parking) — обзор и архитектура
Архитектурная схема (archify) →
ParkTrack — мобильное приложение на Flutter для поиска парковки: показывает свободные зоны на карте, прогнозирует занятость и строит маршрут до выбранного места. Пакет называется mobile, реальное имя продукта — ParkTrack (см. pubspec.yaml).
Clean Architecture на Flutter
Четыре слоя, разделённые по папкам lib/:
core— инфраструктура без бизнес-смысла:network(Dio-клиент, интерцепторы),router(go_router),storage(токены, флаг истёкшей сессии),theme.data— реализация доступа к данным:api(Retrofit-подобные Dio-вызовы для auth/zones/occupancy/routing/forecasts),models(DTO сfreezed+json_serializable),repositories(фасад над api для presentation-слоя).domain— модели предметной области без привязки к сети:Zone,User,RouteResult. Здесь нет@JsonKey-разметки — это чистые бизнес-объекты.presentation— Riverpod-провайдеры (StateNotifierProvider) и экраны (auth/map/profile/search/splash).
Зависимости идут внутрь: presentation знает про domain и data, domain не знает ни про кого.
Зачем нужен MockInterceptor
lib/core/network/mock_interceptor.dart — Dio-интерцептор, который перехватывает запрос на этапе onRequest и сразу отдаёт handler.resolve(...) с готовым JSON, не давая запросу уйти в сеть вообще. Флаг kUseMocks (сейчас false в коде) включает его глобально в dio_client.dart.
Это не заглушка "на всякий случай" — подделанные ответы точно повторяют реальный контракт бэкенда: GeoJSON-полигоны зон (Polygon с кольцом координат), уровень доверия прогноза (confidence), и даже реалистичный deeplink_url вида yandexnavi://build_route_on_map?lat_to=...&appmetrica_tracking_id=... для передачи построения маршрута во внешний Яндекс.Навигатор. Такой мок позволяет верстать и отлаживать весь UI (карту, список кандидатов, экран маршрута) до того, как бэкенд вообще готов — и потом выключается одной константой.
Модель зоны и почему в ней есть прогноз
domain/models/zone.dart — Zone хранит не только текущее состояние (freeCount, confidence, pay), но и опциональные поля прогноза: hasForecast, forecastFor, forecastGeneratedAt. Это значит, что не у каждой зоны обязан быть прогноз занятости — карта корректно работает и с зонами, где есть только текущие данные, и точно так же рисует зоны с добавленным поверх прогнозом на будущее время.
ZoneType — parallel (парковка параллельно бордюру) или standard; LocationType — street/yard/openLot/underground/multilevel. Оба поля не для отображения ради галочки — потенциально влияют на то, как приложение должно ранжировать кандидатов (подземная парковка vs открытая площадка — разный сценарий использования).
Поток "поиск → кандидаты → маршрут"
По мок-данным routing/search виден реальный контракт: сервер возвращает не одну зону, а ранжированный список кандидатов (rank, current_free_count, current_confidence, predicted_free_count, distance_to_destination_meters, duration_from_origin_seconds) — то есть решение "куда ехать" явно не принимается только по расстоянию, в формуле участвует и прогноз занятости. Дальше routing/new фиксирует выбранную зону и возвращает deeplink_url — финальная навигация делегируется внешнему навигатору, приложение не рисует собственный turn-by-turn маршрут.