An open API service indexing awesome lists of open source software.

https://github.com/pspanoudakis/pubtrail

UX Prize πŸ† winner at KTH! A mobile πŸ“± app for creating, joining, and tracking πŸ€³πŸ”Ž pub crawls 🍺 with real-time location πŸ—ΊοΈπŸ“ features
https://github.com/pspanoudakis/pubtrail

expo firebase-auth firestore mapbox react-native redux-toolkit

Last synced: 5 days ago
JSON representation

UX Prize πŸ† winner at KTH! A mobile πŸ“± app for creating, joining, and tracking πŸ€³πŸ”Ž pub crawls 🍺 with real-time location πŸ—ΊοΈπŸ“ features

Awesome Lists containing this project

README

          

# PubTrail πŸΊπŸ—ΊοΈ

**PubTrail** is a mobile application for creating, joining, and tracking pub crawls with real-time location features. It is built with React Native and Expo. PubTrail lets users create routes between pubs, invite friends, add photos, document each stop with location information and notes, and discover nearby crawls.

## UX Prize πŸ† winning project

This application was implemented as a project for the course **ID2216 Developing Mobile Applications at KTH**, winning the **UX Prize**, which is awarded to projects that stand out for their attention to the use case, usability and well-thought design, possibly creating a need that was previously unknown.

### Team Members
- Filip Todosov
- Bogdan Stefanescu
- Pavlos Spanoudakis

As an award-winning project, we also prepared a [short presentation](./docs/PubTrail_Project_Presentation.pdf) of our app to share our work and inspire our classmates.

## Tech Stack

- **React Native** + **Expo** - cross-platform UI (Android, iOS, Web)
- **TypeScript** - strict type safety across the entire codebase
- **Redux Toolkit** - centralized state management
- **Mapbox** (`@rnmapbox/maps`) - interactive maps with user location tracking
- **Firebase** - authentication (Google OAuth) and backend services
- **Expo Router** - file-based navigation with auth-gated routing

## App Features

### Globally used features

- **User Authentication** - email/password and Google OAuth via Firebase with automatic session handling and logout
- **Auth Navigation** - unauthenticated users are redirected to the login screen; authenticated users land on the home screen
- **Real-Time Location** - GPS permission handling and continuous user location tracking (10-second intervals)
- **Interactive Map** - Mapbox integration with user location puck/marker, point markers, and route lines connecting stops (reused across different screens)
- **Navigation** - Intuitive navigation using drawer menu & quick action buttons
- **Reusable UI Components**
- **Design System** - color palette, spacing scale, typography tokens, and shared styles
- **Persistence** - connect all crawl, stop, and participant data to a Firestore database

### Business logic features

- **Add/Edit a crawl** - create/edit your own pub crawl
- **Add/Edit a crawl stop** - create/edit stops in your pub crawl
- **Pick stop location using suggestions** - suggest food & drink places near the user's location when the user tries to add a new stop, to avoid typing the pub information manually
- **Share Crawl** - generate and share QR codes or invitation links for your crawl
- **Join Crawl via QR Code or link** - scan a QR code or invitation link to join an existing crawl in real time
- **Media Upload** - upload images taken using the camera to attach to a pub crawl
- **Real Nearby Crawl Queries** - see live crawls that are currently nearby you, based on their latest stop
- **Sobriety checker game** - A game that uses device's gyroscope to determine how drunk/sober you are

### Demo screenshots

Map-centric home screen UI, with quick action buttons & drawer menu:


Home screen
Drawer menu

All the information of your active crawl in one place:


Home screen
Drawer menu

Add and edit crawl stops:


Home screen
Drawer menu

Capture your favorite moments from your pub crawls:


Home screen
Drawer menu

Check out nearby crawls, or your browse your previous ones:


Home screen
Drawer menu

Join others' crawls by scanning their QR code:


Home screen

Play a gyroscope-based minigame to check your sobriety level:


Home screen
Drawer menu
Drawer menu

## Project File Structure

```
β”œβ”€β”€ app.config.ts # Expo app configuration (name, icons, plugins, permissions)
β”œβ”€β”€ babel.confg.cjs # Babel config with expo preset and module-resolver aliases
β”œβ”€β”€ eas.json # EAS Build profiles (development, preview, production)
β”œβ”€β”€ firebase.json # Firebase Hosting + Firestore config
β”œβ”€β”€ metro.config.js # Metro bundler config (enables CSS support for web)
β”œβ”€β”€ package.json # Dependencies, scripts, and project metadata
β”œβ”€β”€ pubtrail-firebase-android.json # Firebase Android config (used by app.config.ts)
β”œβ”€β”€ pubtrail-firebase-ios.plist # Firebase iOS config (used by app.config.ts)
β”œβ”€β”€ tsconfig.json # TypeScript config
β”‚
β”œβ”€β”€ assets/
β”‚ └── images/ # App icons and image assets
β”‚
└── src/
β”œβ”€β”€ app/ # Expo Router file-based routes
β”‚ β”œβ”€β”€ _layout.tsx # Root layout: Redux Provider, SafeArea, AuthGate (redirects)
β”‚ β”œβ”€β”€ (auth)/
β”‚ β”‚ β”œβ”€β”€ _layout.tsx # Auth stack layout
β”‚ β”‚ └── login.tsx # Login screen
β”‚ β”œβ”€β”€ (public)/
β”‚ β”‚ β”œβ”€β”€ _layout.tsx # Drawer layout for the map home
β”‚ β”‚ └── index.tsx # Map home + quick actions
β”‚ └── (app)/
β”‚ β”œβ”€β”€ _layout.tsx # App stack header config
β”‚ β”œβ”€β”€ activeCrawl/
β”‚ β”‚ β”œβ”€β”€ index.tsx # Active crawl overview
β”‚ β”‚ β”œβ”€β”€ captureMedia.tsx # Capture photo
β”‚ β”‚ β”œβ”€β”€ newStop.tsx # Add a stop
β”‚ β”‚ └── stops/[stopUid].tsx # Edit stop
β”‚ β”œβ”€β”€ album/[crawlUid].tsx # Crawl media album
β”‚ β”œβ”€β”€ crawlHistory.tsx # Past crawls
β”‚ β”œβ”€β”€ join/[code].tsx # External join link
β”‚ β”œβ”€β”€ joinCrawl.tsx # Join via QR / link
β”‚ β”œβ”€β”€ nearbyCrawls.tsx # Nearby public crawls
β”‚ β”œβ”€β”€ newCrawl.tsx # Create a crawl
β”‚ β”œβ”€β”€ placeholder.tsx # Placeholder screen
β”‚ β”œβ”€β”€ shareCrawl.tsx # Share link / QR
β”‚ β”œβ”€β”€ soberCheck.tsx # Sobriety mini-game
β”‚ └── viewCrawl/
β”‚ └── [crawlUid]/
β”‚ β”œβ”€β”€ index.tsx # Crawl details
β”‚ └── stops/[stopUid].tsx # View stop details
β”‚
β”œβ”€β”€ firebaseConfig.ts # Firebase client and auth initialization
β”œβ”€β”€ firebaseEnv.ts # Firebase env var validation
β”œβ”€β”€ mapboxConfig.ts # Mapbox setup + nearby search helpers
β”œβ”€β”€ weatherService.ts # Weather API helpers + crawl tips
β”‚
β”œβ”€β”€ presenters/ # Business logic layer (connects state to views)
β”‚ β”œβ”€β”€ appLayoutPresenter.tsx
β”‚ β”œβ”€β”€ captureMediaPresenter.tsx
β”‚ β”œβ”€β”€ crawlDetailsPresenter.tsx
β”‚ β”œβ”€β”€ crawlHistoryPresenter.tsx
β”‚ β”œβ”€β”€ editCrawlPresenter.tsx
β”‚ β”œβ”€β”€ editStopPresenter.tsx
β”‚ β”œβ”€β”€ externalJoinPresenter.tsx
β”‚ β”œβ”€β”€ joinCrawlPresenter.tsx
β”‚ β”œβ”€β”€ loginPresenter.tsx
β”‚ β”œβ”€β”€ mapHomePresenter.tsx
β”‚ β”œβ”€β”€ mapPresenter.tsx
β”‚ β”œβ”€β”€ mediaAlbumPresenter.tsx
β”‚ β”œβ”€β”€ nearbyCrawlsPresenter.tsx
β”‚ β”œβ”€β”€ newStopPresenter.tsx
β”‚ β”œβ”€β”€ placeholderPresenter.tsx
β”‚ β”œβ”€β”€ shareCrawlPresenter.tsx
β”‚ β”œβ”€β”€ soberCheckPresenter.tsx
β”‚ └── viewCrawlStopPresenter.tsx
β”‚
β”œβ”€β”€ views/ # Pure UI components (receive data via props)
β”‚ β”œβ”€β”€ ExternalJoinView.tsx
β”‚ β”œβ”€β”€ captureMediaView.tsx
β”‚ β”œβ”€β”€ crawlDetailsView.tsx
β”‚ β”œβ”€β”€ crawlHistoryView.tsx
β”‚ β”œβ”€β”€ editCrawlView.tsx
β”‚ β”œβ”€β”€ editStopView.tsx
β”‚ β”œβ”€β”€ joinCrawlView.tsx
β”‚ β”œβ”€β”€ loginView.tsx
β”‚ β”œβ”€β”€ mapView.tsx
β”‚ β”œβ”€β”€ mediaAlbumView.tsx
β”‚ β”œβ”€β”€ nearbyCrawlsView.tsx
β”‚ β”œβ”€β”€ newStopView.tsx
β”‚ β”œβ”€β”€ placeholderView.tsx
β”‚ β”œβ”€β”€ shareCrawlView.tsx
β”‚ β”œβ”€β”€ soberCheckView.tsx
β”‚ β”œβ”€β”€ viewCrawlStopView.tsx
β”‚ β”œβ”€β”€ components/ # Map + UI building blocks
β”‚ β”œβ”€β”€ navigation/ # Drawer + overlay UI
β”‚ └── shared/ # View helpers
β”‚
β”œβ”€β”€ redux/ # Redux state management
β”‚ β”œβ”€β”€ actions/ # Thunks and action creators
β”‚ β”‚ β”œβ”€β”€ activeCrawlActions.ts
β”‚ β”‚ β”œβ”€β”€ authActions.ts
β”‚ β”‚ β”œβ”€β”€ mapActions.ts
β”‚ β”‚ β”œβ”€β”€ nearbyCrawlsActions.ts
β”‚ β”‚ β”œβ”€β”€ newCrawlDraftActions.ts
β”‚ β”‚ β”œβ”€β”€ stopEditorActions.ts
β”‚ β”‚ └── weatherActions.ts
β”‚ β”œβ”€β”€ reducers/ # Slice reducers and root reducer
β”‚ β”‚ β”œβ”€β”€ activeCrawlReducer.ts
β”‚ β”‚ β”œβ”€β”€ authReducer.ts
β”‚ β”‚ β”œβ”€β”€ mapReducer.ts
β”‚ β”‚ β”œβ”€β”€ nearbyCrawlsReducer.ts
β”‚ β”‚ β”œβ”€β”€ newCrawlDraftReducer.ts
β”‚ β”‚ β”œβ”€β”€ stopEditorReducer.ts
β”‚ β”‚ β”œβ”€β”€ weatherReducer.ts
β”‚ β”‚ └── index.ts
β”‚ β”œβ”€β”€ selectors/ # Derived state selectors
β”‚ β”‚ β”œβ”€β”€ activeCrawlSelectors.ts
β”‚ β”‚ β”œβ”€β”€ authSelectors.ts
β”‚ β”‚ β”œβ”€β”€ nearbyCrawlsSelectors.ts
β”‚ β”‚ β”œβ”€β”€ newCrawlDraftSelectors.ts
β”‚ β”‚ └── stopEditorSelectors.ts
β”‚ β”œβ”€β”€ gateways/ # Firestore and data access
β”‚ β”‚ β”œβ”€β”€ activeCrawlGateway.ts
β”‚ β”‚ β”œβ”€β”€ firestoreGatewayUtils.ts
β”‚ β”‚ └── userGateway.ts
β”‚ β”œβ”€β”€ mappers/ # Firestore data mappers
β”‚ β”‚ └── activeCrawlMappers.ts
β”‚ └── store/ # Store config and typed hooks
β”‚ β”œβ”€β”€ store.ts
β”‚ └── hooks.ts
β”‚
β”œβ”€β”€ services/ # External service helpers (Cloudinary)
β”œβ”€β”€ hooks/ # App-specific hooks
β”œβ”€β”€ styles/ # Design system
β”œβ”€β”€ types/ # Shared types
└── utils/ # Shared utilities
```

## Getting Started

1. **Install dependencies**
```bash
npm install
```

2. **Set up environment variables** - create a `.env` file:
```
EXPO_PUBLIC_FIREBASE_API_KEY=
EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN=
EXPO_PUBLIC_FIREBASE_PROJECT_ID=
EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET=
EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
EXPO_PUBLIC_FIREBASE_APP_ID=
EXPO_PUBLIC_GOOGLE_OAUTH_CLIENT_ID=
EXPO_PUBLIC_MAPBOX_TOKEN=
EXPO_PUBLIC_OPENWEATHER_API_KEY=
```

3. **Configure native Firebase** (for Android/iOS builds)
- Download `pubtrail-firebase-android.json` from Firebase Console β†’ place at the project root
- Download `pubtrail-firebase-ios.plist` from Firebase Console β†’ place at the project root
- Ensure your package name (`com.anonymous.pubtrail`) matches your Firebase project settings

4. **Run the development server**
```bash
npm run dev
```

5. **Run on Android device/emulator**
```bash
npm run android:dev
```

6. **Build an Android APK locally**
```bash
npm run android:build_apk
```

7. **Publish web update**
```bash
npm run web:publish
```

## 3rd-Party UI Components

We have used these user visible components from external libraries in our project:

| Library | Which component | Where |
|---------|-----------|---------|
| `@rnmapbox/maps` | `MapView`, `Camera`, `MarkerView`, route `ShapeSource`/`LineLayer` | `src/views/components/FullScreenMap.native.tsx`, `src/views/components/FullScreenMap.web.tsx`, `src/views/components/mapOverlay.native.tsx`, `src/views/components/mapOverlay.web.tsx`, `src/views/components/MapUserLocationMarker.tsx` |
| `@react-native-community/datetimepicker` | `DateTimePicker` | `src/views/editStopView.tsx` |
| `react-native-qrcode-svg` | `QRCode` | `src/views/shareCrawlView.tsx` |
| `expo-camera` | `CameraView` | `src/views/captureMediaView.tsx`, `src/views/joinCrawlView.tsx` |
| `expo-sensors` | `Gyroscope` | `src/presenters/soberCheckPresenter.tsx` |
| `@react-navigation/drawer` | `DrawerContentScrollView` | `src/views/navigation/AppDrawerContent.tsx` |
| `react-native-safe-area-context` | `useSafeAreaInsets` | `src/views/navigation/IndexQuickActionsOverlay.tsx`, `src/views/navigation/AppDrawerContent.tsx` |