---
name: sprite-mobile-components
type: reference
title: "Sprite Mobile — Components & Screens"
description: "Catalog of all React Native components and screens in Sprite Mobile with props, state connections, and animation details"
tags: [react-native, components, screens, ui, animations, reanimated]
timestamp: 2026-07-21
---

# Sprite Mobile — Components & Screens

## Overview

Sprite Mobile contains 4 screens and 13 reusable components. All components use `react-native-reanimated` for animations. The component tree is flat — no deep nesting, no context providers beyond Redux. Each component is self-contained with its own styles file.

## Screens

### OnboardingScreen

**Path**: `src/screens/Onboarding/OnboardingScreen.tsx`
**Purpose**: First-time device pairing flow. Shown when `isOnboardingComplete === false`.

**Behavior**:
- On Android, polls `ApplicationService.requestPermission()` every 1s until granted
- Displays `AnimatedLocket` based on paired device's `jewelModel` (0=circle, 1=square, 2=oval)
- Shows `SearchingDeviceIndicator` (loading spinner while scanning, "Device Found" when paired)
- OK button navigates to `Config` and sets `isOnboardingComplete = true`
- Uses `react-i18next` for localized strings

**State connections**:
- `state.ble.pairedDevice` — determines which locket model to display
- `state.application.isOnboardingComplete` — set to `true` on confirm

**Components used**: `AnimatedLocket`, `SearchingDeviceIndicator`, `SpriteLogo`

---

### HomeScreen

**Path**: `src/screens/Home/HomeScreen.tsx`
**Purpose**: Main landing screen with two navigation buttons.

**Behavior**:
- Renders `BackgroundVideo` with `'home'` variant
- Two `AnimatedButtonWithIcon` buttons: Radar (left) and Config (right)
- Radar button has `infinityPulse` animation (random quadrant rotation every 1.4s)
- Config button has `infinityRotate` animation (continuous 8s rotation cycle)

**State connections**: None (pure navigation screen)

**Components used**: `BackgroundVideo`, `AnimatedButtonWithIcon`

---

### ConfigScreen

**Path**: `src/screens/Config/ConfigScreen.tsx`
**Purpose**: Settings screen — activate alerts, set distance threshold, configure emergency URL.

**Behavior**:
- `ActivateApplicationSwitch` — toggles `isAlertActive` in Redux
- `SliderCustom` — sets `emergencyRange` (3-7m, integer steps)
- `UrlButton` — editable text input for emergency URL (defaults to remote config `defaultVideo`)
- `ConfirmButton` — navigates to `Radar`
- Keyboard-aware: uses `KeyboardAvoidingView` with platform-specific behavior
- Backdrop overlay when URL input is focused (dismisses keyboard on tap)

**State connections**:
- `state.application.isAlertActive` — read/write via `ActivateApplicationSwitch`
- `state.application.emergencyRange` — read/write via `SliderCustom`
- `state.application.emergencyUrl` — read/write via `UrlButton`

**Components used**: `BackgroundVideo`, `ActivateApplicationSwitch`, `SliderCustom`, `UrlButton`, `ConfirmButton`

---

### RadarScreen

**Path**: `src/screens/Radar/RadarScreen.tsx`
**Purpose**: Real-time distance visualization to paired BLE device.

**Behavior**:
- On mount, calls `ApplicationService.monitorRssiForPairedDevice()` to start RSSI monitoring
- Renders `RadarComponent` (animated radar visualization)
- `ConnectionStateManagerView` shows paired device name and locket icon
- `ConfirmButton` navigates back to `Home`
- Background video variant: `'radar'`

**State connections**:
- `state.ble.pairedDevice.approximateDistance` — displayed by `RadarComponent`

**Components used**: `BackgroundVideo`, `RadarComponent`, `ConnectionStateManagerView`, `ConfirmButton`

---

## Components

### RadarComponent

**Path**: `src/components/Radar/RadarComponent.tsx`
**Purpose**: Displays current distance to paired device with animated radar visualization.

**Props**: None (reads from Redux directly)

**State connections**:
- `state.ble.pairedDevice.approximateDistance` — displayed as `${distance}m` or `---`

**Sub-components**: `RadarBaseAndEffectComponent`

**Behavior**:
- Shows distance text in uppercase (`TCCC-UnityHeadline-Bold` font)
- Delegates radar animation to `RadarBaseAndEffect`

---

### RadarBaseAndEffect

**Path**: `src/components/Radar/RadarBaseAndEffect.tsx`
**Purpose**: Animated radar sweep with rotating effect overlay.

**Props**: None

**Animations**:
- **Spin**: Continuous 360-degree rotation of `RadarEffectOriginal` image over 5s, linear easing, infinite repeat
- **Dot opacity**: Pulse effect (0→1→0) on 1s cycle, infinite repeat
- **Rotation**: Secondary rotation value (0→1) over 3s, infinite repeat

**Assets**: `RadarBase` (static base image, 230x227px), `RadarEffectOriginal` (rotating overlay, 340x340px)

**Technical notes**: Uses `ReduceMotion.Never` to ensure animations run even with accessibility settings enabled. All three animations run simultaneously via separate `useSharedValue` hooks.

---

### ActivateApplicationSwitch

**Path**: `src/components/ActivateApplicationSwitch/ActivateApplicationSwitch.tsx`
**Purpose**: On/Off toggle switch for the proximity alert system.

**Props**: None (reads/writes Redux)

**State connections**:
- `state.application.isAlertActive` — toggled on press
- Dispatches `Actions.setAlertState(!isAlertActive)`
- Also resets `notificationAlreadyShownForRange` via `ApplicationService`

**Animations**:
- **Button width**: Spring animation from 0→100% on mount
- **Switch thumb**: React Native `Animated.timing` (150ms) interpolating `marginLeft` from 4% to 50%
- **Spring sequence**: Initial `withTiming(0, 300ms)` → `withSpring(100, 1200ms)`

**Visual**: Two-state button with "Off"/"Text" labels, green active state (`#40c965`)

---

### AnimatedButtonWithIcon

**Path**: `src/components/AnimatedButtonWithIcon/AnimatedButtonWithIcon.tsx`
**Purpose**: Navigation button with icon animation (used on Home screen).

**Props**:
```typescript
interface AnimatedButtonWithIconProps {
  icon: 'radar' | 'config';          // Icon to display
  infinityRotate?: boolean;           // Continuous rotation animation
  infinityPulse?: boolean;            // Random quadrant rotation
  delay?: number;                     // Animation delay (ms)
  onPress?: () => void;               // Tap handler
}
```

**Animations**:
- **Width expansion**: Sequenced: `Timing(38, 200ms)` → `Timing(40, 200ms)` → `Spring(width*0.7)`
- **Icon rotation** (config): `interpolate` from 180° to 0° as width expands
- **Infinity rotation** (radar): Continuous 360° rotation over 8s, linear easing, infinite repeat
- **Infinity pulse** (radar): Random quadrant (0/90/180/270°) every 1.4s via `setInterval`

**Assets**: `RadarIcon` or `GearIcon` depending on `icon` prop

**Technical notes**: Icon display is hidden until button width exceeds 42px via `useDerivedValue` with conditional `'none'/'flex'`.

---

### AnimatedLocket

**Path**: `src/components/AnimatedLocket/AnimatedLocket.tsx`
**Purpose**: Displays the appropriate locket/jewel image based on device model.

**Props**:
```typescript
interface AnimatedJewelComponentProps {
  model?: 'circle' | 'square' | 'oval' | number;
}
```

**Mapping**:
- `circle` / `0` → `AnimatedJewel` (circle pendant)
- `square` / `1` → `AnimatedJewel3` (square pendant)
- `oval` / `2` → `AnimatedJewel2` (oval pendant)
- Default → `AnimatedJewel`

**Technical notes**: Uses `useMemo` for image source selection. The image is a static PNG despite the "Animated" naming — the animation is in the CSS transform, not the image itself.

---

### BackgroundVideo

**Path**: `src/components/BackgroundVideo/BackgroundVideo.tsx` (iOS), `BackgroundVideo.android.tsx` (Android)
**Purpose**: Full-screen background image/animation for each screen.

**Props**:
```typescript
interface BackgroundVideoProps {
  video?: 'home' | 'config' | 'radar';
}
```

**Platform behavior**:
- **Android**: Uses `@shopify/react-native-skia` `Canvas` + `Image` with `useAnimatedImageValue` for animated GIF rendering
- **iOS**: Uses React Native `ImageBackground` with `resizeMode="contain"`

**Assets**: `HomeBg`, `ConfigBg`, `RadarBg` (per-screen background images)

**Technical notes**: Memoized with `React.memo`. Positioned absolutely at full screen with `zIndex: -1`/`-10`.

---

### BluetoothStateModal

**Path**: `src/components/BluetoothStateModal/BluetoothStateModal.tsx`
**Purpose**: Displays when Bluetooth is disabled; prompts user to enable it.

**Props**: None

**Behavior**: Static display only — no action buttons. The app relies on the system Bluetooth toggle. The `RootStack` watches `isBluetoothEnabled` and navigates away automatically when BT is enabled.

**Components used**: `SpriteLogo`

**Localized strings**: `bluetooth-disabled`, `enable-bluetooth`

---

### OverlaysStateModal

**Path**: `src/components/BluetoothStateModal/OverlaysStateModal.tsx`
**Purpose**: Android-only overlay permission request screen.

**Props**: None

**Behavior**:
- Polls `ApplicationService.verifyAndroidOverlayPermissions()` every 1s
- When permission is granted: calls `configureNativeBinds()`, checks BLE state, navigates to appropriate screen
- Shows "Grant overlay permissions" button that calls `requestAndroidOverlayPermissions()`
- Navigates based on BLE state + onboarding state once overlay is granted

**Components used**: `SpriteLogo`

**Localized strings**: `overlay-disabled`, `enable-overlay`, `grant-overlay`

---

### ConfirmButton

**Path**: `src/components/ConfirmButton/ConfirmButton.tsx`
**Purpose**: Back/Confirm navigation button pair (used on Config and Radar screens).

**Props**:
```typescript
interface ConfirmButtonProps {
  onConfirm?: () => void;     // Custom confirm action
  onBack?: () => void;        // (unused, defaults to Home)
  style?: ViewStyle;          // Container style override
}
```

**Animations**:
- **Width expansion**: `Timing(96, 200ms)` → `Timing(128, 200ms)` → `Spring(width)` — expands to full screen width
- **Arrow button width**: `interpolate` from 16→42px as container expands

**Behavior**:
- Left arrow button: navigates to `Home`
- Right confirm button: calls `onConfirm` if provided, else navigates to `Home`
- Platform-specific bottom margin: 4% (Android) vs 12% (iOS)

---

### ConnectionStateManagerView

**Path**: `src/components/ConnectionStateManagerView/ConnectionStateManagerView.tsx`
**Purpose**: Shows paired device name, locket icon, and connection status.

**Props**:
```typescript
interface ConnectionStateManagerViewProps {
  showDevice?: boolean;        // Show locket image
  showBluetothState?: boolean; // Show Bluetooth switch
  message?: string | null;     // Custom status message
}
```

**State connections**:
- `state.ble.pairedDevice` — name, jewelModel

**Behavior**:
- Long-press on locket triggers unpair confirmation dialog
- Unpair resets `ApplicationService` state and navigates to `Onboarding`
- Animated width expansion: `Timing(10, 300ms)` → `Timing(width*0.84, 900ms)`

**Components used**: `AnimatedLocket`

---

### SearchingDeviceIndicator

**Path**: `src/components/SearchingDeviceIndicator/SearchingDeviceIndicator.tsx`
**Purpose**: Shows scanning status during device discovery.

**Props**: None

**State connections**:
- `state.ble.pairedDevice` — if undefined, shows spinner; if defined, shows "Device Found"

**Behavior**:
- Searching: Green border (`#40c965`), `ActivityIndicator` spinner
- Found: Solid green background (`#40c965`), "Device Found" text

**Localized strings**: `device-found`

---

### SliderCustom

**Path**: `src/components/SliderCustom/SliderCustom.tsx`
**Purpose**: Distance threshold slider (3-7m range).

**Props**: None (reads/writes Redux)

**State connections**:
- `state.application.emergencyRange` — initial value, updated on slide complete

**Behavior**:
- Range: 3m to 7m, integer steps only
- Green track (`#3DCB65`), dark track (`#04240D`), white thumb
- Animated text label follows thumb position (Reanimated `useDerivedValue`)
- Width measured via `useAnimatedRef` + `measure()` for accurate label positioning

**Animations**:
- **Container width**: `Spring(100, 1200ms)` with `ReduceMotion.Never`
- **Label position**: Derived from slider value × measured width

---

### UrlButton

**Path**: `src/components/UrlButton/UrlButton.tsx`
**Purpose**: Emergency URL input field with animated expansion.

**Props**:
```typescript
interface UrlButtonProps {
  onfocus: () => void;       // Focus callback
  onblur: () => void;        // Blur callback
  onSubmited: () => void;    // Submit callback
  style?: ViewStyle;         // Container style override
}
```

**State connections**:
- `state.application.emergencyUrl` — current URL value
- `state.application.defaultEmergencyUrl` — fallback value

**Behavior**:
- Displays placeholder text from `t('emergency-url')` translation
- Animated width expansion: `Timing(38, 200ms)` → `Timing(56, 200ms)` → `Spring(width*0.84)`
- On submit: navigates to `Radar` (resets navigation stack)
- Uses `forwardRef` to expose `TextInput` ref for programmatic focus/blur

**Technical notes**: The `onSubmitEditing` handler resets the navigation stack before navigating to Radar, ensuring a clean transition.

---

### SpriteLogo

**Path**: `src/components/SpriteLogo/SpriteLogo.tsx`
**Purpose**: Renders the Sprite brand logo.

**Props**: None

**Behavior**: Simple `Image` component with `LogoSprite` asset, `resizeMode="contain"`

**Used in**: `OnboardingScreen`, `BluetoothStateModal`, `OverlaysStateModal`

---

## Component Dependency Graph

```mermaid
graph TD
    subgraph "Screens"
        ONBOARDING["OnboardingScreen"]
        HOME["HomeScreen"]
        CONFIG["ConfigScreen"]
        RADAR["RadarScreen"]
    end

    subgraph "Components"
        AL["AnimatedLocket"]
        SDI["SearchingDeviceIndicator"]
        SLOGO["SpriteLogo"]
        BV["BackgroundVideo"]
        ABW["AnimatedButtonWithIcon"]
        RC["RadarComponent"]
        CSVM["ConnectionStateManagerView"]
        CB["ConfirmButton"]
        AAS["ActivateApplicationSwitch"]
        SC["SliderCustom"]
        UB["UrlButton"]
        BTM["BluetoothStateModal"]
        OSM["OverlaysStateModal"]
        RBE["RadarBaseAndEffect"]
    end

    ONBOARDING --> AL
    ONBOARDING --> SDI
    ONBOARDING --> SLOGO

    HOME --> BV
    HOME --> ABW

    CONFIG --> BV
    CONFIG --> AAS
    CONFIG --> SC
    CONFIG --> UB
    CONFIG --> CB

    RADAR --> BV
    RADAR --> RC
    RADAR --> CSVM
    RADAR --> CB

    RC --> RBE

    BTM --> SLOGO
    OSM --> SLOGO
    CSVM --> AL
```

## Animation Patterns

All animations use `react-native-reanimated` with these common patterns:

1. **Spring expansion** — Elements start at 0 width and spring to full width on mount (`withSequence(withTiming(0), withSpring(target))`)
2. **Infinite rotation** — Radar sweep, gear icon (`withRepeat(withTiming(360), -1)`)
3. **Interpolated transforms** — Thumb position, icon rotation interpolated from animated values
4. **ReduceMotion.Never** — All critical animations bypass accessibility reduce-motion for safety-critical UI (radar, alerts)

## Styling Conventions

- All style files use `StyleSheet.create()` for optimization
- Color palette: Dark backgrounds (`#000`, `#0d2814`), Green accent (`#40c965`, `#3DCB65`), White text
- Font: `TCCC-UnityHeadline-Bold` and `TCCC-UnityHeadline-Medium` (custom fonts loaded via `react-native-asset`)
- Border radius: 25px for buttons (pill shape), 32px for circular elements
- Platform-specific margins: Android gets smaller bottom margins than iOS

## Cross-References

- [[sprite-mobile]] — Hub overview with full project context
- [[sprite-mobile-architecture]] — State management and navigation flow
- [[sprite-mobile-ble]] — BLE integration details
- [[camdom]] — Comparison with CAMDOM's BLE mesh approach
