Skip to main content

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

ConcernChoice
FrameworkReact Native 0.74+ with New Architecture (Fabric + TurboModules)
Build toolingExpo SDK 52+ (Managed Workflow), EAS Build, EAS Submit
NavigationReact Navigation 7 (native stack + bottom tab)
Styling@ghasi/ui-melmastoon NativeWind + StyleSheet hybrid
State — serverTanStack Query v5
State — URL / deep linkReact Navigation params + expo-linking
State — clientZustand + zustand/middleware/persist (MMKV)
Offline storageMMKV (fast KV), React Native MMKV for async, encrypted
i18n@formatjs/intl + ICU MessageFormat
AuthBFF cookie via expo-secure-store for token
Push notificationsexpo-notifications (FCM + APNs)
Mapsreact-native-maps (Apple Maps on iOS, Google Maps on Android)
AnimationsReanimated 3 (off-thread via worklets)
Lists@shopify/flash-list (high-performance, RecyclerView-backed)
Imagesexpo-image (AVIF/WebP, LQIP placeholder, memory cache)
Camerareact-native-vision-camera (R2: ID document capture)
Biometricsexpo-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-maps full-screen with @gorhom/bottom-sheet results 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

FeatureOffline 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

URLDestination
melmastoon://search?dest=Kabul&from=2026-05-01&to=2026-05-03Search 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.
  • accessibilityLabel on all icons and images.
  • Bottom tab bar: accessible={true}, accessibilityRole="tab".
  • List items: accessibilityRole="button" on card pressables.
  • FlashList: accessibilityLabel on 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-updates for 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-maps with 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