Skip to main content

Screens

Map Screen (app/(tabs)/index.tsx)

The main screen. Shows a full-screen Google Map with a two-tier marker system, a floating search bar with EM logo, zoom in/out controls, a "my location" button, and a bottom sheet with both a place list and inline place detail.

Two-tier marker system:

  • Primary markers: Custom SVG pin icons (MarkerIcon component) with place name labels below. Overlap-deduplicated at ~50px spacing.
  • Secondary markers: Small teal dots (14px circles with white border) for lower-priority places. Overlap-deduplicated at ~10px spacing, up to 200 dots.
  • Selected markers: When a marker is selected (primary or secondary), it turns red (#B91C1C) with zIndex 999. Selected secondary dots are promoted from small dots to full red pin markers (MarkerIcon).
  • Both tiers use a staggered opacity fade-in animation (react-native-reanimated withDelay + withTiming). Primary markers fade in first (30ms stagger), secondary dots follow (15ms stagger).

Key interactions:

  • Pan/zoom the map → triggers onRegionChangeComplete → updates useMapStore.regionusePlaces re-fetches
  • Tap a marker (primary or secondary) → selects place, shows inline place detail in the bottom sheet at 45%
  • Tap a place card in the list → same as tapping a marker (opens inline drawer)
  • Tap "← Back to results" → clears selection, returns to place list
  • Tap the search bar → opens the search modal (/search-modal)
  • Tap "my location" → requests GPS permission, animates map to current location
  • Tap +/− zoom buttons → zooms in/out with animation
  • When bottom sheet is fully expanded (85%), a floating "Back to map" pill appears center-bottom

Data flow: useMapStore.regionusePlaces(0, 50) → tRPC place.searchPlacesByBoundsparsePlace()tieredMarkers() → render primary pins + secondary dots + list

Overlap removal: Uses priority-based two-tier deduplication (see Map Implementation).

Inline Place Detail (Bottom Sheet)

When a marker or list card is tapped, the bottom sheet switches from the place list to an inline place detail view. Shows place name, address, badges, star rating, accessibility attributes (step-free, WC, seating, parking) with thumbs-up/down icons, and a "Write a Review" CTA. A "← Back to results" link at the top clears the selection and returns to the list.

Data flow: The selectPlace() callback sets selectedPlace state and selectedPlaceId in the store, then snaps the bottom sheet to 45%. No navigation or additional API call needed.

Place Detail Screen (app/place/[id].tsx)

Fullscreen detail view for a single place (used for deep links). Shows the same information as the inline detail. In normal usage, the inline bottom sheet detail is preferred.

Data flow: Receives all place data via route params (no additional API call needed). Reads useAuthStore.isAuthenticated to gate review submission.

Profile (app/(tabs)/profile.tsx)

Shows user info (avatar, name, email, role badge) if authenticated, or a sign-in prompt if not. Includes a logout button that clears SecureStore and resets useAuthStore.

Search Modal (app/search-modal.tsx)

Two-tab modal: Search (Google Places text search via place.searchGooglePlaces) and Filters (category chips from category.getAll + ACS attribute toggles). Selecting a search result pans the map; applying filters updates useMapStore.categories and useMapStore.acsAttrs.

Login (app/login.tsx)

Google OAuth sign-in screen with the EnAccess Maps logo. Uses expo-auth-session to get an OAuth token, exchanges it with the backend, stores the session in SecureStore, and updates useAuthStore.

Search List (app/(tabs)/search.tsx)

Hidden tab (no tab bar entry via href: null). Shows a flat list of places in the current map bounds, reusing usePlaces and PlaceCard. Used for list-based browsing.