Skip to main content
The Raffi mobile app provides iOS and Android support using React Native and Expo, with cross-device synchronization and native video playback.

Tech Stack

Core Technologies

  • React Native 0.81.5: Native mobile framework
  • React 19.1.0: Latest React with concurrent features
  • Expo SDK 54: Development framework and tooling
  • TypeScript: Type-safe development
  • Expo Router 6: File-based navigation system

Key Dependencies

From raffi-mobile/package.json:13-53:
{
  "expo": "~54.0.29",
  "expo-router": "~6.0.19",           // File-based navigation
  "expo-video": "^3.0.15",            // Native video player
  "react-native": "0.81.5",
  "react": "19.1.0",
  
  // Backend
  "convex": "^1.31.7",                // Real-time sync
  "@ave-id/sdk": "^0.2.2",            // Authentication
  
  // State & Storage
  "zustand": "^5.0.9",                // State management
  "@react-native-async-storage/async-storage": "^2.2.0",
  
  // Media & UI
  "expo-av": "^16.0.8",               // Audio/video playback
  "expo-image": "~3.0.11",            // Optimized images
  "expo-blur": "^15.0.8",             // Native blur effects
  "expo-linear-gradient": "^15.0.8",  // Gradients
  
  // Device Features
  "expo-haptics": "~15.0.8",          // Haptic feedback
  "expo-brightness": "~14.0.8",       // Screen brightness control
  "expo-keep-awake": "^15.0.8",       // Prevent sleep during playback
  "expo-screen-orientation": "^9.0.8", // Orientation lock
  "expo-pip": "^2.0.1",               // Picture-in-picture
  
  // Animation
  "react-native-reanimated": "~4.1.1",
  "react-native-gesture-handler": "~2.28.0"
}

Architecture Overview

┌──────────────────────────────────────────────────────┐
│                  Mobile App Layer                    │
│                                                      │
│  ┌────────────────────────────────────────────────┐ │
│  │          Expo Router (Navigation)              │ │
│  │  app/                                          │ │
│  │  ├── (tabs)/         # Tab navigation          │ │
│  │  │   ├── index.tsx   # Home/Library           │ │
│  │  │   ├── lists.tsx   # Custom lists           │ │
│  │  │   └── settings.tsx # Settings              │ │
│  │  ├── player.tsx      # Video player           │ │
│  │  ├── addons.tsx      # Addon management       │ │
│  │  └── login.tsx       # Authentication         │ │
│  └────────────────────────────────────────────────┘ │
│                                                      │
│  ┌────────────────────────────────────────────────┐ │
│  │          Component Layer                       │ │
│  │  components/                                   │ │
│  │  ├── player/        # Video player UI         │ │
│  │  ├── library/       # Library components      │ │
│  │  ├── meta/          # Metadata display        │ │
│  │  └── ui/            # Generic UI components   │ │
│  └────────────────────────────────────────────────┘ │
│                                                      │
│  ┌────────────────────────────────────────────────┐ │
│  │          State & Data Layer                    │ │
│  │  lib/                                          │ │
│  │  ├── api.ts         # API clients             │ │
│  │  ├── convex.ts      # Convex integration      │ │
│  │  ├── db.ts          # Local database          │ │
│  │  ├── stores/        # Zustand stores          │ │
│  │  ├── torrent/       # Torrent integration     │ │
│  │  └── types.ts       # TypeScript types        │ │
│  └────────────────────────────────────────────────┘ │
│                                                      │
│  ┌────────────────────────────────────────────────┐ │
│  │          Native Layer                          │ │
│  │  modules/torrent-streamer/                     │ │
│  │  - Native torrent streaming module             │ │
│  │  - iOS & Android implementations               │ │
│  └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────┐
│                  Backend Services                    │
│                                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────┐ │
│  │   Convex     │  │   Supabase   │  │  Decoder  │ │
│  │  (Real-time) │  │  (Auth/DB)   │  │  Server   │ │
│  └──────────────┘  └──────────────┘  └───────────┘ │
└──────────────────────────────────────────────────────┘

App Configuration

Expo Config

Location: app.json
{
  "expo": {
    "name": "Raffi",
    "slug": "raffi-mobile",
    "version": "1.0.0",
    "scheme": "raffi",
    "userInterfaceStyle": "dark",
    "newArchEnabled": true,
    
    "ios": {
      "bundleIdentifier": "al.kaleid.mobile",
      "supportsTablet": true,
      "requireFullScreen": false
    },
    
    "android": {
      "package": "al.kaleid.raffimobile",
      "edgeToEdgeEnabled": true,
      "predictiveBackGestureEnabled": false
    },
    
    "experiments": {
      "typedRoutes": true,        // Type-safe navigation
      "reactCompiler": true       // React Compiler optimization
    }
  }
}

Key Features

  • New Architecture: Enabled for better performance
  • Dark Mode Only: userInterfaceStyle: "dark"
  • Edge-to-Edge: Full-screen immersive UI on Android
  • Deep Linking: raffi:// URL scheme
  • Typed Routes: Compile-time route validation

File-Based Routing

Expo Router uses file structure for navigation:
app/
├── _layout.tsx              # Root layout
├── (tabs)/                  # Tab navigation group
│   ├── _layout.tsx         # Tab bar configuration
│   ├── index.tsx           # Home/Library tab
│   ├── lists.tsx           # Lists tab
│   └── settings.tsx        # Settings tab
├── player.tsx              # Full-screen player (modal)
├── addons.tsx              # Addon management
├── history.tsx             # Watch history
├── login.tsx               # Login screen
└── meta/                   # Content details
    └── [id].tsx            # Dynamic route for content
Tab Navigation (app/(tabs)/_layout.tsx):
  • Bottom tabs for main sections
  • Icons from @expo/vector-icons
  • Active/inactive states
Modal Navigation:
  • Player opens as full-screen modal
  • Swipe down to dismiss
  • Orientation changes to landscape
Deep Linking:
// Open content
raffi://meta/tt1234567

// Open player
raffi://player?imdbId=tt1234567&season=1&episode=1

State Management

Zustand Stores

Location: lib/stores/ Raffi uses Zustand for global state:
// Example store structure
interface AppStore {
  user: User | null;
  setUser: (user: User | null) => void;
  
  currentContent: Content | null;
  setCurrentContent: (content: Content) => void;
  
  playbackState: PlaybackState;
  updatePlayback: (state: Partial<PlaybackState>) => void;
}

AsyncStorage

  • Persisted user preferences
  • Offline caching
  • Auth tokens
  • Download queue

Video Playback

Expo Video

Location: app/player.tsx Native video player with hardware acceleration:
import { VideoView } from 'expo-video';

// Features:
// - HLS streaming support
// - PiP (Picture-in-Picture)
// - Background audio
// - Subtitle tracks
// - Audio track selection
// - Playback speed control

Player Features

Orientation Handling:
  • Auto-rotate to landscape for video
  • Lock orientation during playback
  • Restore on exit
Brightness Control:
  • System brightness adjustment
  • Restore original brightness on exit
Keep Awake:
  • Prevent screen sleep during playback
  • Re-enable after pause/exit
Gesture Controls:
  • Swipe for seek
  • Pinch to zoom
  • Double-tap to play/pause

Data Synchronization

Convex Integration

Location: lib/convex.ts
// Real-time features:
// - Watch progress sync
// - Custom lists
// - Watch parties
// - User presence
Sync Strategy:
  1. Local-first updates
  2. Background sync to Convex
  3. Optimistic UI updates
  4. Conflict resolution

Cross-Device Sync

Mobile App ←─────→ Convex ←─────→ Desktop App
    ↓                                   ↓
  Local DB                           Local DB
Synced Data:
  • Watch progress
  • Custom lists
  • Library items
  • Addon configurations
  • User preferences

Native Modules

Torrent Streamer Module

Location: modules/torrent-streamer/ Custom native module for torrent streaming:
modules/torrent-streamer/
├── package.json
├── android/            # Android implementation
│   └── (Java/Kotlin)
├── ios/               # iOS implementation
│   └── (Swift/Objective-C)
└── src/
    └── index.ts       # TypeScript bindings
Plugin Registration (app.json:56):
"plugins": [
  "./plugins/withTorrentStreamer.js"
]
Functionality:
  • Native torrent client integration
  • On-device video streaming
  • Download management
  • Peer connection handling

Backend Integration

API Client

Location: lib/api.ts
// External APIs:
// - TMDB (metadata)
// - Stremio addons (streams)
// - Trakt (tracking)
// - Decoder server (transcoding)

Authentication

Ave ID SDK (@ave-id/sdk):
  • OAuth authentication
  • User profile management
  • Session handling
Supabase:
  • Database access
  • Row-level security
  • Real-time subscriptions

Decoder Server Connection

Mobile can connect to:
  1. Local decoder (on-device torrents)
  2. Remote decoder (desktop app server)
  3. Cloud decoder (hosted transcoding)
Configuration:
const STREAMING_SERVER = __DEV__
  ? 'http://192.168.1.100:6969'  // Desktop on LAN
  : 'https://decoder.example.com'; // Production

UI Components

Component Structure

components/
├── player/
│   ├── VideoPlayer.tsx       # Main player component
│   ├── Controls.tsx          # Playback controls
│   ├── SeekBar.tsx          # Progress bar
│   └── SubtitleRenderer.tsx  # Subtitle display
├── library/
│   ├── ContentCard.tsx       # Media item card
│   ├── ContentList.tsx       # Horizontal scroll list
│   └── ContinueWatching.tsx  # Resume section
├── meta/
│   ├── ContentDetails.tsx    # Details screen
│   ├── SeasonPicker.tsx      # Season/episode selector
│   └── StreamPicker.tsx      # Stream source picker
└── ui/
    ├── Button.tsx            # Generic button
    ├── LoadingSpinner.tsx    # Loading state
    └── ErrorBoundary.tsx     # Error handling

Styling

React Native StyleSheet:
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#090909',
  },
});
Expo Linear Gradient:
import { LinearGradient } from 'expo-linear-gradient';

<LinearGradient
  colors={['transparent', '#090909']}
  style={styles.gradient}
/>
Expo Blur:
import { BlurView } from 'expo-blur';

<BlurView intensity={80} tint="dark">
  {/* Content */}
</BlurView>

Performance Optimizations

React Compiler

Enabled in app.json:61:
"experiments": {
  "reactCompiler": true
}
Automatic memoization of components and hooks.

Image Optimization

Expo Image:
  • Native image caching
  • Progressive loading
  • Blur placeholders
  • WebP support

Reanimated

react-native-reanimated:
  • 60 FPS animations
  • Runs on UI thread
  • Gesture-driven interactions

Code Splitting

Expo Router automatically splits routes:
  • Lazy loading per route
  • Reduced initial bundle size
  • Faster app startup

Development Workflow

Commands

# Start Expo dev server
npm start

# Run on iOS
npm run ios

# Run on Android
npm run android

# Type checking
npx tsc --noEmit

# Linting
npm run lint

Expo Go vs. Development Build

Expo Go (Quick Testing):
  • Scan QR code
  • No native build required
  • Limited to Expo SDK modules
Development Build (Full Features):
  • Custom native modules
  • Torrent streamer support
  • Full feature testing
# Create development build
npx expo run:ios --device
npx expo run:android --device

Production Builds

EAS Build

# iOS
eas build --platform ios

# Android
eas build --platform android

# Both platforms
eas build --platform all

App Store Distribution

iOS:
  • TestFlight beta testing
  • App Store submission
  • Bundle ID: al.kaleid.mobile
Android:
  • Google Play Console
  • APK/AAB signing
  • Package: al.kaleid.raffimobile

Key Design Decisions

Why React Native + Expo?

  • Cross-platform: Single codebase for iOS and Android
  • Native performance: Native video playback and UI
  • Developer experience: Fast refresh, great tooling
  • Ecosystem: Access to native modules and APIs

Why Expo Router?

  • File-based routing: Intuitive navigation structure
  • Type safety: Compile-time route validation
  • Deep linking: Built-in URL handling
  • SEO ready: Meta tags for web export

Why Zustand Over Redux?

  • Simplicity: Less boilerplate
  • Performance: Minimal re-renders
  • TypeScript: Excellent type inference
  • Size: Smaller bundle footprint

Why Custom Torrent Module?

  • Performance: Native torrent handling
  • Reliability: Better than JavaScript implementations
  • Features: Advanced peer management
  • Offline: Works without remote server

Platform-Specific Considerations

iOS

Capabilities:
  • Background audio playback
  • Picture-in-Picture
  • AirPlay support
  • CarPlay ready
Permissions:
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>

Android

Features:
  • Edge-to-edge display
  • Adaptive icons
  • Material You theming support
Permissions:
  • Internet access (auto-included)
  • Storage access for downloads
  • Foreground service for downloads

Build docs developers (and LLMs) love