Skip to content
This repository was archived by the owner on Aug 8, 2026. It is now read-only.

Repository files navigation

Attractions Guide

Мобильный гид по достопримечательностям городов: каталог мест с фильтрами и поиском, интерактивная карта с кастомными маркерами, построение маршрутов через Google Directions, избранное и офлайн-режим.

React Native Expo Firebase Redux Toolkit Google Maps

Демонстрация

Главная

Больше скриншотов
Города и места — выбор города, режим «Рядом», экран города Карта
Города и места Карта
Авторизация Тёмная тема
Авторизация Тёмная тема

Возможности

Каталог

  • Сетка достопримечательностей с изображениями и скелетон-загрузкой
  • Поиск по названию и фильтрация по категориям
  • Три режима просмотра: «Все», «Избранные», «Рядом»
  • Режим «Рядом» считает расстояние от текущей геопозиции по формуле гаверсинуса, оставляет места в радиусе 10 км и сортирует по удалённости
  • Экраны детальной информации о достопримечательности и о городе: карусель изображений, описание, история, координаты

Карта

  • Google Maps с кастомными SVG-маркерами, свой значок для каждой категории (памятник, статуя, мемориал, улица, архитектура, музей, храм)
  • Bottom sheet с карточкой места при тапе на маркер
  • Построение маршрута до выбранного места — авто и пешком, с расстоянием и временем в пути, камера автоматически подгоняется под полигон маршрута
  • Переключение типа карты (схема / спутник / гибрид), слой пробок, точки интереса
  • Компас со сбросом на север, кнопка «моё местоположение», зум кнопками с ускорением по долгому нажатию
  • Отдельная тёмная стилизация карты, синхронная с темой системы

Аккаунт и данные

  • Регистрация, вход, восстановление пароля через Firebase Auth
  • Избранное, привязанное к аккаунту, с синхронизацией в Firestore
  • Редактирование профиля, выход, очистка локального кэша
  • Выбор города при первом запуске, смена города в любой момент

Офлайн-режим

  • Все данные города (город, достопримечательности, категории) кэшируются в AsyncStorage
  • При старте приложение сначала поднимает кэш и рисует интерфейс, затем обновляет данные из Firestore
  • NetInfo отслеживает соединение: без сети приложение работает на кэше и сообщает об этом
  • Сессия пользователя сохраняется между запусками через getReactNativePersistence

Архитектура

src/
├── components/
│   ├── main.js               # Инициализация: кэш → Firestore, авторизация, таб-навигация
│   ├── Home/                 # Стек: список, город, достопримечательность, профиль
│   │   └── Profile/          # Профиль и экран авторизации
│   ├── Map/                  # Карта, маркеры, контролы, bottom sheets
│   │   └── hooks/            # useLocation, useMapCamera, darkMapStyle
│   └── Layouts/              # Переиспользуемые элементы: карусель, чипы, скелетоны, кнопки
├── features/
│   ├── store/                # 7 слайсов Redux Toolkit
│   └── AsyncStorage.js       # Обёртка над локальным хранилищем
├── firebase/                 # Инициализация SDK и сервисный слой запросов
├── utils/                    # Расчёт расстояний и хелперы выборки
└── assets/icons/             # SVG-иконки категорий

Импорты используют алиасы из jsconfig.json (@components/*, @features/*, @utils/*, @firebase/*), которые babel-preset-expo резолвит без дополнительных плагинов.

Стек по слоям
Слой Технологии
Платформа React Native 0.74, Expo SDK 51, expo-dev-client
Состояние Redux Toolkit, react-redux
UI React Native Paper (MD3), кастомная палитра, react-native-reanimated-carousel, @gorhom/bottom-sheet
Навигация React Navigation (Stack) + Paper BottomNavigation
Карты react-native-maps, react-native-maps-directions, expo-location
Бэкенд Firebase Auth, Cloud Firestore
Хранилище AsyncStorage, @react-native-community/netinfo
Графика react-native-svg, expo-linear-gradient
Как устроен запуск приложения

src/components/main.js — единственная точка, где приложение решает, что показывать:

  1. Читает current-city-id из AsyncStorage. Если города нет — открывает экран выбора города.
  2. Если город есть, loadCityData сначала поднимает кэш (city-{id}, attractions-{id}, categories) и сразу диспатчит его в стор — интерфейс отрисовывается без ожидания сети.
  3. Затем, если есть соединение, тремя параллельными запросами тянет актуальные данные из Firestore и перезаписывает кэш.
  4. Сплэш-скрин скрывается только после того, как решение принято, — без мигания пустым экраном.
  5. authStateListener независимо подписывается на состояние Firebase Auth и подтягивает документ пользователя из users/{uid}.
Переходы между вкладками через стор

Вкладки «Главная» и «Карта» — независимые деревья навигации, поэтому переход «показать это место на карте» нельзя выразить обычным navigation.navigate. Вместо этого используются слайсы map и attraction с флагом shouldAnimate:

  • Экран достопримечательности диспатчит setMapCoordinates({ latitude, longitude, attraction }) → вкладка «Карта» видит флаг, анимирует камеру, открывает bottom sheet места и сбрасывает флаг.
  • Карточка на карте диспатчит setAttraction(...) → вкладка «Главная» перехватывает флаг и открывает экран достопримечательности.

Флаг сбрасывается сразу после обработки, чтобы повторные монтирования вкладки не запускали анимацию заново.

Модель данных Firestore
cities/{cityId}
  name, description, history, founded, area, population
  images: string[]
  location: { latitude, longitude }

attractions/{attractionId}
  name, description, history
  city_id: string          # связь с городом
  category: string         # id категории, он же ключ иконки маркера
  images: string[]
  location: { latitude, longitude }

categories/{categoryId}
  name                     # id: statue | monument | memorial | street | architecture | museum | temple

users/{uid}
  firstname, lastname, email
  created_at: Timestamp
  favorites: [{ attraction_id: string, added_at: Timestamp }]

Достопримечательности запрашиваются по where('city_id', '==', cityId) — приложение никогда не тянет каталог целиком.

Запуск

Проект использует нативные модули (react-native-maps с конфигом Google Maps), поэтому Expo Go не подходит — приложение не запустится ни по QR, ни любым другим способом через него. Нужен development build: он ставится на устройство как обычное приложение и дальше работает с Fast Refresh, как обычная разработка.

git clone https://github.com/choompuy/reactnative-attractionsguide.git
cd reactnative-attractionsguide
npm install

Создайте файл .env в корне:

# Firebase (Project settings → General → Your apps → Web app)
EXPO_PUBLIC_API_KEY=
EXPO_PUBLIC_AUTH_DOMAIN=
EXPO_PUBLIC_PROJECT_ID=
EXPO_PUBLIC_STORAGE_BUCKET=
EXPO_PUBLIC_MESSAGING_SENDER_ID=
EXPO_PUBLIC_APP_ID=

# Google Cloud → APIs & Services → Credentials
EXPO_PUBLIC_GOOGLE_MAPS_API_KEY=

Для ключа Google нужно включить Maps SDK for Android и Directions API.

Локально на своём телефоне (нужен Android SDK — ставится вместе с Android Studio):

  1. На телефоне: Настройки → О телефоне → 7 раз тапнуть по номеру сборки → Для разработчиков → включить «Отладка по USB».
  2. Подключить телефон кабелем, подтвердить запрос на экране, проверить adb devices — телефон должен быть в списке со статусом device.
  3. Собрать и установить:
npx expo run:android

Первая сборка займёт несколько минут — Gradle тянет зависимости и собирает нативный код. Дальше, пока телефон подключён и Metro запущен, изменения в JS подхватываются мгновенно; пересборка нужна только при изменении нативных зависимостей или app.json.

Эмулятор Android Studio подходит так же, отличие практическое: реальные GPS-координаты для проверки режима «Рядом» и построения маршрутов удобнее брать с телефона, а образ эмулятора для карт должен быть с Google Play Services (обычный образ без них карту не отрисует).

Без локальной среды — сборка в облаке через EAS:

eas build -p android --profile preview
Подготовка Firebase
  1. Создайте проект в Firebase Console.
  2. Включите Authentication → Sign-in method → Email/Password.
  3. Создайте базу Cloud Firestore и коллекции cities, attractions, categories по схеме выше.
  4. Опубликуйте правила безопасности: справочные коллекции доступны на чтение всем, документ пользователя — только владельцу.
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{document=**}     { allow read: if true; allow write: if false; }
    match /attractions/{document=**} { allow read: if true; allow write: if false; }
    match /categories/{document=**} { allow read: if true; allow write: if false; }

    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}

Планы

  • Отдельный экран списка избранного с сортировкой по дате добавления
  • Кэширование изображений на устройстве для полноценного офлайн-просмотра
  • Инкрементальная синхронизация данных города вместо полной перезагрузки коллекций
  • Промежуточные точки в маршрутах и режимы «велосипед» / «общественный транспорт»
  • Перевод кодовой базы на TypeScript

About

Attractions guide with interactive maps, route building and offline-first caching. React Native · Expo · Firebase · Redux Toolkit

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Used by

Contributors

Languages