18 — Consumer Mobile Specification
Surface: Consumer Mobile (iOS + Android — React Native 0.74+ with Expo SDK 52+) BFFs:
bff-consumer-service(discovery),bff-tenant-booking-service(booking) Audience: Guests on phones and tablets discovering and booking properties Unique property: Single multi-tenant app — one app serves all tenants; no per-tenant apps
1. Overview
The Consumer Mobile app is a single React Native application that delivers the full discovery + booking experience across all tenants on iOS and Android. It shares design tokens (@ghasi/ui-melmastoon) and the i18n bundle with the web surfaces, and switches tenant theme context at runtime — the same way the web does, but using React Native's ThemeProvider instead of CSS variables.
Platform targets:
- iOS 15+ (Expo Managed Workflow, EAS Build)
- Android 9+ (Pixel 4a equivalent as baseline device)
App paradigm: One app, one listing on App Store / Google Play. Tenant branding is runtime context; not compiled in.
2. Tech stack
| Concern | Choice |
|---|---|
| Framework | React Native 0.74+ with New Architecture (Fabric + TurboModules) |
| Build tooling | Expo SDK 52+ (Managed Workflow), EAS Build, EAS Submit |
| Navigation | React Navigation 7 (native stack + bottom tab) |
| Styling | @ghasi/ui-melmastoon NativeWind + StyleSheet hybrid |
| State — server | TanStack Query v5 |
| State — URL / deep link | React Navigation params + expo-linking |
| State — client | Zustand + zustand/middleware/persist (MMKV) |
| Offline storage | MMKV (fast KV), React Native MMKV for async, encrypted |
| i18n | @formatjs/intl + ICU MessageFormat |
| Auth | BFF cookie via expo-secure-store for token |
| Push notifications | expo-notifications (FCM + APNs) |
| Maps | react-native-maps (Apple Maps on iOS, Google Maps on Android) |
| Animations | Reanimated 3 (off-thread via worklets) |
| Lists | @shopify/flash-list (high-performance, RecyclerView-backed) |
| Images | expo-image (AVIF/WebP, LQIP placeholder, memory cache) |
| Camera | react-native-vision-camera (R2: ID document capture) |
| Biometrics | expo-local-authentication |
3. Navigation structure
Tab bar (bottom)
├── Discover ← Consumer meta search (equivalent of web /search)
├── Shortlist ← Saved properties
├── Bookings ← Account bookings (login-gated)
└── Account ← Login, profile, settings
Discover (stack)
├── Home / Search screen
├── Search results
├── Property detail
└── [Tenant booking context]
├── Room selection
├── Rate selection
├── Guest details
├── Payment
└── Confirmation
Modals (over stack)
├── Filter bottom sheet
├── Date range picker
├── Guests picker
├── Photo lightbox
└── Map full-screen
4. Key screens
4.1 Home / Search screen
Anatomy:
- Search bar at top (destination text input + calendar icon + guests icon).
- Below: featured properties (horizontal scroll), popular destinations (chips), nearby (location-gated, 5 cards).
- Pull-to-refresh: re-fetches featured + nearby.
State: Featured loaded at mount (React Query, 5-min TTL). Shortlist loaded if session exists.
4.2 Search results
Layout:
- Full-screen list (FlashList, 20 items per page).
- Map view:
react-native-mapsfull-screen with@gorhom/bottom-sheetresults list. - Toggle list/map via pill switcher at top.
- Filter CTA → opens full-height bottom sheet (C8 §3.2).
Offline: Last-fetched results shown from MMKV cache; "Offline — showing last results" banner.
4.3 Property detail
- Hero carousel (expo-image, swipe gesture via Reanimated).
- Sticky header with property name fades in on scroll.
- Amenities grid.
- Rate panel (sticky at bottom): "from AFN X,XXX / night" + "Book" CTA.
- Booking CTA → navigate to Room selection in tenant booking context.
4.4 Booking funnel
Mirrors the web funnel (§3 of 13-tenant-booking-web-specification.md) but uses native components:
- Date picker:
react-native-calendars(Gregorian + Hijri side-by-side). - Payment step: Adyen React Native Drop-in SDK (card, Apple/Google Pay).
- Progress indicator: step dots at top.
- Back gesture: swipe right (iOS native stack, blocked on payment step).
4.5 Shortlist
- FlashList of shortlisted property cards.
- Swipe-to-remove.
- "Compare" mode: side-by-side toggle for up to 3 (modal bottom sheet).
4.6 Account / Bookings
- Login required (OTP via email).
- Upcoming reservations → tap → Guest Portal web view (in-app WebView, tenant themed).
- Past reservations.
- Profile: name, email, preferred locale, preferred currency.
5. Theme switching (in-tenant context)
When the guest enters the tenant booking context (after selecting a property), the app applies the tenant theme:
// In navigation config
<Stack.Screen
name="TenantBooking"
component={TenantBookingNavigator}
options={{ header: () => <TenantHeader /> }}
/>
// In TenantBookingNavigator:
const { theme } = useTenantBootstrap(tenantSlug);
return (
<ThemeProvider theme={theme}>
{/* Room → Rate → Details → Payment → Confirmation */}
</ThemeProvider>
);
ThemeProvider applies the tenant's ThemeVersion CSS variables translated to React Native StyleSheet objects via the @ghasi/ui-melmastoon/native package.
Loading state: Neutral placeholder theme until bootstrap resolves (< 500 ms p95 for bootstrap).
6. Offline behavior
| Feature | Offline capability |
|---|---|
| Browse last search results | ✅ MMKV cache, shown with timestamp |
| View shortlist | ✅ MMKV cached |
| Start new search | ❌ Requires network |
| View property detail (previously loaded) | ✅ MMKV cache |
| Booking funnel | ❌ All steps require network |
| View confirmed bookings | ✅ MMKV cached list |
7. Push notifications
See C4 §3.1 for full trigger + payload spec.
Permission request: After first booking confirmation.
Notification handling:
- App in foreground: in-app toast (C3 ER-01).
- App in background / closed: OS push notification; tap opens app to relevant screen (deep link via
expo-notifications.addNotificationResponseReceivedListener).
8. Performance budget
See ../common/09-non-functional-requirements.md §1.2 for canonical mobile targets.
Additional mobile-specific targets:
- Property detail renders (after network): < 400 ms p95
- Search results FlashList initial render (20 items): < 150 ms p95 (Reassure benchmark)
- Tenant theme switch (bootstrap): < 500 ms p95
9. Deep linking
| URL | Destination |
|---|---|
melmastoon://search?dest=Kabul&from=2026-05-01&to=2026-05-03 | Search results |
melmastoon://property/<propertyId> | Property detail |
melmastoon://booking/<reservationId> | Booking confirmation or account bookings |
https://melmastoon.app/property/<propertyId> | Universal link → property detail |
Universal links registered in Apple App Site Association + Android Intent Filter (via Expo app.json).
10. Accessibility
- All interactive elements: minimum 44 × 44 pt touch target.
accessibilityLabelon all icons and images.- Bottom tab bar:
accessible={true},accessibilityRole="tab". - List items:
accessibilityRole="button"on card pressables. - FlashList:
accessibilityLabelon each rendered item. - Date picker: VoiceOver/TalkBack navigable grid.
- RTL:
I18nManager.forceRTL(locale.isRTL)at app boot (requires restart for full effect; show prompt if locale changes).
11. Release & CI
- EAS Build:
eas build --platform all --profile production. - EAS Submit: automatic submission to App Store Connect + Google Play.
- OTA updates:
expo-updatesfor JS bundle hot fixes (within app binary constraints). - Version bump:
eas update --channel production.
12. Open Questions
- Should the app support iPad (UISplitViewController-style layout) in R2, or iPhone/Android phone only in R1?
- Google Maps on Android requires an API key and billing account. Consider switching to
react-native-mapswith OpenStreetMap tile provider for cost control. - Biometric login: available at account login. Should it also be offered for accessing confirmed booking details (security gate)?
- App Store category: Travel or Business? Leaning Travel.
References
../common/01-product-overview-frontend.md../catalogs/C6-native-api-capability-catalog.md../catalogs/C4-notification-ux-catalog.md../journeys/guest/J-01-discover-and-compare-on-meta-layer.md../journeys/guest/J-03-multi-step-booking-with-cash-on-arrival.md../journeys/guest/J-04-booking-with-card-payment.md