EITO

Technical Documentation

EITO Architecture & RevenueCat Integration

A feature-rich AI-powered recipe app with multi-platform video extraction, hands-free cooking mode, Dynamic Island integration, real-time social features, and a hardened RevenueCat subscription pipeline with 3-layer entitlement verification.

React Native TypeScript Expo Router Firebase RevenueCat v8 Gemini AI Zustand Live Activity Xcode / TestFlight

01 Scene

Executive Summary

EITO is a production-ready React Native recipe app that extracts recipes from YouTube, TikTok, Instagram, websites, and physical cookbooks using Gemini AI. It features hands-free cooking mode with iOS Dynamic Island, real-time social messaging, and a metered freemium model monetized through RevenueCat. On every purchase or restore, is_premium and premium_expires_at are synced into the Firebase user profile for fast feature-gating, while RevenueCat remains the single source of truth for billing and entitlement lifecycle.

RevenueCat v8

Singleton RevenueCatService class with 4-step package resolution, fallback purchases, real-time listener, and readiness diagnostics.

3-Layer Premium Sync

RevenueCat → Firebase → Zustand. Every launch reconciles premium status bidirectionally. A useUsageGate hook double-checks RC before blocking any user.

Never False-Negative

Multi-layer entitlement check: configured IDs → any active entitlement → active subscriptions. No paying customer is ever wrongly gated.

02 Scene

Tech Stack

Mobile

React Native + TypeScript

File-based routing via Expo Router. Reanimated 3 for animations, Gesture Handler for swipe interactions.

State

Zustand (10 Stores)

Persisted slices for auth, usage, recipes, cooking, shopping, meal planning, messaging, cookbooks, preferences, and pro showcase.

Backend

Firebase (Auth, Firestore, Storage)

User profiles, recipes, usage counters, real-time messaging, social graph, and media storage.

Payments

RevenueCat v8

Singleton service with hardened purchase pipeline, 4-step package resolution, real-time subscription listener, and trial eligibility checks.

AI Engine

Google Gemini (Vision + Text)

Multi-modal recipe extraction from video transcripts, frames, descriptions, websites, and cookbook page images. AI chef chat for cooking guidance.

Live Activity

iOS Dynamic Island (Swift)

Native SwiftUI widget showing cooking step progress, active timers, and deep-link tap-to-resume from lock screen.

Real-Time Chat

Firestore Subscriptions

1-on-1 and group messaging with recipe/meal-plan/shopping-list sharing, unread badges, and conversation muting.

Notifications

Expo Notifications + Channels

Push notifications for messages, scheduled timer alerts with vibration patterns, and Android notification channels.

Build & Release

Xcode / TestFlight

Native iOS builds with Live Activity extension, shipped and tested via TestFlight.

03 Scene

System Architecture

Client

React Native App

10 Zustand stores, paywall, useUsageGate hook, 15+ service modules.

Backend

Firebase

Auth identities, user profiles, usage limits, recipes, real-time messaging, and social data.

Payments

RevenueCat + StoreKit

Offerings, purchases, restores, entitlement lifecycle, real-time subscription events.

App launch → revenueCatService.login(userId) → bidirectional premium reconciliation (RC ↔ Firebase) → usageStore.syncFromFirebase()addPremiumStatusListener() for real-time subscription changes.

3-Layer Premium Check

Premium status is verified at three independent layers, ensuring no paying customer is ever wrongly gated:

  1. Local (Zustand): user.is_premium in auth store — instant, used for UI rendering
  2. Remote (Firebase): firebaseService.checkUsageLimits() — authoritative for usage metering
  3. Source of Truth (RevenueCat): revenueCatService.isPremium() — real subscription state from App Store

App-Launch Reconciliation

On every app launch in _layout.tsx, premium status is reconciled bidirectionally:

  • RC premium & Firebase not: update Firebase is_premium: true + premium_expires_at
  • RC not premium & Firebase is: update Firebase is_premium: false, clear expiration
  • Auth store and usage store are re-synced after every reconciliation

03b Scene

File Architecture

The project follows a modular architecture with clear separation of concerns.

RecipeApp/
├── app/                          # Expo Router file-based routing
│   ├── _layout.tsx               # Root layout, auth guard, RC listener
│   ├── index.tsx                 # Entry redirect
│   ├── welcome.tsx               # Welcome / sign-in screen
│   ├── onboarding.tsx            # Onboarding flow
│   ├── paywall.tsx               # RevenueCat paywall
│   ├── subscription.tsx          # Subscription management
│   ├── (tabs)/                   # Bottom tab navigator
│   │   ├── _layout.tsx           # Tab bar config
│   │   ├── index.tsx             # Home (recipe feed)
│   │   ├── messages.tsx          # Messaging hub
│   │   ├── planner.tsx           # Meal planner
│   │   ├── shopping.tsx          # Shopping lists
│   │   └── profile.tsx           # User profile
│   ├── cooking/[id].tsx          # Hands-free cooking mode
│   ├── recipe/[id].tsx           # Recipe detail
│   ├── ai-chef-chat.tsx          # AI chef assistant
│   ├── fridge-scan.tsx           # Camera fridge scan
│   ├── fridge-review.tsx         # AI detection review
│   ├── cookbook-scan.tsx          # Cookbook page scanner
│   ├── cookbook-review.tsx        # Cookbook extraction review
│   ├── chat/                     # Chat screens (1-on-1, group)
│   ├── auth/                     # Auth screens (login, register)
│   └── ...                       # 40+ screens total
│
├── stores/                       # Zustand state management (10 stores)
│   ├── authStore.ts              # Auth state, premium status, profile
│   ├── recipeStore.ts            # Recipe CRUD, favorites, search
│   ├── cookingStore.ts           # Active cooking session state
│   ├── shoppingStore.ts          # Shopping lists & items
│   ├── mealPlanStore.ts          # Weekly meal plans
│   ├── messagingStore.ts         # Conversations, messages, unread
│   ├── usageStore.ts             # Free-tier usage limits & gates
│   ├── cookbookStore.ts          # Cookbook collections
│   ├── preferencesStore.ts       # App preferences & settings
│   └── proShowcaseStore.ts       # Premium celebration flow
│
├── services/                     # Business logic & API layer (15 services)
│   ├── revenueCat.service.ts     # Singleton RC class, purchases, sync
│   ├── firebase.service.ts       # Firestore CRUD, auth, storage
│   ├── ai.service.ts             # Gemini AI extraction & chat
│   ├── messaging.service.ts      # Real-time chat & group messaging
│   ├── social.service.ts         # Social graph, follows, blocks
│   ├── youtube.service.ts        # YouTube transcript extraction
│   ├── tiktok.service.ts         # TikTok video extraction
│   ├── instagram.service.ts      # Instagram post extraction
│   ├── liveActivity.ts           # iOS Dynamic Island bridge
│   ├── notifications.service.ts  # Push & local notifications
│   ├── pantry.service.ts         # Fridge scan AI processing
│   ├── user.service.ts           # User profiles & search
│   ├── mealPlanWidget.service.ts # Home Screen widget sync
│   └── savedRecipesWidget.service.ts
│
├── components/                   # Reusable UI components
│   ├── ui/                       # Shared UI (EmptyState, Loading, UsageLimitSheet)
│   ├── recipe/                   # RecipeCard, IngredientList, StepList
│   ├── cooking/                  # CookingProgress, CookingTimer
│   ├── shopping/                 # AddItemModal
│   ├── messaging/                # RecipeMessageCard
│   ├── planner/                  # Meal planner components
│   ├── navigation/               # Tab bar, headers
│   └── layout/                   # Layout wrappers
│
├── hooks/                        # Custom React hooks
│   ├── useUsageGate.ts           # 3-layer premium check before gating
│   ├── useVoiceRecognition.ts    # Speech-to-text for cooking mode
│   ├── useTimer.ts               # Cooking step timers
│   └── useScreenLayout.ts        # Responsive layout helpers
│
├── constants/                    # App-wide constants
│   ├── Theme.ts                  # Colors, typography, spacing
│   └── layout.ts                 # Layout breakpoints
│
├── utils/                        # Utility functions
│   ├── types.ts                  # TypeScript interfaces & types
│   ├── ingredientImages.ts       # AI ingredient image mapping
│   ├── imageCache.ts             # Image caching layer
│   └── speechRecognition.ts      # Voice command processing
│
├── plugins/                      # Expo config plugins
│   ├── withLiveActivities.js     # Live Activity Xcode extension
│   └── live-activity-sources/    # SwiftUI widget source files
│
├── ios/                          # Native iOS (Xcode project)
│   ├── LiveActivity/             # Dynamic Island SwiftUI widget
│   └── RecipeApp.xcodeproj/      # Xcode build config
│
├── functions/                    # Firebase Cloud Functions
├── assets/                       # Images, fonts, animations
├── app.json                      # Expo config
├── eas.json                      # EAS Build profiles
├── tsconfig.json                 # TypeScript config
└── firebase.json                 # Firebase project config

04 Scene

RevenueCat Implementation

All RevenueCat logic lives in a singleton RevenueCatService class (revenueCat.service.ts). The paywall, subscription screen, auth store, root layout, and usage gate hook all consume it.

Env-Driven Configuration

Every product ID and API key is read from environment variables at build time. No hardcoded IDs anywhere in the codebase:

  • EXPO_PUBLIC_REVENUECAT_APPLE_KEY
  • EXPO_PUBLIC_REVENUECAT_IOS_MONTHLY_PRODUCT_ID
  • EXPO_PUBLIC_REVENUECAT_IOS_YEARLY_PRODUCT_ID
  • EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_IDS (comma-separated, e.g. pro,premium,EITO Pro)

getCurrentProductIds() selects iOS or Android IDs based on Platform.OS at runtime.

4-Step Package Resolution

resolvePackageForPlan(offering, plan) walks through four fallback layers to always find a matching package:

  1. Match by packageType (MONTHLY / ANNUAL)
  2. Match by RC default identifier ($rc_monthly / $rc_annual)
  3. Match by exact configured product ID from env
  4. Heuristic regex match on product identifier (/month/i, /(year|annual)/i)

This same resolver is reused in both pricing display and purchase execution.

Purchase Pipeline

From handlePurchase() in the paywall:

  1. revenueCatService.login(userId) — identify the user
  2. Try purchasePackage(pkg) from cached offering
  3. If no package found, call getFallbackProductForPlan()Purchases.getProducts(ids, SUBSCRIPTION)
  4. If fallback found, purchase via purchaseStoreProduct(product)
  5. If nothing works, run getPurchaseReadiness(plan) and show a stage-specific error

Firebase Sync on Purchase

On successful purchase or restore, unlockPremiumAccess() runs:

  1. getExpirationDate(customerInfo) — 4-level date resolution (configured entitlements → any active entitlement → active subscriptions → latestExpirationDate)
  2. Write is_premium: true + premium_expires_at to Firebase profile
  3. Trigger proShowcase celebration flow
  4. Resync usageStore from Firebase to lift feature limits immediately

Premium Status Check

checkPremiumStatus(customerInfo) uses a 3-layer entitlement check to never false-negative:

  1. Check configured entitlement IDs (case-insensitive match)
  2. Fallback: any active entitlement in customerInfo.entitlements.active
  3. Fallback: any active subscription in activeSubscriptions or subscriptionsByProductIdentifier

Fallback resolutions are logged with ACTIVE_ENTITLEMENT_FALLBACK or ACTIVE_SUBSCRIPTION_FALLBACK for debugging.

Readiness Diagnostics

getPurchaseReadiness(plan) returns typed issues so the paywall shows the exact problem instead of a generic error:

  • MISSING_API_KEY → "RevenueCat API key is missing in this build"
  • NO_CURRENT_OFFERING → "No active offering was returned"
  • NO_MATCHING_PACKAGE → "No matching monthly/yearly package found"
  • NO_STORE_PRODUCTS → "Product IDs not returned by App Store Connect"

Usage Gate — RevenueCat Safety Net

The useUsageGate hook is the last line of defense before blocking any free-tier user. When usage is exhausted:

  1. Check local user.is_premium — fast path for known premium users
  2. Sync latest usage from Firebase
  3. If still blocked: double-check RevenueCat directly via isPremium()
  4. If RC says premium: rescue the user, update Firebase + auth store, and allow access
  5. Only then show UsageLimitSheet with usage details

This catches edge cases like cold starts, share extensions, or RC sync delays — no paying customer is ever wrongly gated.

Real-Time Subscription Listener

addPremiumStatusListener() registers a customerInfoUpdated callback in the root layout that fires on every subscription state change:

  • Checks hasPremiumEntitlement(customerInfo) on every event
  • Updates is_premium + premium_expires_at in Firebase
  • Updates auth store locally
  • Re-syncs usage store to reflect new limits immediately
  • Handles subscription renewals, expirations, and cancellations in real time

Listener is cleaned up on sign-out via removePremiumStatusListener() + logout().

Error Handling & Resilience

  • Retry empty offerings: call syncAttributesAndOfferingsIfNeeded(), then retry up to 2 times with incremental delay.
  • Custom log handler: Purchases.setLogHandler() downgrades offerings config errors, purchase cancellations, and network errors to console.warn instead of console.error.
  • User cancellation: caught and silently dismissed — no error alert shown.
  • Network errors: detected via string matching and shown as "Connection Error" with retry option.
  • Auth sign-out: revenueCatService.removePremiumStatusListener() and logout() are called before clearing all 10 stores.
  • Paywall retry: offerings are loaded with up to 3 attempts (1.5s, 3s delays) before showing the paywall with static fallback pricing.
  • Trial eligibility: checkTrialEligibility() defaults to true on error — optimistic, never blocks a potential subscriber.
  • Expo Go detection: paywall detects the Expo Go environment (no IAP support) and shows a specific diagnostic alert.

05 Scene

Feature Highlights

The features that make EITO a production-quality app worth monetizing.

AI Multi-Platform Recipe Extraction

Powered by Google Gemini (vision + text), EITO extracts structured recipes from 7 different sources using a 3-tier fallback strategy:

  1. YouTube: transcript extraction → Gemini parses recipe from audio text
  2. TikTok / Instagram: description + hashtags → Gemini infers recipe
  3. Websites: scrape HTML + JSON-LD schema → Gemini structures the recipe
  4. Cookbook pages: camera capture → Gemini OCR extracts recipe from page images
  5. Fridge scan: camera detects ingredients → Gemini suggests 6 varied recipes
  6. AI Chef chat: conversational multi-turn AI cooking assistant
  7. Manual entry: user-created recipes with AI-assisted suggestions

Every extraction produces structured JSON: title, ingredients (categorized), steps (with timings & pro tips), and nutrition estimates (calories, protein, carbs, fat).

iOS Dynamic Island & Live Activity

A native SwiftUI widget built as an Xcode extension that surfaces cooking progress on the iOS lock screen and Dynamic Island:

  • Step progress: shows current step instruction and "STEP 3 OF 10" indicator
  • Active timer: real-time countdown visible from lock screen
  • Timer completion: green checkmark state when timer finishes
  • Deep-link resume: tap Dynamic Island → opens cooking mode at the exact step with action via recipeapp://cooking/:id?laAction=next&laStep=3
  • Nonce tracking: prevents duplicate actions from repeated taps

Implemented with custom SwiftUI styling (gold progress bars, charcoal text) matching the app's luxury aesthetic.

Hands-Free Cooking Mode

A full-screen immersive cooking experience in cooking/[id].tsx:

  • Step-by-step navigation: swipe gestures (left/right) or voice commands ("next step", "previous step")
  • Multiple simultaneous timers: each step can have its own active timer with notification + vibration on completion
  • Voice feedback: reads step instructions aloud for hands-free operation
  • Ingredient scaling: adjust servings on the fly, all amounts recalculate
  • Mini video player: embedded WebView for the source video alongside instructions
  • Live Activity sync: updates Dynamic Island with every step change and timer event
  • Screen awake: prevents screen from sleeping while cooking

Real-Time Social & Messaging

A complete social layer built on Firestore real-time subscriptions:

  • 1-on-1 messaging: direct conversations with any user
  • Group chats: create groups with admin roles, member management, and custom avatars
  • Rich message types: text, recipe cards (with thumbnail + cooking time), meal plans (day-by-day breakdown), shopping lists (items by category), and images
  • Unread badges: real-time count tracking with local notification alerts
  • Mute / block: per-conversation muting and user blocking
  • Optimistic sending: messages appear instantly with rollback on failure

Metered Freemium Model

Free-tier users get weekly usage limits enforced through Firebase with RevenueCat as the premium unlock:

  • 5 recipes / week — from any source (YouTube, TikTok, etc.)
  • 3 fridge scans / week — AI-powered ingredient detection
  • 20 AI chats / week — conversational AI chef assistant

Premium users get unlimited access to all features. Usage counters use optimistic updates with rollback, double-tap prevention locks, and weekly auto-reset.

The useUsageGate hook checks local → Firebase → RevenueCat before ever blocking a user, ensuring zero false negatives.

Meal Planning & Shopping

Full weekly meal planning and shopping list management:

  • Meal planner: week/day view with 4 meal slots (breakfast, lunch, dinner, snack), nutrition tracking, copy/paste days
  • Shopping lists: multiple lists with custom colors/icons, items grouped by category (produce, meat, dairy, pantry, etc.)
  • Smart sorting: by category, name, checked status, recent, or recipe
  • Sharing: send recipes, meal plans, or shopping lists directly to friends or group chats
  • Home Screen widgets: native widgets syncing saved recipes and upcoming meals to the iOS Home Screen
EITO App Store preview – Cook Smarter, Faster

06 Scene

App Screenshots

Onboarding & Monetization — Welcome screen, paywall with RevenueCat pricing, and recipe discovery.

Discover & Cook — From recipe detail to hands-free cooking mode.

AI Fridge Scan — Scan your fridge, detect ingredients with AI, and get personalized recipe suggestions.

Plan & Share — Meal planning, smart shopping lists, and social recipe sharing.

Share & Grow — Auto-generated recipe cards with App Store Smart Banner for organic growth.