master e63ff1bf4a4e cached
193 files
452.1 KB
121.3k tokens
149 symbols
1 requests
Download .txt
Showing preview only (496K chars total). Download the full file or copy to clipboard to get everything.
Repository: FaridSafi/react-native-gifted-chat
Branch: master
Commit: e63ff1bf4a4e
Files: 193
Total size: 452.1 KB

Directory structure:
gitextract_6i4y2rb6/

├── .expo-shared/
│   └── assets.json
├── .github/
│   ├── FUNDING.yml
│   ├── copilot-instructions.md
│   ├── stale.yml
│   └── workflows/
│       ├── main.yml
│       └── stale.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .npmignore
├── CHANGELOG.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── babel.config.cjs
├── codecov.yml
├── eslint.config.js
├── example/
│   ├── .gitignore
│   ├── README.md
│   ├── app/
│   │   ├── (tabs)/
│   │   │   ├── _layout.tsx
│   │   │   ├── explore.tsx
│   │   │   └── index.tsx
│   │   ├── _layout.tsx
│   │   ├── chat/
│   │   │   ├── _layout.tsx
│   │   │   ├── basic.tsx
│   │   │   ├── customized-rendering.tsx
│   │   │   ├── links.tsx
│   │   │   ├── reply.tsx
│   │   │   └── slack.tsx
│   │   └── modal.tsx
│   ├── app.json
│   ├── babel.config.js
│   ├── components/
│   │   ├── chat-examples/
│   │   │   ├── BasicExample.tsx
│   │   │   ├── CustomizedRenderingExample.tsx
│   │   │   ├── LinksExample.tsx
│   │   │   ├── ReplyExample.tsx
│   │   │   └── SlackExample.tsx
│   │   ├── external-link.tsx
│   │   ├── haptic-tab.tsx
│   │   ├── hello-wave.tsx
│   │   ├── parallax-scroll-view.tsx
│   │   ├── themed-text.tsx
│   │   ├── themed-view.tsx
│   │   └── ui/
│   │       ├── collapsible.tsx
│   │       ├── icon-symbol.ios.tsx
│   │       └── icon-symbol.tsx
│   ├── constants/
│   │   └── theme.ts
│   ├── eslint.config.js
│   ├── example-expo/
│   │   ├── AccessoryBar.tsx
│   │   ├── CustomActions.tsx
│   │   ├── CustomView/
│   │   │   ├── index.tsx
│   │   │   ├── index.web.tsx
│   │   │   ├── styles.ts
│   │   │   └── types.ts
│   │   ├── data/
│   │   │   ├── earlierMessages.ts
│   │   │   └── messages.ts
│   │   └── mediaUtils.ts
│   ├── example-gifted-chat/
│   │   ├── README.md
│   │   └── src/
│   │       ├── Chats.tsx
│   │       ├── InputToolbar.tsx
│   │       ├── customComponents.tsx
│   │       └── messages.ts
│   ├── example-slack-message/
│   │   ├── README.md
│   │   └── src/
│   │       ├── SlackBubble.tsx
│   │       └── SlackMessage.tsx
│   ├── hooks/
│   │   ├── use-color-scheme.ts
│   │   ├── use-color-scheme.web.ts
│   │   ├── use-theme-color.ts
│   │   └── useKeyboardVerticalOffset.ts
│   ├── ios/
│   │   ├── .gitignore
│   │   ├── .xcode.env
│   │   ├── Podfile
│   │   ├── Podfile.properties.json
│   │   ├── example/
│   │   │   ├── Images.xcassets/
│   │   │   │   ├── AppIcon.appiconset/
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   ├── Info.plist
│   │   │   ├── PrivacyInfo.xcprivacy
│   │   │   ├── SplashScreen.storyboard
│   │   │   └── Supporting/
│   │   │       └── Expo.plist
│   │   ├── example.xcodeproj/
│   │   │   ├── project.pbxproj
│   │   │   └── xcshareddata/
│   │   │       └── xcschemes/
│   │   │           └── example.xcscheme
│   │   └── example.xcworkspace/
│   │       └── contents.xcworkspacedata
│   ├── metro.config.js
│   ├── package.json
│   ├── scripts/
│   │   └── reset-project.js
│   ├── styles/
│   │   └── index.ts
│   ├── tsconfig.json
│   └── utils/
│       └── styleUtils.ts
├── expoSnack/
│   ├── ExpoSnack.tsx
│   ├── README.md
│   └── package.json
├── jest.config.cjs
├── package.json
├── src/
│   ├── Actions.tsx
│   ├── Avatar.tsx
│   ├── Bubble/
│   │   ├── index.tsx
│   │   ├── styles.ts
│   │   └── types.ts
│   ├── Color.ts
│   ├── Composer.tsx
│   ├── Constant.ts
│   ├── Day/
│   │   ├── index.tsx
│   │   ├── styles.ts
│   │   └── types.ts
│   ├── GiftedAvatar.tsx
│   ├── GiftedChat/
│   │   ├── index.tsx
│   │   ├── styles.ts
│   │   └── types.ts
│   ├── GiftedChatContext.ts
│   ├── InputToolbar.tsx
│   ├── LoadEarlierMessages.tsx
│   ├── Message/
│   │   ├── index.tsx
│   │   ├── styles.ts
│   │   └── types.ts
│   ├── MessageAudio.tsx
│   ├── MessageImage.tsx
│   ├── MessageReply.tsx
│   ├── MessageText.tsx
│   ├── MessageVideo.tsx
│   ├── MessagesContainer/
│   │   ├── components/
│   │   │   ├── DayAnimated/
│   │   │   │   ├── index.tsx
│   │   │   │   ├── styles.ts
│   │   │   │   └── types.ts
│   │   │   └── Item/
│   │   │       ├── index.tsx
│   │   │       └── types.ts
│   │   ├── index.tsx
│   │   ├── styles.ts
│   │   └── types.ts
│   ├── Models.ts
│   ├── QuickReplies.tsx
│   ├── Reply/
│   │   ├── index.ts
│   │   └── types.ts
│   ├── ReplyPreview.tsx
│   ├── Send.tsx
│   ├── SystemMessage.tsx
│   ├── Time.tsx
│   ├── TypingIndicator/
│   │   ├── index.tsx
│   │   ├── styles.ts
│   │   └── types.ts
│   ├── __tests__/
│   │   ├── Actions.test.tsx
│   │   ├── Avatar.test.tsx
│   │   ├── Bubble.test.tsx
│   │   ├── Color.test.tsx
│   │   ├── Composer.test.tsx
│   │   ├── Constant.test.tsx
│   │   ├── Day.test.tsx
│   │   ├── DayAnimated.test.tsx
│   │   ├── GiftedAvatar.test.tsx
│   │   ├── GiftedChat.test.tsx
│   │   ├── InputToolbar.test.tsx
│   │   ├── LoadEarlier.test.tsx
│   │   ├── Message.test.tsx
│   │   ├── MessageImage.test.tsx
│   │   ├── MessageReply.test.tsx
│   │   ├── MessageText.test.tsx
│   │   ├── MessagesContainer.test.tsx
│   │   ├── ReplyPreview.test.tsx
│   │   ├── Send.test.tsx
│   │   ├── SystemMessage.test.tsx
│   │   ├── Time.test.tsx
│   │   ├── __snapshots__/
│   │   │   ├── Actions.test.tsx.snap
│   │   │   ├── Avatar.test.tsx.snap
│   │   │   ├── Bubble.test.tsx.snap
│   │   │   ├── Color.test.tsx.snap
│   │   │   ├── Composer.test.tsx.snap
│   │   │   ├── Constant.test.tsx.snap
│   │   │   ├── Day.test.tsx.snap
│   │   │   ├── DayAnimated.test.tsx.snap
│   │   │   ├── GiftedAvatar.test.tsx.snap
│   │   │   ├── GiftedChat.test.tsx.snap
│   │   │   ├── InputToolbar.test.tsx.snap
│   │   │   ├── LoadEarlier.test.tsx.snap
│   │   │   ├── Message.test.tsx.snap
│   │   │   ├── MessageImage.test.tsx.snap
│   │   │   ├── MessageReply.test.tsx.snap
│   │   │   ├── MessageText.test.tsx.snap
│   │   │   ├── ReplyPreview.test.tsx.snap
│   │   │   ├── Send.test.tsx.snap
│   │   │   ├── SystemMessage.test.tsx.snap
│   │   │   └── Time.test.tsx.snap
│   │   ├── data.ts
│   │   └── utils.test.ts
│   ├── components/
│   │   ├── MessageReply.tsx
│   │   ├── ReplyPreview.tsx
│   │   └── TouchableOpacity.tsx
│   ├── hooks/
│   │   ├── useColorScheme.ts
│   │   └── useUpdateLayoutEffect.ts
│   ├── index.ts
│   ├── linkParser.tsx
│   ├── logging.ts
│   ├── styles.ts
│   ├── types.ts
│   └── utils.ts
├── tests/
│   └── setup.ts
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .expo-shared/assets.json
================================================
{
  "5c6d215cbde93d15ae63d2ea43dfe8bf8a79a53146382cf7f3f0089bec2fc5d6": true,
  "d0e86e9f72936ac85597d9cd6415cf22a3c208505d5586ac04de09d4dd305707": true,
  "b884dbf3daca9d0a4de2f6552aa4dbdda9cbff26e2677bd76a514d71af2e7e2c": true,
  "30bf2d1edfc90d2841794660cf30a94bb134b89d4808bd4b05cac2304cf6fad7": true,
  "01d8b00b4e3d1dfab70e1ef3354b373f13bdcc3ad29122b3afacf34afd043960": true,
  "36cb6cfb9a281169f9ba1eb7c345fba1d56a0c7115fbf8c4b3753427aad8edaa": true,
  "3232d6cbd4824ece99982787e431ff1425df1d22288961602506b046c50bc516": true,
  "5c8d230c038116f9327c1a38157e7b5d25e1d6bbfbb0ba4e86310f097c3d0f9f": true,
  "250d0d32ab3051aee4b8f9d26a3299e6b3d8e6ee137dffe8a7e183e5478a2040": true
}


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [faridsafi, kesha-antonov, xcarpentier, johan-dutoit]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with a custom sponsorship URL


================================================
FILE: .github/copilot-instructions.md
================================================
# React Native Gifted Chat

The most complete chat UI for React Native & Web. This is a TypeScript React Native component library with example applications demonstrating usage across React Native, React Native Web, and Expo platforms.

Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.

## Working Effectively

### Bootstrap and build the repository:
- `yarn install` -- NEVER CANCEL: takes 58 seconds. Set timeout to 120+ seconds.
- `yarn build` -- builds TypeScript library, takes 3 seconds. Set timeout to 30+ seconds.
- `yarn lint` -- lints source code, takes 3 seconds. Currently has warnings but no errors. Set timeout to 30+ seconds.
- `yarn test` -- NEVER CANCEL: runs Jest test suite, takes 9 seconds. Set timeout to 60+ seconds.

### Full validation before publishing:
- `yarn prepublishOnly` -- NEVER CANCEL: runs lint + build + test, takes 11 seconds total. Set timeout to 60+ seconds.

### Known Issues:
- If tests fail due to snapshot mismatches after fresh dependency install, run `yarn test -u` to update snapshots
- Snapshot tests may need updates when React Native or dependency versions change

### Example app development:
- `cd example && yarn install` -- NEVER CANCEL: takes 38 seconds. Set timeout to 90+ seconds.
- Install Expo CLI globally: `npm install -g @expo/cli` or use `npx expo` commands
- Native development: `cd example && npx expo start`
- Web development: `cd example && npx expo start --web` (requires additional dependencies)
- The example app starts Metro bundler on http://localhost:8081
- Expect dependency version warnings in offline/CI mode - these are normal

### Type checking and development:
- `yarn tsc:watch` -- runs TypeScript compiler in watch mode for development
- `yarn tsc:write` -- compiles TypeScript and writes output to /lib directory

## Requirements and Setup

### System Requirements:
- Node.js >= 18 (tested with 20.19.4)
- Yarn package manager 1.22.22+ (do NOT use npm for this project)
- TypeScript compiler (included in devDependencies)

### Dependencies are already installed if yarn.lock exists
The repository includes both package-lock.json and yarn.lock but ALWAYS use yarn commands, never npm commands.

## Validation

### Always run full validation before completing changes:
1. `yarn lint` -- check for code style issues (warnings are acceptable, errors are not)
2. `yarn build` -- verify TypeScript compilation succeeds
3. `yarn test` -- NEVER CANCEL: ensure all 19 test suites and 29 tests pass. Takes 31 seconds.

### Manual validation scenarios:
After making code changes, you should test basic functionality by:
1. Building the library: `yarn build` 
2. Running the test suite: `yarn test`
3. For UI changes: Start the example app with `cd example && npx expo start --web` (if web dependencies are installed) or `npx expo start` for native development
4. ALWAYS test that the TypeScript declarations in /lib are correctly generated

### Testing approach:
- All tests are located in `src/__tests__/` directory
- Tests use Jest with React Test Renderer
- Test coverage can be viewed with `yarn test:coverage`
- Snapshot tests are used extensively (27 snapshots)

## Project Structure

### Key directories:
- `/src` -- main library source code (TypeScript)
- `/lib` -- compiled JavaScript output (generated by `yarn build`, do not edit manually)  
- `/example` -- example React Native app demonstrating library usage
- `/src/__tests__` -- Jest test files
- `/.github/workflows/main.yml` -- CI/CD pipeline (tests Node 18 and 20)

### Important files:
- `package.json` -- main project configuration and scripts
- `tsconfig.json` -- TypeScript compiler configuration
- `jest.config.cjs` -- Jest test configuration
- `.eslintrc.cjs` -- ESLint linting rules
- `babel.config.cjs` -- Babel transformation configuration
- `example/package.json` -- example app dependencies

### Build output:
The `yarn build` command generates JavaScript files and TypeScript declaration files in the `/lib` directory. This directory should not be edited manually and is ignored by git but included in npm package.

## Common Tasks

### Making code changes:
1. Edit source files in `/src` directory
2. Run `yarn lint` to check style
3. Run `yarn build` to compile TypeScript
4. Run `yarn test` to verify tests pass
5. Test manually with example app if UI changes

### Adding or modifying tests:
- Tests are in `src/__tests__/` directory
- Follow existing test patterns using React Test Renderer
- Update snapshots if needed with `yarn test -u`
- Ensure all tests pass with `yarn test`

### Working with the example app:
- Example app uses Expo and demonstrates library functionality
- Install dependencies: `cd example && yarn install`
- Start development server: `npx expo start`
- For web: `npx expo start --web` (requires react-native-web and @expo/metro-runtime)

## CI/CD Integration

The repository uses GitHub Actions (`.github/workflows/main.yml`) that:
- Tests on Node.js versions 18 and 20  
- Runs `yarn install` and `yarn build`
- Build typically takes 1-2 minutes on CI

Always ensure your changes pass the full validation pipeline locally before pushing.

## Common Command Reference

### Root directory (library development):
```bash
yarn install        # Install dependencies (58 seconds)
yarn build         # Build TypeScript library (3 seconds)  
yarn lint          # Lint source code (3 seconds)
yarn test          # Run test suite (9 seconds)
yarn prepublishOnly # Full validation pipeline (11 seconds)
yarn tsc:watch     # TypeScript watch mode
```

### Example directory (app development):
```bash
cd example
yarn install       # Install example dependencies (38 seconds)
npx expo start     # Start native development server
npx expo start --web # Start web development server
```

### Time expectations (NEVER CANCEL these operations):
- yarn install: 58 seconds (root), 41 seconds (example)
- yarn build: 3 seconds
- yarn lint: 3 seconds  
- yarn test: 9 seconds (after fresh install and snapshot updates)
- yarn prepublishOnly: 11 seconds total

Always set timeouts to at least double these times to account for system variations.

================================================
FILE: .github/stale.yml
================================================
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 15
# Issues with these labels will never be considered stale
exemptLabels:
  - pinned
  - security
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
  Sorry, but this issue has been automatically marked as stale because it has not had
  recent activity. It will be closed if no further activity occurs. BTW Thank you
  for your contributions 😀 !!!
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false


================================================
FILE: .github/workflows/main.yml
================================================
name: Main CI

on:
  pull_request:
    branches:
      - master

jobs:
  checks:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [20, 22]

    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - name: Node modules
        run: |
          yarn install

      - name: Lint
        run: |
          yarn lint

      - name: Type Check
        run: |
          yarn tsc --noEmit

      - name: Build
        run: |
          yarn build

      - name: Test
        run: |
          yarn test


================================================
FILE: .github/workflows/stale.yml
================================================
name: Close stale issues

on:
  schedule:
    - cron: '0 0 * * *' # runs daily

jobs:
  stale:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/stale@v9
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          days-before-issue-stale: 365
          days-before-issue-close: 0
          stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed now.'
          close-issue-message: 'Closing due to inactivity for over a year.'
          only-issue-labels: ''


================================================
FILE: .gitignore
================================================
.DS_Store
node_modules/
.expo/
npm-debug.log
lib/
TODO.md
.idea
.vscode
Exponent-*.app
*.log
coverage/
web-build/
.eslintcache

# Yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
yarn-error.log

example_bare/vendor
example_bare/**/build
example_bare/ios/Pods
example_bare/android/.gradle


================================================
FILE: .husky/pre-commit
================================================
yarn lint-staged


================================================
FILE: .npmignore
================================================
.expo/
.expo-shared/
.circleci/
.github/
.vscode/
example/
example-expo/
example-slack-message/
example-gifted-chat/
screenshots/
babel.config.js
tests/
README.md
ISSUE_TEMPLATE.md
codecov.yml
media/
App.tsx
app.json
metro.config.js
src/
tsconfig.json
tslint.json
yarn.lock
flow-typedefs/
.flowconfig
yarn-error.log
web-build/
types.d.ts


================================================
FILE: CHANGELOG.md
================================================
# Changelog

## [3.3.2] - 2026-01-22

### 🐛 Bug Fixes
- Fixed `React.memo` and `React.forwardRef` components not rendering correctly when passed as render props
  - `renderComponentOrElement` now properly handles components with `$$typeof` property
- Fixed layout jump on initial render - content now renders with `opacity: 0` until initialized
- Fixed keyboard vertical offset documentation and examples

### 🔧 Improvements
- Updated `keyboardVerticalOffset` documentation in README with clearer explanation
- Added `hidden` style for smoother initial render transitions

### 📝 Documentation
- Improved `keyboardVerticalOffset` section explaining that it equals distance from screen top to container top
- Added recommendation to use `useHeaderHeight()` from `@react-navigation/elements`

## [3.3.0] - 2026-01-21

### ✨ Features
- **Swipe to Reply**: New swipe-to-reply functionality using `ReanimatedSwipeable` (based on #2692)
  - Replaced deprecated `Swipeable` with `ReanimatedSwipeable` from react-native-gesture-handler
  - Added `reply` prop to `GiftedChat` with grouped configuration options
  - Swipe direction support: `'left'` (swipe left, icon on right) or `'right'` (swipe right, icon on left)
  - Custom swipe action rendering via `renderAction`
  - Built-in animated `ReplyIcon` component
  - `ReplyPreview` component with smooth enter/exit animations
  - Reply message display in `Bubble` component via `messageReply` prop
- **New Props**:
  - `scrollToBottomContentStyle` - style for scroll to bottom button content

### 🐛 Bug Fixes
- Fixed #2702 - typing issues
- Fixed #2708 - component issues
- Fixed #2607 - edge case handling
- Fixed #2701 - rendering issues
- Fixed #2691 - prop handling
- Fixed #2688 - style issues
- Fixed #2687 - component behavior
- Fixed #2618 - scroll issues
- Fixed #2677, #2682, #2602 - multiple fixes
- Fixed #2684, #2686 - component issues
- Fixed `onScroll` type definition
- Fixed messages padding
- Fixed SystemMessage styles
- Added missing worklets for animations
- Removed `ts-expect-error` for `requestAnimationFrame` (now properly typed for React Native)
- Fixed two typing issues (#2698)

### 🔧 Improvements
- Grouped reply-related props into `ReplyProps` interface for cleaner API
- Added `SwipeToReplyProps` for Message-level swipe configuration
- Added `BubbleReplyProps` for Bubble-level reply message styling
- Added example app to lint command with proper path alias support
- Improved reply animations (enter/exit transitions)
- Changes from #2705

### 📝 Documentation
- Updated README with swipe-to-reply feature documentation and examples
- Updated license link
- Added reply message implementation example (#2690)

### 🧪 Testing
- Updated test snapshots
- Added tests for `MessageReply` component
- Added tests for `ReplyPreview` component

## [3.2.3] - 2025-12-XX

### 🐛 Bug Fixes
- Fixed `onScroll` type definition

## [3.2.0] - 2025-11-25

### ✨ Features
- **Custom Link Parser**: Replaced `react-native-autolink` dependency with custom link parser implementation for better control and performance
  - Removed external dependency on `react-native-autolink`
  - Improved link parsing with custom implementation in `linkParser.tsx`
  - Updated `MessageText` component to use new parser
  - Enhanced links example in example app

### 🐛 Bug Fixes
- Adjusted message bubble styles for better rendering
- Updated test snapshots to reflect parser changes

## [3.1.5] - 2025-11-25

### ✨ Features
- **Color Scheme Support**: Added `colorScheme` prop to `GiftedChat` component
  - New `useColorScheme` hook for consistent color scheme handling
  - Automatically adapts UI elements (Composer, InputToolbar, Send) based on color scheme
  - Added comprehensive tests for color scheme functionality

### 📝 Documentation
- Updated README with `colorScheme` prop documentation

## [3.1.4] - 2025-11-25

### 🐛 Bug Fixes
- Added left padding to `TextInput` when no accessory is present for better visual alignment
- Adjusted input toolbar styles for improved layout

## [3.1.3] - 2025-11-25

### 🔧 Improvements
- Removed unused imports for cleaner codebase

## [3.1.2] - 2025-11-24

### 🐛 Bug Fixes
- Fixed message bubble styles for small messages
- Improved rendering of compact message content

### 🧪 Testing
- Updated test snapshots

## [3.1.1] - 2025-11-24

### 🐛 Bug Fixes
- Fixed Bubble component styles for better message rendering
- Corrected style inconsistencies in message bubbles

### 🧪 Testing
- Updated test snapshots to reflect style fixes

## [3.1.0] - 2025-11-24

### 🔧 Improvements
- Refactored component styles for better maintainability
- Updated Expo Snack example with latest changes

### 🧪 Testing
- Updated test snapshots

## [3.0.1] - 2025-11-24

### 🐛 Bug Fixes
- Fixed Composer auto-resize height behavior on web platform

### 🧪 Testing
- Updated test snapshots

## [3.0.0] - 2025-11-23

This is a major release with significant breaking changes, new features, and improvements. The library has been completely rewritten in TypeScript with improved type safety, better keyboard handling, and enhanced customization options.

### 🚨 Breaking Changes

#### Renamed Props (GiftedChat)
- `onInputTextChanged` → moved to `textInputProps.onChangeText` (follows React Native naming pattern)
- `alwaysShowSend` → `isSendButtonAlwaysVisible` (consistent boolean naming convention)
- `onPress` → `onPressMessage` (more specific naming)
- `onLongPress` → `onLongPressMessage` (more specific naming)
- `options` → `actions` (better semantic naming, different type signature)
- `optionTintColor` → `actionSheetOptionTintColor` (clearer naming)
- `renderUsernameOnMessage` → `isUsernameVisible` (consistent boolean naming)
- `showUserAvatar` → `isUserAvatarVisible` (consistent boolean naming)
- `showAvatarForEveryMessage` → `isAvatarVisibleForEveryMessage` (consistent boolean naming)
- `renderAvatarOnTop` → `isAvatarOnTop` (consistent boolean naming)
- `focusOnInputWhenOpeningKeyboard` → `shouldFocusInputOnKeyboardOpen` (consistent boolean naming)
- `messageContainerRef` → `messagesContainerRef` (typo fix)
- `alignTop` → `isAlignedTop` (consistent boolean naming)
- `inverted` → `isInverted` (consistent boolean naming)

#### Removed Props (GiftedChat)
- `bottomOffset` - use `keyboardAvoidingViewProps.keyboardVerticalOffset` instead
- `disableKeyboardController` - removed keyboard controller configuration
- `isKeyboardInternallyHandled` - keyboard handling now always uses react-native-keyboard-controller
- `lightboxProps` - custom Modal implementation replaced react-native-lightbox-v2
- `placeholder` - moved to `textInputProps.placeholder`
- `disableComposer` - moved to `textInputProps.editable={false}`
- `keyboardShouldPersistTaps` - moved to `listProps.keyboardShouldPersistTaps`
- `maxInputLength` - moved to `textInputProps.maxLength`
- `extraData` - moved to `listProps.extraData`
- `infiniteScroll` - use `loadEarlierMessagesProps.isInfiniteScrollEnabled` instead
- `parsePatterns` - removed, automatic link parsing improved

#### Props Moved to MessagesContainer (via spreading)
These props moved from `GiftedChatProps` to `MessagesContainerProps` but are still accessible on `GiftedChat` via prop spreading:
- `messages` - now in MessagesContainerProps
- `isTyping` - now in MessagesContainerProps (via TypingIndicatorProps)
- `loadEarlier` → `loadEarlierMessagesProps.isAvailable`
- `isLoadingEarlier` → `loadEarlierMessagesProps.isLoading`
- `onLoadEarlier` → `loadEarlierMessagesProps.onPress`
- `renderLoadEarlier` - now in MessagesContainerProps
- `renderDay` - now in MessagesContainerProps
- `renderMessage` - now in MessagesContainerProps
- `renderFooter` - now in MessagesContainerProps
- `renderChatEmpty` - now in MessagesContainerProps
- `scrollToBottomStyle` - now in MessagesContainerProps
- `isScrollToBottomEnabled` - now in MessagesContainerProps
- `scrollToBottomComponent` - now in MessagesContainerProps
- `onQuickReply` - now in MessagesContainerProps
- `listViewProps` → `listProps` (renamed in MessagesContainerProps)

#### Type Signature Changes
- `options`: changed from `{ [key: string]: () => void }` to `Array<{ title: string, action: () => void }>`
- `textInputProps`: changed from `object` to `Partial<React.ComponentProps<typeof TextInput>>`
- `renderInputToolbar`: now accepts `React.ComponentType | React.ReactElement | function | null` (can be component, element, function, or null)
- All callback props now use arrow function syntax instead of function syntax for better type inference

#### Dependency Changes
- Removed `react-native-lightbox-v2` (replaced with custom Modal implementation)
- Removed `react-native-iphone-x-helper` (deprecated)
- Removed `react-native-keyboard-controller` as direct dependency
- Added `react-native-keyboard-controller` as peer dependency (>=1.0.0)
- Added `react-native-gesture-handler` as peer dependency (>=2.0.0)
- Added `react-native-reanimated` support for v3 & v4
- Added `react-native-safe-area-context` as peer dependency (>=5.0.0)

### ✨ New Features

#### TypeScript Migration
- Complete conversion from JavaScript to TypeScript/TSX
- Improved type safety and IntelliSense support
- Better type definitions for all components and props
- Refactored types to arrow functions for better readability

#### Keyboard Handling
- New `keyboardTopToolbarHeight` prop for better keyboard customization
- New `keyboardAvoidingViewProps` to pass props to KeyboardAvoidingView from react-native-keyboard-controller
- Improved keyboard behavior and offset handling
- Consolidated keyboard configuration (removed individual keyboard props in favor of `keyboardAvoidingViewProps`)
- Fixed auto-grow text input behavior
- Better keyboard open/close transitions
- New `OverKeyboardView` component for MessageImage to keep keyboard open

#### Message Rendering
- `isDayAnimationEnabled` prop to control day separator animations
- Support for passing custom components in render functions
- Improved message parsing with better link detection
- Parse links in system messages (fixes #2105)
- Better phone number parsing with custom matchers support
- Improved URL parsing (email, phone, URL detection)

#### UI & Styling
- Dark theme support in example app
- Safe area provider included in library
- Improved LoadEarlier messages logic
- Better themed styles implementation
- Fixed press animation for TouchableOpacity
- Replaced deprecated `TouchableWithoutFeedback` with `Pressable`
- Better scroll to bottom button behavior on Android

#### Image Viewing
- Custom Modal implementation replacing react-native-lightbox-v2
- Better image viewing experience with proper insets handling
- Improved MessageImage component

#### Accessibility & UX
- `renderTicks` prop for message status indicators
- Better scroll to bottom wrapper visibility handling
- `useCallbackThrottled` for improved scroll performance
- Allow passing children to SystemMessage
- Improved load earlier messages functionality

### 🐛 Bug Fixes

- Fixed duplicate paragraph tags in README
- Fixed scroll to bottom when `isScrollToBottomEnabled=false` (#2652)
- Fixed TypeScript type inconsistencies and ESLint errors (#2653)
- Fixed automatic scroll to bottom issues (#2630, #2621, #2644)
- Fixed DayAnimated test import and added proper test coverage for renderDay prop
- Fixed not passed `isDayAnimationEnabled` prop
- Fixed MessageContainer scroll to bottom press on Android
- Fixed safer change ScrollToBottomWrapper visibility
- Fixed dependency cycles in imports
- Fixed MessageText container style
- Fixed reanimated issue in MessageContainer
- Fixed construct messages on send in example
- Fixed web support in example
- Fixed #2659 (memoization issues)
- Fixed #2640 (various bug fixes)
- Fixed show location in example
- Fixed errors in keyboard handling
- Fixed load earlier messages functionality
- Fixed Bubble type parameter to re-enable generics on message prop (#2639)
- Fixed listViewProps typing with Partial<FlatListProps> (#2628)
- Fixed MessageContainer to add renderDay prop and insert DayAnimated Component (#2632)
- Fixed dateFormatCalendar default value in README

### 🔧 Improvements

#### Performance
- Memoized values & functions for better performance
- Better scroll performance with throttled callbacks
- Optimized re-renders

#### Code Quality
- Added ESLint with import sorting
- Fixed all examples with ESLint
- Improved code structure and organization
- Better error handling
- Cleaner prop passing and component structure

#### Testing
- All tests converted to TypeScript
- Updated snapshots for new components
- Run tests in correct timezone (Europe/Paris)
- Improved test coverage
- Added comprehensive copilot instructions with validated commands

#### Documentation
- Improved README structure and formatting
- Better prop documentation and grouping
- Added matchers example
- Added working Expo Snack link
- Better feature documentation
- Added maintainer section
- Improved previews and images
- Added export documentation
- Fixed formatting issues and typos
- Better keyboard props documentation

#### Example App
- Updated to latest React Native and Expo
- Added tabs with different chat examples
- Added working link to Expo Snack
- Better example organization
- Added dark theme support
- Removed padding from bottom of toolbar
- Added custom phone matcher example
- Switch to dev build in README
- Android: transparent navigation & status bars by default
- Better project structure with multiple example types

#### Build & Development
- Better dependency management
- Updated to Node.js >= 20
- Yarn 1.22.22+ as package manager
- Added stale workflow for issue management
- Script to rebuild native dependencies
- Improved local development setup

### 📦 Dependencies

#### Added
- `@expo/react-native-action-sheet`: ^4.1.1
- `@types/lodash.isequal`: ^4.5.8
- `dayjs`: ^1.11.19
- `lodash.isequal`: ^4.5.0
- `react-native-zoom-reanimated`: ^1.4.10

#### Peer Dependencies (now required)
- `react`: >=18.0.0
- `react-native`: *
- `react-native-gesture-handler`: >=2.0.0
- `react-native-keyboard-controller`: >=1.0.0
- `react-native-reanimated`: >=3.0.0 || ^4.0.0
- `react-native-safe-area-context`: >=5.0.0

### 🔄 Migration Guide

#### Update Prop Names
```javascript
// Before (v2.8.1)
<GiftedChat
  messages={messages}
  onInputTextChanged={handleTextChange}
  alwaysShowSend={true}
  onPress={handlePress}
  onLongPress={handleLongPress}
  options={{ 'Option 1': action1, 'Option 2': action2 }}
  optionTintColor="#007AFF"
  bottomOffset={100}
  placeholder="Type a message..."
  maxInputLength={1000}
  renderUsernameOnMessage={true}
  showUserAvatar={false}
  showAvatarForEveryMessage={false}
  renderAvatarOnTop={false}
  alignTop={false}
  inverted={true}
  loadEarlier={true}
  isLoadingEarlier={false}
  onLoadEarlier={handleLoadEarlier}
/>

// After (v3.0.0)
<GiftedChat
  messages={messages}
  textInputProps={{
    onChangeText: handleTextChange,
    placeholder: "Type a message...",
    maxLength: 1000
  }}
  isSendButtonAlwaysVisible={true}
  onPressMessage={handlePress}
  onLongPressMessage={handleLongPress}
  actions={[
    { title: 'Option 1', action: action1 },
    { title: 'Option 2', action: action2 }
  ]}
  actionSheetOptionTintColor="#007AFF"
  keyboardAvoidingViewProps={{ keyboardVerticalOffset: 100 }}
  isUsernameVisible={true}
  isUserAvatarVisible={false}
  isAvatarVisibleForEveryMessage={false}
  isAvatarOnTop={false}
  isAlignedTop={false}
  isInverted={true}
  loadEarlierMessagesProps={{
    isAvailable: true,
    isLoading: false,
    onPress: handleLoadEarlier
  }}
/>
```

#### Install Peer Dependencies
```bash
npm install react-native-gesture-handler react-native-keyboard-controller react-native-reanimated react-native-safe-area-context
# or
yarn add react-native-gesture-handler react-native-keyboard-controller react-native-reanimated react-native-safe-area-context
```

#### Update Image Lightbox
The library now uses a custom Modal implementation instead of react-native-lightbox-v2. If you were customizing the lightbox, you'll need to update your customization approach.

### 📝 Notes

- This version includes 170+ commits since v2.8.1
- Full TypeScript support with improved type definitions
- Better React Native compatibility (tested with RN 0.81.5)
- Improved React 19 support
- Better Expo integration

### 👥 Contributors

Special thanks to all contributors who made this release possible, including fixes and improvements from the community.

---

For detailed commit history, see: https://github.com/FaridSafi/react-native-gifted-chat/compare/2.8.1...3.0.0


================================================
FILE: ISSUE_TEMPLATE.md
================================================
#### Issue Description

[FILL THIS OUT]

#### Steps to Reproduce / Code Snippets

[FILL THIS OUT]

#### Expected Results

[FILL THIS OUT]

#### Additional Information

* Nodejs version: [FILL THIS OUT]
* React version: [FILL THIS OUT]
* React Native version: [FILL THIS OUT]
* react-native-gifted-chat version: [FILL THIS OUT]
* Platform(s) (iOS, Android, or both?): [FILL THIS OUT]
* TypeScript version: [FILL THIS OUT]


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2019 Farid from Safi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<p align="center">
  <a href="https://www.npmjs.com/package/react-native-gifted-chat"><img alt="npm version" src="https://badge.fury.io/js/react-native-gifted-chat.svg"/></a>
  <a href="https://www.npmjs.com/package/react-native-gifted-chat"><img alt="npm downloads" src="https://img.shields.io/npm/dm/react-native-gifted-chat.svg"/></a>
  <a href="https://circleci.com/gh/FaridSafi/react-native-gifted-chat"><img src="https://circleci.com/gh/FaridSafi/react-native-gifted-chat.svg?style=shield" alt="build"></a>
  <img src="https://img.shields.io/badge/platforms-iOS%20%7C%20Android%20%7C%20Web-lightgrey.svg" alt="platforms">
  <img src="https://img.shields.io/badge/TypeScript-supported-blue.svg" alt="TypeScript">
  <img src="https://img.shields.io/badge/Expo-compatible-000020.svg" alt="Expo compatible">
</p>

<h1 align="center">React Native Gifted Chat</h1>

<p align="center">
  The most complete chat UI for React Native & Web
</p>

<p align="center">
  <a href="https://snack.expo.dev/@kesha-antonov/gifted-chat-playground" target="_blank">
    <img src="https://img.shields.io/badge/▶️_Try_in_Browser-4630EB?style=for-the-badge&logo=expo&logoColor=white" alt="Try GiftedChat on Expo Snack"/>
  </a>
</p>

---

## ✨ Features

- 🎨 **Fully Customizable** - Override any component with your own implementation
- 📎 **Composer Actions** - Attach photos, files, or trigger custom actions
- ↩️ **Reply to Messages** - Swipe-to-reply with reply preview and message threading
- ⏮️ **Load Earlier Messages** - Infinite scroll with pagination support
- 📋 **Copy to Clipboard** - Long-press messages to copy text
- 🔗 **Smart Link Parsing** - Auto-detect URLs, emails, phone numbers, hashtags, mentions
- 👤 **Avatars** - User initials or custom avatar images
- 🌍 **Localized Dates** - Full i18n support via Day.js
- ⌨️ **Keyboard Handling** - Smart keyboard avoidance for all platforms
- 💬 **System Messages** - Display system notifications in chat
- ⚡ **Quick Replies** - Bot-style quick reply buttons
- ✍️ **Typing Indicator** - Show when users are typing
- ✅ **Message Status** - Tick indicators for sent/delivered/read states
- ⬇️ **Scroll to Bottom** - Quick navigation button
- 🌐 **Web Support** - Works with react-native-web
- 📱 **Expo Support** - Easy integration with Expo projects
- 📝 **TypeScript** - Complete TypeScript definitions included

<p align="center">
  <img width="200" src="https://github.com/user-attachments/assets/c9da88f5-0b20-471c-8cd7-373bdb767517" />
  &nbsp;&nbsp;&nbsp;&nbsp;
  <img width="200" src="https://github.com/user-attachments/assets/f72b17f1-6c2e-43b5-87e7-477011aa3b07" />
  &nbsp;&nbsp;&nbsp;&nbsp;
  <img width="200" src="https://github.com/user-attachments/assets/86711e73-ee3c-4527-b38d-e4dab47a44fe" />
</p>

---

<h3 align="center">Sponsors</h3>

<table align="center" border="0" cellspacing="20">
  <tr>
    <td align="center" valign="middle">
      <a href="https://www.lereacteur.io" target="_blank"><img src="https://raw.githubusercontent.com/FaridSafi/react-native-gifted-chat/master/media/logo_sponsor.png" height="50"></a>
    </td>
    <td align="center" valign="middle">
      <a href="https://getstream.io/chat/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_Chat&utm_term=react-native-gifted-chat" target="_blank"><img src="https://raw.githubusercontent.com/FaridSafi/react-native-gifted-chat/master/media/stream-logo.png" height="35"></a>
    </td>
    <td align="center" valign="middle">
      <a href="https://www.ethora.com" target="_blank"><img src="https://www.dappros.com/wp-content/uploads/2023/12/Ethora-Logo.png" height="50"></a>
    </td>
  </tr>
</table>

<p align="center">
  <a href="https://www.lereacteur.io" target="_blank"><strong>Le Reacteur</strong></a> - Coding Bootcamp in Paris co-founded by Farid Safi
  <br>
  <a href="https://getstream.io/chat/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_Chat&utm_term=react-native-gifted-chat" target="_blank"><strong>Stream</strong></a> - Scalable chat API/Server written in Go (<a href="https://getstream.io/chat/get_started/?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_Chat&utm_term=react-native-gifted-chat" target="_blank">API Tour</a> | <a href="https://dev.to/nickparsons/react-native-chat-with-chuck-norris-3h7m?utm_source=Github&utm_medium=Github_Repo_Content_Ad&utm_content=Developer&utm_campaign=Github_Jan2022_Chat&utm_term=react-native-gifted-chat" target="_blank">Tutorial</a>)
  <br>
  <a href="https://www.ethora.com" target="_blank"><strong>Ethora</strong></a> - A complete app engine featuring GiftedChat (<a href="https://bit.ly/ethorachat" target="_blank">GitHub</a>)
  <br><br>
  📚 <a href="https://amzn.to/3ZmTyb2" target="_blank">React Key Concepts (2nd ed.)</a>
</p>

---

## 📖 Table of Contents

- [Features](#-features)
- [Requirements](#-requirements)
- [Installation](#-installation)
- [Usage](#-usage)
- [Props Reference](#-props-reference)
- [Data Structure](#-data-structure)
- [Platform Notes](#-platform-notes)
- [Example App](#-example-app)
- [Troubleshooting](#-troubleshooting)
- [Contributing](#-contributing)
- [Authors](#-authors)
- [License](#-license)

---

## 📋 Requirements

| Requirement | Version |
|-------------|---------|
| React Native | >= 0.70.0 |
| iOS | >= 13.4 |
| Android | API 21+ (Android 5.0) |
| Expo | SDK 50+ |
| TypeScript | >= 5.0 (optional) |

---

## 📦 Installation

### Expo Projects

```bash
npx expo install react-native-gifted-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
```

### Bare React Native Projects

**Step 1:** Install the packages

Using yarn:
```bash
yarn add react-native-gifted-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
```

Using npm:
```bash
npm install --save react-native-gifted-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller
```

**Step 2:** Install iOS pods

```bash
npx pod-install
```

**Step 3:** Configure react-native-reanimated

Follow the [react-native-reanimated installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/#step-2-add-reanimateds-babel-plugin) to add the Babel plugin.

---

## 🚀 Usage

### Basic Example

```jsx
import React, { useState, useCallback, useEffect } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import { useHeaderHeight } from '@react-navigation/elements'

export function Example() {
  const [messages, setMessages] = useState([])

  // keyboardVerticalOffset = distance from screen top to GiftedChat container
  // useHeaderHeight() returns status bar + navigation header height
  const headerHeight = useHeaderHeight()

  useEffect(() => {
    setMessages([
      {
        _id: 1,
        text: 'Hello developer',
        createdAt: new Date(),
        user: {
          _id: 2,
          name: 'John Doe',
          avatar: 'https://placeimg.com/140/140/any',
        },
      },
    ])
  }, [])

  const onSend = useCallback((messages = []) => {
    setMessages(previousMessages =>
      GiftedChat.append(previousMessages, messages),
    )
  }, [])

  return (
    <GiftedChat
      messages={messages}
      onSend={messages => onSend(messages)}
      user={{
        _id: 1,
      }}
      keyboardAvoidingViewProps={{ keyboardVerticalOffset: headerHeight }}
    />
  )
}
```

> **💡 Tip:** Check out more examples in the [`example`](example) directory including Slack-style messages, quick replies, and custom components.

---

## 📊 Data Structure

Messages, system messages, and quick replies follow the structure defined in [Models.ts](src/Models.ts).

<details>
<summary><strong>Message Object Structure</strong></summary>

```typescript
interface IMessage {
  _id: string | number
  text: string
  createdAt: Date | number
  user: User
  image?: string
  video?: string
  audio?: string
  system?: boolean
  sent?: boolean
  received?: boolean
  pending?: boolean
  quickReplies?: QuickReplies
}

interface User {
  _id: string | number
  name?: string
  avatar?: string | number | (() => React.ReactNode)
}
```

</details>

---

## 📖 Props Reference

### Core Configuration

- **`messages`** _(Array)_ - Messages to display
- **`user`** _(Object)_ - User sending the messages: `{ _id, name, avatar }`
- **`onSend`** _(Function)_ - Callback when sending a message
- **`messageIdGenerator`** _(Function)_ - Generate an id for new messages. Defaults to a simple random string generator.
- **`locale`** _(String)_ - Locale to localize the dates. You need first to import the locale you need (ie. `require('dayjs/locale/de')` or `import 'dayjs/locale/fr'`)
- **`colorScheme`** _('light' | 'dark')_ - Force color scheme (light/dark mode). When set to `'light'` or `'dark'`, it overrides the system color scheme. When `undefined`, it uses the system color scheme. Default is `undefined`.

### Refs

- **`messagesContainerRef`** _(FlatList ref)_ - Ref to the flatlist
- **`textInputRef`** _(TextInput ref)_ - Ref to the text input

### Keyboard & Layout

- **`keyboardProviderProps`** _(Object)_ - Props to be passed to the [`KeyboardProvider`](https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-provider) for keyboard handling. Default values:
  - `statusBarTranslucent: true` - Required on Android for correct keyboard height calculation when status bar is translucent (edge-to-edge mode)
  - `navigationBarTranslucent: true` - Required on Android for correct keyboard height calculation when navigation bar is translucent (edge-to-edge mode)
- **`keyboardAvoidingViewProps`** _(Object)_ - Props to be passed to the [`KeyboardAvoidingView`](https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/components/keyboard-avoiding-view). See **keyboardVerticalOffset** below for proper keyboard handling.
- **`isAlignedTop`** _(Boolean)_ Controls whether or not the message bubbles appear at the top of the chat (Default is false - bubbles align to bottom)
- **`isInverted`** _(Bool)_ - Reverses display order of `messages`; default is `true`

#### Understanding `keyboardVerticalOffset`

The [`keyboardVerticalOffset`](https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/components/keyboard-avoiding-view#keyboardverticaloffset) tells the KeyboardAvoidingView where its container starts relative to the top of the screen. This is essential when GiftedChat is not positioned at the very top of the screen (e.g., when you have a navigation header).

**Default value:** `insets.top` (status bar height from `useSafeAreaInsets()`). This works correctly only when GiftedChat fills the entire screen without a navigation header. If you have a navigation header, you need to pass the correct offset via `keyboardAvoidingViewProps`.

**What the value means:** The offset equals the distance (in points) from the top of the screen to the top of your GiftedChat container. This typically includes:
- Status bar height
- Navigation header height (on iOS, `useHeaderHeight()` already includes status bar)

**How to use:**

```jsx
import { useHeaderHeight } from '@react-navigation/elements'

function ChatScreen() {
  // useHeaderHeight() returns status bar + navigation header height on iOS
  const headerHeight = useHeaderHeight()

  return (
    <GiftedChat
      keyboardAvoidingViewProps={{ keyboardVerticalOffset: headerHeight }}
      // ... other props
    />
  )
}
```

> **Note:** `useHeaderHeight()` requires your chat component to be rendered inside a proper navigation screen (not conditional rendering). If it returns `0`, ensure your chat screen is a real navigation screen with a visible header.

**Why this matters:** Without the correct offset, the keyboard may overlap the input field or leave extra space. The KeyboardAvoidingView uses this value to calculate how much to shift the content when the keyboard appears.

### Text Input & Composer

- **`text`** _(String)_ - Input text; default is `undefined`, but if specified, it will override GiftedChat's internal state. Useful for managing text state outside of GiftedChat (e.g. with Redux). Don't forget to implement `textInputProps.onChangeText` to update the text state.
- **`initialText`** _(String)_ - Initial text to display in the input field
- **`isSendButtonAlwaysVisible`** _(Bool)_ - Always show send button in input text composer; default `false`, show only when text input is not empty
- **`isTextOptional`** _(Bool)_ - Allow sending messages without text (useful for media-only messages); default `false`. Use with `isSendButtonAlwaysVisible` for media attachments.
- **`minComposerHeight`** _(Object)_ - Custom min-height of the composer.
- **`maxComposerHeight`** _(Object)_ - Custom max height of the composer.
- **`minInputToolbarHeight`** _(Integer)_ - Minimum height of the input toolbar; default is `44`
- **`renderInputToolbar`** _(Component | Function)_ - Custom message composer container
- **`renderComposer`** _(Component | Function)_ - Custom text input message composer
- **`renderSend`** _(Component | Function)_ - Custom send button; you can pass children to the original `Send` component quite easily, for example, to use a custom icon ([example](https://github.com/FaridSafi/react-native-gifted-chat/pull/487))
- **`renderActions`** _(Component | Function)_ - Custom action button on the left of the message composer
- **`renderAccessory`** _(Component | Function)_ - Custom second line of actions below the message composer
- **`textInputProps`** _(Object)_ - props to be passed to the [`<TextInput>`](https://reactnative.dev/docs/textinput).

### Actions & Action Sheet

- **`onPressActionButton`** _(Function)_ - Callback when the Action button is pressed (if set, the default `actionSheet` will not be used)
- **`actionSheet`** _(Function)_ - Custom action sheet interface for showing action options
- **`actions`** _(Array)_ - Custom action options for the input toolbar action button; array of objects with `title` (string) and `action` (function) properties
- **`actionSheetOptionTintColor`** _(String)_ - Tint color for action sheet options

### Messages & Message Container

- **`messagesContainerStyle`** _(Object)_ - Custom style for the messages container
- **`renderMessage`** _(Component | Function)_ - Custom message container
- **`renderLoading`** _(Component | Function)_ - Render a loading view when initializing
- **`renderChatEmpty`** _(Component | Function)_ - Custom component to render in the ListView when messages are empty
- **`renderChatFooter`** _(Component | Function)_ - Custom component to render below the MessagesContainer (separate from the ListView)
- **`listProps`** _(Object)_ - Extra props to be passed to the messages [`<FlatList>`](https://reactnative.dev/docs/flatlist). Supports all FlatList props including `maintainVisibleContentPosition` for keeping scroll position when new messages arrive (useful for AI chatbots).

### Message Bubbles & Content

- **`renderBubble`** _(Component | Function(`props: BubbleProps`))_ - Custom message bubble. Receives [BubbleProps](src/Bubble/types.ts) as parameter.
- **`renderMessageText`** _(Component | Function)_ - Custom message text
- **`renderMessageImage`** _(Component | Function)_ - Custom message image
- **`renderMessageVideo`** _(Component | Function)_ - Custom message video
- **`renderMessageAudio`** _(Component | Function)_ - Custom message audio
- **`renderCustomView`** _(Component | Function)_ - Custom view inside the bubble
- **`isCustomViewBottom`** _(Bool)_ - Determine whether renderCustomView is displayed before or after the text, image and video views; default is `false`
- **`onPressMessage`** _(Function(`context`, `message`))_ - Callback when a message bubble is pressed
- **`onLongPressMessage`** _(Function(`context`, `message`))_ - Callback when a message bubble is long-pressed; you can use this to show action sheets (e.g., copy, delete, reply)
- **`imageProps`** _(Object)_ - Extra props to be passed to the [`<Image>`](https://reactnative.dev/docs/image) component created by the default `renderMessageImage`
- **`imageStyle`** _(Object)_ - Custom style for message images
- **`videoProps`** _(Object)_ - Extra props to be passed to the video component created by the required `renderMessageVideo`
- **`messageTextProps`** _(Object)_ - Extra props to be passed to the MessageText component. Useful for customizing link parsing behavior, text styles, and matchers. Supports the following props:
  - `matchers` - Custom matchers for linking message content (like URLs, phone numbers, hashtags, mentions)
  - `linkStyle` - Custom style for links
  - `email` - Enable/disable email parsing (default: true)
  - `phone` - Enable/disable phone number parsing (default: true)
  - `url` - Enable/disable URL parsing (default: true)
  - `hashtag` - Enable/disable hashtag parsing (default: false)
  - `mention` - Enable/disable mention parsing (default: false)
  - `hashtagUrl` - Base URL for hashtags (e.g., 'https://x.com/hashtag')
  - `mentionUrl` - Base URL for mentions (e.g., 'https://x.com')
  - `stripPrefix` - Strip 'http://' or 'https://' from URL display (default: false)
  - `TextComponent` - Custom Text component to use (e.g., from react-native-gesture-handler)

Example:

```tsx
<GiftedChat
  messageTextProps={{
    phone: false, // Disable default phone number linking
    matchers: [
      {
        type: 'phone',
        pattern: /\+?[1-9][0-9\-\(\) ]{7,}[0-9]/g,
        getLinkUrl: (replacerArgs: ReplacerArgs): string => {
          return replacerArgs[0].replace(/[\-\(\) ]/g, '')
        },
        getLinkText: (replacerArgs: ReplacerArgs): string => {
          return replacerArgs[0]
        },
        style: styles.linkStyle,
        onPress: (match: CustomMatch) => {
          const url = match.getAnchorHref()

          const options: {
            title: string
            action?: () => void
          }[] = [
            { title: 'Copy', action: () => setStringAsync(url) },
            { title: 'Call', action: () => Linking.openURL(`tel:${url}`) },
            { title: 'Send SMS', action: () => Linking.openURL(`sms:${url}`) },
            { title: 'Cancel' },
          ]

          showActionSheetWithOptions({
            options: options.map(o => o.title),
            cancelButtonIndex: options.length - 1,
          }, (buttonIndex?: number) => {
            if (buttonIndex === undefined)
              return

            const option = options[buttonIndex]
            option.action?.()
          })
        },
      },
    ],
    linkStyle: { left: { color: 'blue' }, right: { color: 'lightblue' } },
  }}
/>
```

See full example in [LinksExample](example/components/chat-examples/LinksExample.tsx)

### Avatars

- **`renderAvatar`** _(Component | Function)_ - Custom message avatar; set to `null` to not render any avatar for the message
- **`isUserAvatarVisible`** _(Bool)_ - Whether to render an avatar for the current user; default is `false`, only show avatars for other users
- **`isAvatarVisibleForEveryMessage`** _(Bool)_ - When false, avatars will only be displayed when a consecutive message is from the same user on the same day; default is `false`
- **`onPressAvatar`** _(Function(`user`))_ - Callback when a message avatar is tapped
- **`onLongPressAvatar`** _(Function(`user`))_ - Callback when a message avatar is long-pressed
- **`isAvatarOnTop`** _(Bool)_ - Render the message avatar at the top of consecutive messages, rather than the bottom; default is `false`

### Username

- **`isUsernameVisible`** _(Bool)_ - Indicate whether to show the user's username inside the message bubble; default is `false`
- **`renderUsername`** _(Component | Function)_ - Custom Username container

### Date & Time

- **`timeFormat`** _(String)_ - Format to use for rendering times; default is `'LT'` (see [Day.js Format](https://day.js.org/docs/en/display/format))
- **`dateFormat`** _(String)_ - Format to use for rendering dates; default is `'D MMMM'` (see [Day.js Format](https://day.js.org/docs/en/display/format))
- **`dateFormatCalendar`** _(Object)_ - Format to use for rendering relative times; default is `{ sameDay: '[Today]' }` (see [Day.js Calendar](https://day.js.org/docs/en/plugin/calendar))
- **`renderDay`** _(Component | Function)_ - Custom day above a message
- **`dayProps`** _(Object)_ - Props to pass to the Day component:
  - `containerStyle` - Custom style for the day container
  - `wrapperStyle` - Custom style for the day wrapper
  - `textProps` - Props to pass to the Text component (e.g., `style`, `allowFontScaling`, `numberOfLines`)
- **`renderTime`** _(Component | Function)_ - Custom time inside a message
- **`timeTextStyle`** _(Object)_ - Custom text style for time inside messages (supports left/right styles)
- **`isDayAnimationEnabled`** _(Bool)_ - Enable animated day label that appears on scroll; default is `true`

### System Messages

- **`renderSystemMessage`** _(Component | Function)_ - Custom system message

### Load Earlier Messages

- **`loadEarlierMessagesProps`** _(Object)_ - Props to pass to the LoadEarlierMessages component. The button is only visible when `isAvailable` is `true`. Supports the following props:
  - `isAvailable` - Controls button visibility (default: false)
  - `onPress` - Callback when button is pressed
  - `isLoading` - Display loading indicator (default: false)
  - `isInfiniteScrollEnabled` - Enable infinite scroll up when reaching the top of messages container, automatically calls `onPress` (not yet supported for web)
  - `label` - Override the default "Load earlier messages" text
  - `containerStyle` - Custom style for the button container
  - `wrapperStyle` - Custom style for the button wrapper
  - `textStyle` - Custom style for the button text
  - `activityIndicatorStyle` - Custom style for the loading indicator
  - `activityIndicatorColor` - Color of the loading indicator (default: 'white')
  - `activityIndicatorSize` - Size of the loading indicator (default: 'small')
- **`renderLoadEarlier`** _(Component | Function)_ - Custom "Load earlier messages" button

### Typing Indicator

- **`isTyping`** _(Bool)_ - Typing Indicator state; default `false`. If you use`renderFooter` it will override this.
- **`renderTypingIndicator`** _(Component | Function)_ - Custom typing indicator component
- **`typingIndicatorStyle`** _(StyleProp<ViewStyle>)_ - Custom style for the TypingIndicator component.
- **`renderFooter`** _(Component | Function)_ - Custom footer component on the ListView, e.g. `'User is typing...'`; see [CustomizedFeaturesExample.tsx](example/components/chat-examples/CustomizedFeaturesExample.tsx) for an example. Overrides default typing indicator that triggers when `isTyping` is true.

### Quick Replies

See [Quick Replies example in messages.ts](example/example-expo/data/messages.ts)

- **`onQuickReply`** _(Function)_ - Callback when sending a quick reply (to backend server)
- **`renderQuickReplies`** _(Function)_ - Custom all quick reply view
- **`quickReplyStyle`** _(StyleProp<ViewStyle>)_ - Custom quick reply view style
- **`quickReplyTextStyle`** _(StyleProp<TextStyle>)_ - Custom text style for quick reply buttons
- **`quickReplyContainerStyle`** _(StyleProp<ViewStyle>)_ - Custom container style for quick replies
- **`renderQuickReplySend`** _(Function)_ - Custom quick reply **send** view

### Reply to Messages

Gifted Chat supports swipe-to-reply functionality out of the box. When enabled, users can swipe on a message to reply to it, displaying a reply preview in the input toolbar and the replied message above the new message bubble.

> **Note:** This feature uses `ReanimatedSwipeable` from `react-native-gesture-handler` and `react-native-reanimated` for smooth, performant animations.

#### Basic Usage

```tsx
<GiftedChat
  messages={messages}
  onSend={onSend}
  user={{ _id: 1 }}
  reply={{
    swipe: {
      isEnabled: true,
      direction: 'left', // swipe left to reply
    },
  }}
/>
```

#### Reply Props (Grouped)

The `reply` prop accepts an object with the following structure:

```typescript
interface ReplyProps<TMessage> {
  // Swipe gesture configuration
  swipe?: {
    isEnabled?: boolean              // Enable swipe-to-reply; default false
    direction?: 'left' | 'right'     // Swipe direction; default 'left'
    onSwipe?: (message: TMessage) => void  // Callback when swiped
    renderAction?: (                 // Custom swipe action component
      progress: SharedValue<number>,
      translation: SharedValue<number>,
      position: 'left' | 'right'
    ) => React.ReactNode
    actionContainerStyle?: StyleProp<ViewStyle>
  }

  // Reply preview styling (above input toolbar)
  previewStyle?: {
    containerStyle?: StyleProp<ViewStyle>
    textStyle?: StyleProp<TextStyle>
    imageStyle?: StyleProp<ImageStyle>
  }

  // In-bubble reply styling
  messageStyle?: {
    containerStyle?: StyleProp<ViewStyle>
    containerStyleLeft?: StyleProp<ViewStyle>
    containerStyleRight?: StyleProp<ViewStyle>
    textStyle?: StyleProp<TextStyle>
    textStyleLeft?: StyleProp<TextStyle>
    textStyleRight?: StyleProp<TextStyle>
    imageStyle?: StyleProp<ImageStyle>
  }

  // Callbacks and state
  message?: ReplyMessage             // Controlled reply state
  onClear?: () => void               // Called when reply cleared
  onPress?: (message: TMessage) => void  // Called when reply preview tapped

  // Custom renderers
  renderPreview?: (props: ReplyPreviewProps) => React.ReactNode
  renderMessageReply?: (props: MessageReplyProps) => React.ReactNode
}
```

#### ReplyMessage Structure

When a message has a reply, it includes a `replyMessage` property:

```typescript
interface ReplyMessage {
  _id: string | number
  text: string
  user: User
  image?: string
  audio?: string
}
```

#### Advanced Example with External State

```tsx
const [replyMessage, setReplyMessage] = useState<ReplyMessage | null>(null)

<GiftedChat
  messages={messages}
  onSend={messages => {
    const newMessages = messages.map(msg => ({
      ...msg,
      replyMessage: replyMessage || undefined,
    }))
    setMessages(prev => GiftedChat.append(prev, newMessages))
    setReplyMessage(null)
  }}
  user={{ _id: 1 }}
  reply={{
    swipe: {
      isEnabled: true,
      direction: 'right',
      onSwipe: setReplyMessage,
    },
    message: replyMessage,
    onClear: () => setReplyMessage(null),
    onPress: (msg) => scrollToMessage(msg._id),
  }}
/>
```

#### Smooth Animations

The reply preview automatically animates when:
- **Appearing**: Smoothly expands from zero height with fade-in effect
- **Disappearing**: Smoothly collapses with fade-out effect
- **Content changes**: Smoothly transitions when replying to a different message

These animations use `react-native-reanimated` for 60fps performance.

### Scroll to Bottom

- **`isScrollToBottomEnabled`** _(Bool)_ - Enables the scroll to bottom Component (Default is false)
- **`scrollToBottomComponent`** _(Function)_ - Custom Scroll To Bottom Component container
- **`scrollToBottomOffset`** _(Integer)_ - Custom Height Offset upon which to begin showing Scroll To Bottom Component (Default is 200)
- **`scrollToBottomStyle`** _(Object)_ - Custom style for Scroll To Bottom wrapper (position, bottom, right, etc.)
- **`scrollToBottomContentStyle`** _(Object)_ - Custom style for Scroll To Bottom content (size, background, shadow, etc.)

### Maintaining Scroll Position (AI Chatbots)

For AI chat interfaces where long responses arrive and you don't want to disrupt the user's reading position, use [`maintainVisibleContentPosition`](https://reactnative.dev/docs/scrollview#maintainvisiblecontentposition) via `listProps`:

```tsx
// Basic usage - always maintain scroll position
<GiftedChat
  listProps={{
    maintainVisibleContentPosition: {
      minIndexForVisible: 0,
    },
  }}
/>

// With auto-scroll threshold - auto-scroll if within 10 pixels of newest content
<GiftedChat
  listProps={{
    maintainVisibleContentPosition: {
      minIndexForVisible: 0,
      autoscrollToTopThreshold: 10,
    },
  }}
/>

// Conditionally enable based on scroll state (recommended for chatbots)
const [isScrolledUp, setIsScrolledUp] = useState(false)

<GiftedChat
  listProps={{
    onScroll: (event) => {
      setIsScrolledUp(event.contentOffset.y > 50)
    },
    maintainVisibleContentPosition: isScrolledUp
      ? { minIndexForVisible: 0, autoscrollToTopThreshold: 10 }
      : undefined,
  }}
/>
```

---

## 📱 Platform Notes

### Android

<details>
<summary><strong>Keyboard configuration</strong></summary>

If you are using Create React Native App / Expo, no Android specific installation steps are required. Otherwise, we recommend modifying your project configuration:

Make sure you have `android:windowSoftInputMode="adjustResize"` in your `AndroidManifest.xml`:

```xml
<activity
  android:name=".MainActivity"
  android:label="@string/app_name"
  android:windowSoftInputMode="adjustResize"
  android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
```

For **Expo**, you can append `KeyboardAvoidingView` after GiftedChat (Android only):

```jsx
<View style={{ flex: 1 }}>
   <GiftedChat />
   {Platform.OS === 'android' && <KeyboardAvoidingView behavior="padding" />}
</View>
```

</details>

### Web (react-native-web)

<details>
<summary><strong>With create-react-app</strong></summary>

1. Install react-app-rewired: `yarn add -D react-app-rewired`
2. Create `config-overrides.js`:

```js
module.exports = function override(config, env) {
  config.module.rules.push({
    test: /\.js$/,
    exclude: /node_modules[/\\](?!react-native-gifted-chat)/,
    use: {
      loader: 'babel-loader',
      options: {
        babelrc: false,
        configFile: false,
        presets: [
          ['@babel/preset-env', { useBuiltIns: 'usage' }],
          '@babel/preset-react',
        ],
        plugins: ['@babel/plugin-proposal-class-properties'],
      },
    },
  })
  return config
}
```

> **Examples:**
> - [xcarpentier/gifted-chat-web-demo](https://github.com/xcarpentier/gifted-chat-web-demo)
> - [Gatsby example](https://github.com/xcarpentier/clean-archi-boilerplate/tree/develop/apps/web)

</details>

---

## 🧪 Testing

<details>
<summary><strong>Triggering layout events in tests</strong></summary>

`TEST_ID` is exported as constants that can be used in your testing library of choice.

Gifted Chat uses `onLayout` to determine the height of the chat container. To trigger `onLayout` during your tests:

```typescript
const WIDTH = 200
const HEIGHT = 2000

const loadingWrapper = getByTestId(TEST_ID.LOADING_WRAPPER)
fireEvent(loadingWrapper, 'layout', {
  nativeEvent: {
    layout: {
      width: WIDTH,
      height: HEIGHT,
    },
  },
})
```

</details>

---

## 📦 Example App

The repository includes a comprehensive example app demonstrating all features:

```bash
# Clone and install
git clone https://github.com/FaridSafi/react-native-gifted-chat.git
cd react-native-gifted-chat/example
yarn install

# Run on iOS
npx expo run:ios

# Run on Android
npx expo run:android

# Run on Web
npx expo start --web
```

The example app showcases:
- 💬 Basic chat functionality
- 🎨 Custom message bubbles and avatars
- ↩️ Reply to messages with swipe gesture
- ⚡ Quick replies (bot-style)
- ✍️ Typing indicators
- 📎 Attachment actions
- 🔗 Link parsing and custom matchers
- 🌐 Web compatibility

---

## ❓ Troubleshooting

<details>
<summary><strong>TextInput is hidden on Android</strong></summary>

Make sure you have `android:windowSoftInputMode="adjustResize"` in your `AndroidManifest.xml`. See [Android configuration](#android) above.

</details>

<details>
<summary><strong>How to set Bubble color for each user?</strong></summary>

See [this issue](https://github.com/FaridSafi/react-native-gifted-chat/issues/672) for examples.

</details>

<details>
<summary><strong>How to customize InputToolbar styles?</strong></summary>

See [this issue](https://github.com/FaridSafi/react-native-gifted-chat/issues/662) for examples.

</details>

<details>
<summary><strong>How to manually dismiss the keyboard?</strong></summary>

See [this issue](https://github.com/FaridSafi/react-native-gifted-chat/issues/647) for examples.

</details>

<details>
<summary><strong>How to use renderLoading?</strong></summary>

See [this issue](https://github.com/FaridSafi/react-native-gifted-chat/issues/298) for examples.

</details>

---

## 🤔 Have a Question?

1. Check this README first
2. Search [existing issues](https://github.com/FaridSafi/react-native-gifted-chat/issues)
3. Ask on [StackOverflow](https://stackoverflow.com/questions/tagged/react-native-gifted-chat)
4. Open a new issue if needed

---

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Install dependencies (`yarn install`)
4. Make your changes
5. Run tests (`yarn test`)
6. Run linting (`yarn lint`)
7. Build the library (`yarn build`)
8. Commit your changes (`git commit -m 'Add amazing feature'`)
9. Push to the branch (`git push origin feature/amazing-feature`)
10. Open a Pull Request

### Development Setup

```bash
# Install dependencies
yarn install

# Build the library
yarn build

# Run tests
yarn test

# Run linting
yarn lint

# Full validation
yarn prepublishOnly
```

---

## 👥 Authors

**Original Author:** [Farid Safi](https://www.x.com/FaridSafi)

**Co-author:** [Xavier Carpentier](https://www.x.com/xcapetir) - [Hire Xavier](https://xaviercarpentier.com)

**Maintainer:** [Kesha Antonov](https://github.com/kesha-antonov)

> I've been maintaining this project for 2 years, completely in my free time and without any compensation. If you find it helpful, please consider [becoming a sponsor](https://github.com/sponsors/kesha-antonov) to support continued development. 💖

---

## 📄 License

[MIT](LICENSE)

---

<p align="center">
  <sub>Built with ❤️ by the React Native community</sub>
</p>


================================================
FILE: babel.config.cjs
================================================
module.exports = function (api) {
  api.cache(true)

  return {
    presets: [
      '@babel/preset-env',
      'module:@react-native/babel-preset',
      '@babel/preset-typescript',
    ],
    plugins: [
      '@babel/plugin-transform-unicode-property-regex',
      '@babel/plugin-transform-react-jsx',
      'react-native-reanimated/plugin',
    ],
  }
}


================================================
FILE: codecov.yml
================================================
coverage:
  status:
    patch:
      default: off


================================================
FILE: eslint.config.js
================================================
import stylistic from '@stylistic/eslint-plugin'
import typescriptEslint from '@typescript-eslint/eslint-plugin'
import typescriptParser from '@typescript-eslint/parser'
import importPlugin from 'eslint-plugin-import'
import jestPlugin from 'eslint-plugin-jest'
import perfectionistPlugin from 'eslint-plugin-perfectionist'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'

export default [
  {
    ignores: [
      '**/node_modules/**',
      '**/lib/**',
      '**/build/**',
      '**/.expo/**',
      '**/android/**',
      '**/ios/**',
      // Config files
      'example/*.js',
      'example/*.config.js',
      'example/scripts/**',
    ],
  },
  {
    files: ['src/**/*.{js,jsx,ts,tsx}', 'tests/**/*.{js,jsx,ts,tsx}'],
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      parser: typescriptParser,
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
      },
      globals: {
        fetch: 'readonly',
        navigator: 'readonly',
        __DEV__: 'readonly',
        XMLHttpRequest: 'readonly',
        FormData: 'readonly',
        React$Element: 'readonly',
        requestAnimationFrame: 'readonly',

        // Node.js globals for build scripts and configuration files
        require: 'readonly',
        module: 'readonly',
        process: 'readonly',
        global: 'readonly',
        console: 'readonly',
        setTimeout: 'readonly',
        clearTimeout: 'readonly',
        setInterval: 'readonly',
        clearInterval: 'readonly',

        // Jest globals
        describe: 'readonly',
        test: 'readonly',
        it: 'readonly',
        jest: 'readonly',
        expect: 'readonly',
        beforeAll: 'readonly',
        beforeEach: 'readonly',
        afterAll: 'readonly',
        afterEach: 'readonly',
      },
    },
    plugins: {
      '@stylistic': stylistic,
      '@typescript-eslint': typescriptEslint,
      'import': importPlugin,
      'perfectionist': perfectionistPlugin,
      'react': react,
      'react-hooks': reactHooks,
    },
    settings: {
      react: {
        version: 'detect',
      },
      'import/parsers': {
        '@typescript-eslint/parser': ['.ts', '.tsx'],
      },
      'import/resolver': {
        typescript: {
          alwaysTryTypes: true,
          project: './tsconfig.json',
        },
        node: {
          extensions: ['.js', '.jsx', '.ts', '.tsx'],
        },
      },
      'import/core-modules': ['react', 'react-native'],
    },
    rules: {
      // Import rules
      'import/no-unresolved': 'error',
      'import/named': 'error',
      'import/default': 'error',
      'import/namespace': 'error',
      'import/export': 'error',
      'import/no-absolute-path': 'error',
      'import/no-self-import': 'error',
      'import/no-cycle': 'warn',
      'import/no-useless-path-segments': 'error',
      'import/no-duplicates': 'error',
      'import/first': 'error',
      'import/newline-after-import': 'warn',
      'import/extensions': [
        'error',
        'ignorePackages',
        {
          js: 'never',
          jsx: 'never',
          ts: 'never',
          tsx: 'never',
        },
      ],

      // React rules
      'react/react-in-jsx-scope': 'off',
      'react/no-unknown-property': 'off',
      'react/display-name': 'off',
      'react/prop-types': 'off',

      // React Hooks
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': [
        'warn',
        {
          additionalHooks:
            '(useAnimatedStyle|useSharedValue|useAnimatedGestureHandler|useAnimatedScrollHandler|useAnimatedProps|useDerivedValue|useAnimatedRef|useAnimatedReact|useAnimatedReaction|useCallbackDebounced|useCallbackThrottled)',
        },
      ],

      // TypeScript rules
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-unused-vars': ['error'],

      // Stylistic rules
      '@stylistic/semi': ['error', 'never'],
      '@stylistic/member-delimiter-style': [
        'error',
        {
          multiline: {
            delimiter: 'none',
            requireLast: true,
          },
          singleline: {
            delimiter: 'comma',
            requireLast: false,
          },
        },
      ],
      '@stylistic/indent': [
        'error',
        2,
        {
          SwitchCase: 1,
          VariableDeclarator: 'first',
          ignoredNodes: ['TemplateLiteral'],
        },
      ],
      '@stylistic/quotes': ['error', 'single'],
      '@stylistic/jsx-quotes': ['error', 'prefer-single'],
      '@stylistic/comma-dangle': [
        'error',
        {
          arrays: 'always-multiline',
          objects: 'always-multiline',
          imports: 'always-multiline',
          exports: 'never',
          functions: 'never',
        },
      ],
      '@stylistic/arrow-parens': ['error', 'as-needed'],
      '@stylistic/template-curly-spacing': 'off',
      '@stylistic/linebreak-style': ['off', 'unix'],
      '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: false }],
      '@stylistic/jsx-closing-bracket-location': ['error', 'line-aligned'],

      // General rules
      'no-func-assign': 'off',
      'no-class-assign': 'off',
      'no-useless-escape': 'off',
      'no-unused-vars': 'off', // Use @typescript-eslint/no-unused-vars instead
      'no-unreachable': 'error',
      'curly': [2, 'multi', 'consistent'],
      'nonblock-statement-body-position': ['error', 'below'],

      // Perfectionist rules
      'perfectionist/sort-imports': [
        'error',
        {
          groups: [
            'react',
            'external',
            'internal',
            ['parent', 'sibling'],
            'index',
          ],
          customGroups: {
            value: {
              react: ['^react$', '^react-native$'],
            },
          },
          newlinesBetween: 'ignore',
        },
      ],
      'perfectionist/sort-interfaces': 'off',
    },
  },
  // Example app configuration with path aliases
  {
    files: ['example/**/*.{js,jsx,ts,tsx}'],
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      parser: typescriptParser,
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
        project: './example/tsconfig.json',
      },
      globals: {
        fetch: 'readonly',
        navigator: 'readonly',
        __DEV__: 'readonly',
        XMLHttpRequest: 'readonly',
        FormData: 'readonly',
        React$Element: 'readonly',
        requestAnimationFrame: 'readonly',
        require: 'readonly',
        module: 'readonly',
        process: 'readonly',
        global: 'readonly',
        console: 'readonly',
        setTimeout: 'readonly',
        clearTimeout: 'readonly',
        setInterval: 'readonly',
        clearInterval: 'readonly',
      },
    },
    plugins: {
      '@stylistic': stylistic,
      '@typescript-eslint': typescriptEslint,
      'import': importPlugin,
      'perfectionist': perfectionistPlugin,
      'react': react,
      'react-hooks': reactHooks,
    },
    settings: {
      react: {
        version: 'detect',
      },
      'import/parsers': {
        '@typescript-eslint/parser': ['.ts', '.tsx'],
      },
      'import/resolver': {
        typescript: {
          alwaysTryTypes: true,
          project: './example/tsconfig.json',
        },
        node: {
          extensions: ['.js', '.jsx', '.ts', '.tsx'],
        },
      },
      'import/core-modules': ['react', 'react-native', 'expo-router', 'expo-blur', 'expo-haptics', 'expo-symbols', 'expo-system-ui', 'expo-web-browser', 'expo-font', 'expo-splash-screen', 'expo-status-bar', 'react-native-gifted-chat'],
    },
    rules: {
      // Import rules
      'import/no-unresolved': 'error',
      'import/named': 'error',
      'import/default': 'error',
      'import/namespace': 'error',
      'import/export': 'error',
      'import/no-absolute-path': 'error',
      'import/no-self-import': 'error',
      'import/no-cycle': 'warn',
      'import/no-useless-path-segments': 'error',
      'import/no-duplicates': 'error',
      'import/first': 'error',
      'import/newline-after-import': 'warn',
      'import/extensions': [
        'error',
        'ignorePackages',
        {
          js: 'never',
          jsx: 'never',
          ts: 'never',
          tsx: 'never',
        },
      ],

      // React rules
      'react/react-in-jsx-scope': 'off',
      'react/no-unknown-property': 'off',
      'react/display-name': 'off',
      'react/prop-types': 'off',

      // React Hooks
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': [
        'warn',
        {
          additionalHooks:
            '(useAnimatedStyle|useSharedValue|useAnimatedGestureHandler|useAnimatedScrollHandler|useAnimatedProps|useDerivedValue|useAnimatedRef|useAnimatedReact|useAnimatedReaction|useCallbackDebounced|useCallbackThrottled)',
        },
      ],

      // TypeScript rules
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-unused-vars': ['error'],

      // Stylistic rules
      '@stylistic/semi': ['error', 'never'],
      '@stylistic/member-delimiter-style': [
        'error',
        {
          multiline: {
            delimiter: 'none',
            requireLast: true,
          },
          singleline: {
            delimiter: 'comma',
            requireLast: false,
          },
        },
      ],
      '@stylistic/indent': [
        'error',
        2,
        {
          SwitchCase: 1,
          VariableDeclarator: 'first',
          ignoredNodes: ['TemplateLiteral'],
        },
      ],
      '@stylistic/quotes': ['error', 'single'],
      '@stylistic/jsx-quotes': ['error', 'prefer-single'],
      '@stylistic/comma-dangle': [
        'error',
        {
          arrays: 'always-multiline',
          objects: 'always-multiline',
          imports: 'always-multiline',
          exports: 'never',
          functions: 'never',
        },
      ],
      '@stylistic/arrow-parens': ['error', 'as-needed'],
      '@stylistic/template-curly-spacing': 'off',
      '@stylistic/linebreak-style': ['off', 'unix'],
      '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: false }],
      '@stylistic/jsx-closing-bracket-location': ['error', 'line-aligned'],

      // General rules
      'no-func-assign': 'off',
      'no-class-assign': 'off',
      'no-useless-escape': 'off',
      'no-unused-vars': 'off',
      'no-unreachable': 'error',
      'curly': [2, 'multi', 'consistent'],
      'nonblock-statement-body-position': ['error', 'below'],

      // Perfectionist rules
      'perfectionist/sort-imports': [
        'error',
        {
          groups: [
            'react',
            'external',
            'internal',
            ['parent', 'sibling'],
            'index',
          ],
          customGroups: {
            value: {
              react: ['^react$', '^react-native$'],
            },
          },
          newlinesBetween: 'ignore',
        },
      ],
      'perfectionist/sort-interfaces': 'off',
    },
  },
  {
    files: ['tests/**/*', 'src/__tests__/**/*'],
    plugins: {
      jest: jestPlugin,
    },
    rules: {
      ...jestPlugin.configs.recommended.rules,
    },
  },
]


================================================
FILE: example/.gitignore
================================================
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files

# dependencies
node_modules/

# Expo
.expo/
dist/
web-build/
expo-env.d.ts

# Native
.kotlin/
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision

# Metro
.metro-health-check*

# debug
npm-debug.*
yarn-debug.*
yarn-error.*

# macOS
.DS_Store
*.pem

# local env files
.env*.local

# typescript
*.tsbuildinfo

app-example

# generated native folders
/ios
/android


================================================
FILE: example/README.md
================================================
# How to use

1. Install dependencies:

   ```bash
   yarn install
   cd example
   yarn install
   ```

2. Install iOS dev build (only needed once or after rebuilding native dependencies):
   ```bash
   yarn installDevBuild:ios
   ```

   or

   ```bash
   yarn installDevBuild:android
   ```

3. Start the example app on iOS simulator:

   ```bash
   yarn start:ios
   ```

# Dark Theme Support

To switch theme press `Cmd + Shift + A` in iOS simulator.


================================================
FILE: example/app/(tabs)/_layout.tsx
================================================
import React from 'react'
import { Tabs } from 'expo-router'

import { HapticTab } from '@/components/haptic-tab'
import { IconSymbol } from '@/components/ui/icon-symbol'
import { Colors } from '@/constants/theme'
import { useColorScheme } from '@/hooks/use-color-scheme'

export default function TabLayout () {
  const colorScheme = useColorScheme()

  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
        headerShown: false,
        tabBarButton: HapticTab,
      }}
    >
      <Tabs.Screen
        name='index'
        options={{
          title: 'Home',
          tabBarIcon: ({ color }) => <IconSymbol size={28} name='house.fill' color={color} />,
        }}
      />
      <Tabs.Screen
        name='explore'
        options={{
          title: 'Explore',
          tabBarIcon: ({ color }) => <IconSymbol size={28} name='paperplane.fill' color={color} />,
        }}
      />
    </Tabs>
  )
}


================================================
FILE: example/app/(tabs)/explore.tsx
================================================
import React from 'react'
import { ScrollView, StyleSheet, View } from 'react-native'
import { useRouter } from 'expo-router'
import { RectButton } from 'react-native-gesture-handler'
import { SafeAreaView } from 'react-native-safe-area-context'

import { ThemedText } from '@/components/themed-text'
import { ThemedView } from '@/components/themed-view'
import { useThemeColor } from '@/hooks/use-theme-color'

type ChatExample = 'basic' | 'customized-rendering' | 'slack' | 'links' | 'reply'

const examples: Array<{ id: ChatExample, title: string, description: string }> = [
  { id: 'basic', title: 'Basic Example', description: 'Basic chat with keyboard logging for testing' },
  { id: 'links', title: 'Links & Patterns', description: 'Phone numbers, emails, URLs, hashtags, and mentions' },
  { id: 'customized-rendering', title: 'Customized Rendering', description: 'Customized chat with all rendering options' },
  { id: 'slack', title: 'Slack Style', description: 'Slack-like message styling' },
  { id: 'reply', title: 'Reply Example', description: 'Example demonstrating reply functionality' },
]

export default function ExploreScreen () {
  const router = useRouter()
  const backgroundColor = useThemeColor({}, 'background')
  const borderColor = useThemeColor({ light: '#e0e0e0', dark: '#444' }, 'icon')

  return (
    <SafeAreaView style={[styles.fill, { backgroundColor }]} edges={['top']}>
      <ScrollView style={styles.fill}>
        <ThemedView style={styles.titleContainer}>
          <ThemedText type='title'>Explore Chat Examples</ThemedText>
        </ThemedView>
        <ThemedView style={styles.description}>
          <ThemedText>
            Choose from different chat implementations to see various features and styling options.
          </ThemedText>
        </ThemedView>
        <View style={styles.examplesContainer}>
          {examples.map(example => (
            <RectButton
              key={example.id}
              style={[styles.exampleCard, { borderColor }]}
              onPress={() => router.push(`/chat/${example.id}`)}
            >
              <ThemedText type='subtitle' style={styles.exampleTitle}>
                {example.title}
              </ThemedText>
              <ThemedText style={styles.exampleDescription}>
                {example.description}
              </ThemedText>
              <ThemedText type='link' style={styles.tryButton}>
                Try it →
              </ThemedText>
            </RectButton>
          ))}
        </View>
      </ScrollView>
    </SafeAreaView>
  )
}

const styles = StyleSheet.create({
  fill: {
    flex: 1,
  },
  titleContainer: {
    padding: 20,
    paddingBottom: 10,
  },
  description: {
    paddingHorizontal: 20,
    paddingBottom: 20,
  },
  examplesContainer: {
    padding: 20,
    gap: 15,
  },
  exampleCard: {
    padding: 20,
    borderRadius: 12,
    borderWidth: 1,
    gap: 8,
  },
  exampleTitle: {
    marginBottom: 4,
  },
  exampleDescription: {
    opacity: 0.7,
    marginBottom: 8,
  },
  tryButton: {
    alignSelf: 'flex-start',
  },
})


================================================
FILE: example/app/(tabs)/index.tsx
================================================
import { StyleSheet } from 'react-native'
import { Image } from 'expo-image'

import { ExternalLink } from '@/components/external-link'
import { HelloWave } from '@/components/hello-wave'
import ParallaxScrollView from '@/components/parallax-scroll-view'
import { ThemedText } from '@/components/themed-text'
import { ThemedView } from '@/components/themed-view'

export default function HomeScreen () {
  return (
    <ParallaxScrollView
      headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
      headerImage={
        <Image
          source={require('@/assets/images/partial-react-logo.png')}
          style={styles.reactLogo}
        />
      }
    >
      <ThemedView style={styles.titleContainer}>
        <ThemedText type='title'>Welcome!</ThemedText>
        <HelloWave />
      </ThemedView>
      <ThemedView style={styles.stepContainer}>
        <ThemedText type='subtitle'>React Native Gifted Chat</ThemedText>
        <ThemedText>
          The most complete chat UI for React Native
        </ThemedText>
        <ExternalLink href='https://github.com/FaridSafi/react-native-gifted-chat'>
          <ThemedText type='link'>View on GitHub</ThemedText>
        </ExternalLink>
      </ThemedView>
      <ThemedView style={styles.stepContainer}>
        <ThemedText>
          Tap the Explore tab to try different chat examples.
        </ThemedText>
      </ThemedView>
    </ParallaxScrollView>
  )
}

const styles = StyleSheet.create({
  titleContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8,
  },
  stepContainer: {
    gap: 8,
    marginBottom: 8,
  },
  reactLogo: {
    height: 178,
    width: 290,
    bottom: 0,
    left: 0,
    position: 'absolute',
  },
})


================================================
FILE: example/app/_layout.tsx
================================================
import { LogBox, StyleSheet } from 'react-native'
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'
import { Stack } from 'expo-router'
import { StatusBar } from 'expo-status-bar'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import 'react-native-reanimated'

import { useColorScheme } from '@/hooks/use-color-scheme'

LogBox.ignoreLogs(['Open debugger to view warnings'])

export const unstable_settings = {
  anchor: '(tabs)',
}

export default function RootLayout () {
  const colorScheme = useColorScheme()

  return (
    <GestureHandlerRootView style={styles.container}>
      <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
        <Stack screenOptions={{ headerShown: false }}>
          <Stack.Screen name='(tabs)' />
          <Stack.Screen name='chat' />
          <Stack.Screen name='modal' options={{ headerShown: true, presentation: 'modal', title: 'Modal' }} />
        </Stack>
        <StatusBar style='auto' />
      </ThemeProvider>
    </GestureHandlerRootView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
})


================================================
FILE: example/app/chat/_layout.tsx
================================================
import { TouchableOpacity, Text, StyleSheet } from 'react-native'
import { Ionicons } from '@expo/vector-icons'
import { Stack, useRouter } from 'expo-router'
import { useSafeAreaInsets } from 'react-native-safe-area-context'

export default function ChatLayout () {
  const insets = useSafeAreaInsets()
  const router = useRouter()

  return (
    <Stack
      screenOptions={{
        headerShown: true,
        contentStyle: { paddingBottom: insets.bottom, backgroundColor: '#fff' },
        headerLeft: () => (
          <TouchableOpacity onPress={() => router.back()} style={styles.backButton}>
            <Ionicons name='chevron-back' size={24} color='#007AFF' />
            <Text style={styles.backText}>Back</Text>
          </TouchableOpacity>
        ),
      }}
    >
      <Stack.Screen
        name='basic'
        options={{ title: 'Basic Example' }}
      />
      <Stack.Screen
        name='customized-rendering'
        options={{ title: 'Customized Rendering' }}
      />
      <Stack.Screen
        name='links'
        options={{ title: 'Links & Patterns' }}
      />
      <Stack.Screen
        name='reply'
        options={{ title: 'Reply Example' }}
      />
      <Stack.Screen
        name='slack'
        options={{ title: 'Slack Style' }}
      />
    </Stack>
  )
}

const styles = StyleSheet.create({
  backButton: {
    flexDirection: 'row',
    alignItems: 'center',
    marginLeft: -8,
  },
  backText: {
    color: '#007AFF',
    fontSize: 17,
  },
})


================================================
FILE: example/app/chat/basic.tsx
================================================
import BasicExample from '@/components/chat-examples/BasicExample'

export default BasicExample


================================================
FILE: example/app/chat/customized-rendering.tsx
================================================
import CustomizedRenderingExample from '@/components/chat-examples/CustomizedRenderingExample'

export default CustomizedRenderingExample


================================================
FILE: example/app/chat/links.tsx
================================================
import LinksExample from '@/components/chat-examples/LinksExample'

export default LinksExample


================================================
FILE: example/app/chat/reply.tsx
================================================
import ReplyExample from '@/components/chat-examples/ReplyExample'

export default ReplyExample


================================================
FILE: example/app/chat/slack.tsx
================================================
import SlackExample from '@/components/chat-examples/SlackExample'

export default SlackExample


================================================
FILE: example/app/modal.tsx
================================================
import { StyleSheet } from 'react-native'
import { Link } from 'expo-router'

import { ThemedText } from '@/components/themed-text'
import { ThemedView } from '@/components/themed-view'

export default function ModalScreen () {
  return (
    <ThemedView style={styles.container}>
      <ThemedText type='title'>This is a modal</ThemedText>
      <Link href='/' dismissTo style={styles.link}>
        <ThemedText type='link'>Go to home screen</ThemedText>
      </Link>
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
  },
  link: {
    marginTop: 15,
    paddingVertical: 15,
  },
})


================================================
FILE: example/app.json
================================================
{
  "expo": {
    "name": "example",
    "slug": "example",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/images/icon.png",
    "scheme": "example",
    "userInterfaceStyle": "automatic",
    "newArchEnabled": true,
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.anonymous.example",
      "config": {
        "googleMapsApiKey": "YOUR_GOOGLE_MAPS_API_KEY"
      }
    },
    "android": {
      "adaptiveIcon": {
        "backgroundColor": "#E6F4FE",
        "foregroundImage": "./assets/images/android-icon-foreground.png",
        "backgroundImage": "./assets/images/android-icon-background.png",
        "monochromeImage": "./assets/images/android-icon-monochrome.png"
      },
      "edgeToEdgeEnabled": true,
      "predictiveBackGestureEnabled": false,
      "package": "com.anonymous.example",
      "config": {
        "googleMaps": {
          "apiKey": "YOUR_GOOGLE_MAPS_API_KEY"
        }
      }
    },
    "web": {
      "output": "static",
      "favicon": "./assets/images/favicon.png"
    },
    "plugins": [
      "expo-router",
      [
        "expo-splash-screen",
        {
          "image": "./assets/images/splash-icon.png",
          "imageWidth": 200,
          "resizeMode": "contain",
          "backgroundColor": "#ffffff",
          "dark": {
            "backgroundColor": "#000000"
          }
        }
      ]
    ],
    "experiments": {
      "typedRoutes": true,
      "reactCompiler": true
    }
  }
}


================================================
FILE: example/babel.config.js
================================================
const fs = require('fs')
const path = require('path')

const root = path.resolve(__dirname, '..')
const rootPak = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))

module.exports = function (api) {
  api.cache(true)

  return {
    presets: ['babel-preset-expo'],
    plugins: [
      [
        'module-resolver',
        {
          extensions: ['.tsx', '.ts', '.js', '.json'],
          alias: {
            // For development, we want to alias the library to the source
            [rootPak.name]: path.join(root, rootPak.main),
          },
        },
      ],
      'react-native-worklets/plugin',
    ],
  }
}


================================================
FILE: example/components/chat-examples/BasicExample.tsx
================================================
import React, { useCallback, useMemo, useState } from 'react'
import { StyleSheet, View, useColorScheme } from 'react-native'
import { GiftedChat, IMessage } from 'react-native-gifted-chat'
import AccessoryBar from '../../example-expo/AccessoryBar'
import CustomActions from '../../example-expo/CustomActions'
import CustomView from '../../example-expo/CustomView'
import earlierMessages from '../../example-expo/data/earlierMessages'
import messagesData from '../../example-expo/data/messages'
import { useKeyboardVerticalOffset } from '../../hooks/useKeyboardVerticalOffset'
import { getColorSchemeStyle } from '../../utils/styleUtils'

export default function BasicExample () {
  const [messages, setMessages] = useState<IMessage[]>(messagesData)
  const [isLoadingEarlier, setIsLoadingEarlier] = useState(false)
  const [isTyping, setIsTyping] = useState(false)
  const colorScheme = useColorScheme()

  const keyboardVerticalOffset = useKeyboardVerticalOffset()

  const user = useMemo(() => ({
    _id: 1,
    name: 'Developer',
  }), [])

  const onSend = useCallback((newMessages: IMessage[] = []) => {
    const messagesWithIds = newMessages.map(msg => ({
      ...msg,
      _id: msg._id || Math.random().toString(36).substring(7),
      user: msg.user || user,
    }))
    setMessages(previousMessages =>
      GiftedChat.append(previousMessages, messagesWithIds)
    )
  }, [user])

  const onPressLoadEarlierMessages = useCallback(() => {
    setIsLoadingEarlier(true)
    setTimeout(() => {
      setMessages(previousMessages =>
        GiftedChat.prepend(previousMessages, earlierMessages())
      )
      setIsLoadingEarlier(false)
    }, 1500)
  }, [])

  const renderAccessory = useCallback(
    () => <AccessoryBar onSend={onSend} isTyping={() => setIsTyping(isTyping => !isTyping)} user={user} />,
    [onSend, user]
  )

  const renderCustomView = useCallback((props: any) => <CustomView {...props} />, [])

  const renderActions = useCallback(
    (props: any) => <CustomActions {...props} onSend={onSend} user={user} />,
    [onSend, user]
  )

  return (
    <View style={[styles.container, getColorSchemeStyle(styles, 'container', colorScheme)]}>
      <GiftedChat
        messages={messages}
        onSend={onSend}
        loadEarlierMessagesProps={{ isAvailable: true, isLoading: isLoadingEarlier, onPress: onPressLoadEarlierMessages }}
        user={user}
        renderActions={renderActions}
        renderAccessory={renderAccessory}
        renderCustomView={renderCustomView}
        isTyping={isTyping}
        messagesContainerStyle={getColorSchemeStyle(styles, 'messagesContainer', colorScheme)}
        textInputProps={{
          style: getColorSchemeStyle(styles, 'composer', colorScheme),
        }}
        messageTextProps={{
          hashtag: 'twitter',
        }}
        keyboardAvoidingViewProps={{ keyboardVerticalOffset }}
        isScrollToBottomEnabled
      />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  container_dark: {
    backgroundColor: '#000',
  },
  messagesContainer_dark: {
    backgroundColor: '#000',
  },
  composer_dark: {
    backgroundColor: '#1a1a1a',
    color: '#fff',
  },
})


================================================
FILE: example/components/chat-examples/CustomizedRenderingExample.tsx
================================================
import React from 'react'
import { StyleSheet, View, useColorScheme } from 'react-native'
import Chats from '../../example-gifted-chat/src/Chats'
import { getColorSchemeStyle } from '../../utils/styleUtils'

export default function CustomizedRenderingExample () {
  const colorScheme = useColorScheme()

  return (
    <View style={[styles.container, getColorSchemeStyle(styles, 'container', colorScheme)]}>
      <Chats />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  container_dark: {
    backgroundColor: '#000',
  },
})


================================================
FILE: example/components/chat-examples/LinksExample.tsx
================================================
import React, { useCallback, useMemo, useState } from 'react'
import { Linking, StyleSheet, Text, View } from 'react-native'
import { ActionSheetProvider, useActionSheet } from '@expo/react-native-action-sheet'
import { setStringAsync } from 'expo-clipboard'
import { isValidPhoneNumber, parsePhoneNumberWithError } from 'libphonenumber-js'
import { GiftedChat, IMessage, LinkMatcher } from 'react-native-gifted-chat'
import { useKeyboardVerticalOffset } from '../../hooks/useKeyboardVerticalOffset'

const LinksExample: React.FC = () => {
  const { showActionSheetWithOptions } = useActionSheet()
  const keyboardVerticalOffset = useKeyboardVerticalOffset()

  const initialMessages: IMessage[] = useMemo(() => [
    {
      text: 'Welcome! 👋',
      createdAt: new Date(),
      user: {
        _id: 2,
        name: 'John Doe',
      },
    },
    {
      text: 'This example shows how GiftedChat handles different types of links in messages. Try tapping on any link!',
      createdAt: new Date(Date.now() - 1 * 60000),
      user: {
        _id: 2,
        name: 'John Doe',
      },
    },
    {
      text: 'Phone numbers are also parsed:\n\n• +79931234567\n\n• 89931234567\n\n• +1-215-456-7890.\n\nIt shouldn\'t parse phone numbers in file names like IMG_20220101_123456.jpg, 89201234567_today.pdf, or in addresses like 1234 React St., JS City, 56789.',
      createdAt: new Date(Date.now() - 2 * 60000),
      user: {
        _id: 2,
        name: 'John Doe',
      },
    },
    {
      text: 'Email addresses are clickable: cool.guy@example.com or contact@reactnative.dev',
      createdAt: new Date(Date.now() - 3 * 60000),
      user: {
        _id: 2,
        name: 'John Doe',
      },
    },
    {
      text: 'Different link formats work:\n• www.google.com\n• google.com\n• https://google.com',
      createdAt: new Date(Date.now() - 4 * 60000),
      user: {
        _id: 2,
        name: 'John Doe',
      },
    },
    {
      text: 'Use hashtags to categorize: #giftedchat #reactnative #opensource',
      createdAt: new Date(Date.now() - 5 * 60000),
      user: {
        _id: 2,
        name: 'John Doe',
      },
    },
    {
      text: 'You can mention people like @kesha-antonov or @john-doe',
      createdAt: new Date(Date.now() - 6 * 60000),
      user: {
        _id: 2,
        name: 'John Doe',
      },
    },
    {
      text: 'System message with link: Check out our documentation at https://github.com/FaridSafi/react-native-gifted-chat',
      createdAt: new Date(Date.now() - 7 * 60000),
      user: {
        _id: 2,
        name: 'John Doe',
      },
      system: true,
    },
    {
      text: 'System message with data and phone numbers: Contact support at +79931234567 or +1-215-456-7890. We have holidays on 2025-12-25 and until 2026-01-01 12:00.',
      createdAt: new Date(Date.now() - 7 * 60000),
      user: {
        _id: 2,
        name: 'John Doe',
      },
      system: true,
    },
  ].map((message, index) => ({
    ...message,
    _id: index + 1,
  })).reverse(), [])

  const [messages, setMessages] = useState<IMessage[]>(initialMessages)

  const user = useMemo(() => ({
    _id: 1,
    name: 'Developer',
  }), [])

  const onSend = useCallback((newMessages: IMessage[] = []) => {
    const messagesWithIds = newMessages.map(msg => ({
      ...msg,
      _id: msg._id || Math.random().toString(36).substring(7),
      user: msg.user || user,
    }))
    setMessages(previousMessages =>
      GiftedChat.append(previousMessages, messagesWithIds)
    )
  }, [user])

  const getValidPhoneNumber = useCallback((text: string): string | undefined => {
    const cleaned = text.replace(/[\-\(\)\s\.]/g, '')

    // Validate with libphonenumber-js
    try {
      // Try direct validation first
      if (isValidPhoneNumber(cleaned))
        return cleaned

      // Try with RU region for local numbers
      if (isValidPhoneNumber(cleaned, 'RU'))
        return cleaned

      // Try parsing to check validity
      const phoneNumber = parsePhoneNumberWithError(cleaned, 'RU')
      if (phoneNumber && phoneNumber.isValid())
        return cleaned

    } catch (error) {
      console.warn('Invalid phone number:', error)
    }

    return undefined
  }, [])

  const handlePressPhoneNumber = useCallback((url: string) => {
    if (!url)
      return // Skip if validation failed

    const options: {
      title: string
      action?: () => void
    }[] = [
      { title: 'Call', action: () => Linking.openURL(`tel:${url}`) },
      { title: 'Send SMS', action: () => Linking.openURL(`sms:${url}`) },
      { title: 'Copy Phone Number', action: () => setStringAsync(url) },
      { title: 'Cancel' },
    ]

    showActionSheetWithOptions({
      options: options.map(o => o.title),
      cancelButtonIndex: options.length - 1,
    }, (buttonIndex?: number) => {
      if (buttonIndex === undefined)
        return

      const option = options[buttonIndex]
      option.action?.()
    })
  }, [showActionSheetWithOptions])

  const matchers = useMemo<LinkMatcher[]>(() => [
    {
      type: 'phone',
      // Pattern that excludes numbers adjacent to underscores or part of filenames
      pattern: /(?<![A-Za-z0-9_])(?:\+?\d{1,3}[\s.-]?)?\(?\d{1,4}\)?[\s.-]?\d{1,4}[\s.-]?\d{1,9}(?![A-Za-z0-9_]|\.[a-z]{2,4})/gi,
      getLinkUrl: (matchedText: string): string => {
        return getValidPhoneNumber(matchedText) || ''
      },
      getLinkText: (text: string): string => {
        return text
      },
      renderLink: (text: string, url: string, index: number) => {
        const validPhoneNumber = getValidPhoneNumber(text)
        const isDisabled = !validPhoneNumber || !url

        return (
          <Text
            key={index}
            style={[styles.link, isDisabled && styles.linkDisabled]}
            onPress={() => !isDisabled && handlePressPhoneNumber(url)}
          >
            {text}
          </Text>
        )
      },
    },
  ], [getValidPhoneNumber, handlePressPhoneNumber])

  return (
    <ActionSheetProvider>
      <View style={styles.container}>
        <GiftedChat
          messages={messages}
          onSend={onSend}
          user={user}
          messageTextProps={{
            phone: false,
            url: true,
            matchers,
            mention: true,
            hashtag: true,
            mentionUrl: 'https://x.com',
            hashtagUrl: 'https://x.com/hashtag',
          }}
          keyboardAvoidingViewProps={{ keyboardVerticalOffset }}
          colorScheme='light'
        />
      </View>
    </ActionSheetProvider>
  )
}

const ExampleContainer = () => (
  <ActionSheetProvider>
    <LinksExample />
  </ActionSheetProvider>
)

export default ExampleContainer

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  link: {
    textDecorationLine: 'underline',
  },
  linkDisabled: {
    textDecorationLine: 'none',
  },
})


================================================
FILE: example/components/chat-examples/ReplyExample.tsx
================================================
import React, { useCallback, useMemo, useState } from 'react'
import { StyleSheet, View, useColorScheme } from 'react-native'
import { GiftedChat, IMessage, ReplyMessage } from 'react-native-gifted-chat'

import messagesData from '../../example-expo/data/messages'
import { useKeyboardVerticalOffset } from '../../hooks/useKeyboardVerticalOffset'
import { getColorSchemeStyle } from '../../utils/styleUtils'

export interface IChatMessage extends IMessage {
  replyMessage?: ReplyMessage
}

export default function ReplyExample () {
  const [messages, setMessages] = useState<IChatMessage[]>(messagesData)
  const colorScheme = useColorScheme()
  const keyboardVerticalOffset = useKeyboardVerticalOffset()

  const user = useMemo(() => ({
    _id: 1,
    name: 'Developer',
  }), [])

  const onSend = useCallback((newMessages: IChatMessage[] = []) => {
    const messagesWithIds = newMessages.map(msg => ({
      ...msg,
      _id: msg._id || Math.random().toString(36).substring(7),
      user: msg.user || user,
    }))
    setMessages(previousMessages =>
      GiftedChat.append(previousMessages, messagesWithIds)
    )
  }, [user])

  const handlePressReply = useCallback((reply: ReplyMessage) => {
    console.log('Pressed reply:', reply)
    // Could scroll to the original message here
  }, [])

  return (
    <View style={[styles.container, getColorSchemeStyle(styles, 'container', colorScheme)]}>
      <GiftedChat<IChatMessage>
        messages={messages}
        onSend={onSend}
        user={user}
        messagesContainerStyle={getColorSchemeStyle(styles, 'messagesContainer', colorScheme)}
        textInputProps={{
          style: getColorSchemeStyle(styles, 'composer', colorScheme),
        }}
        // New grouped reply props
        reply={{
          swipe: {
            isEnabled: true,
            direction: 'left', // swipe left to reply
          },
          onPress: handlePressReply,
        }}
        keyboardAvoidingViewProps={{ keyboardVerticalOffset }}
      />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  container_dark: {
    backgroundColor: '#000',
  },
  messagesContainer_dark: {
    backgroundColor: '#000',
  },
  composer_dark: {
    backgroundColor: '#1a1a1a',
    color: '#fff',
  },
})


================================================
FILE: example/components/chat-examples/SlackExample.tsx
================================================
import React, { useCallback, useMemo, useState } from 'react'
import { StyleSheet, View, useColorScheme } from 'react-native'
import { GiftedChat, IMessage } from 'react-native-gifted-chat'
import messagesData from '../../example-expo/data/messages'
import SlackMessage from '../../example-slack-message/src/SlackMessage'
import { useKeyboardVerticalOffset } from '../../hooks/useKeyboardVerticalOffset'
import { getColorSchemeStyle } from '../../utils/styleUtils'

export default function SlackExample () {
  const [messages, setMessages] = useState<IMessage[]>(messagesData)
  const colorScheme = useColorScheme()
  const keyboardVerticalOffset = useKeyboardVerticalOffset()

  const user = useMemo(() => ({
    _id: 1,
    name: 'Developer',
  }), [])

  const onSend = useCallback((newMessages: IMessage[] = []) => {
    const messagesWithIds = newMessages.map(msg => ({
      ...msg,
      _id: msg._id || Math.random().toString(36).substring(7),
      user: msg.user || user,
    }))
    setMessages(previousMessages =>
      GiftedChat.append(previousMessages, messagesWithIds)
    )
  }, [user])

  return (
    <View style={[styles.container, getColorSchemeStyle(styles, 'container', colorScheme)]}>
      <GiftedChat
        messages={messages}
        onSend={onSend}
        user={user}
        renderMessage={props => <SlackMessage {...props} />}
        messagesContainerStyle={getColorSchemeStyle(styles, 'messagesContainer', colorScheme)}
        textInputProps={{
          style: getColorSchemeStyle(styles, 'composer', colorScheme),
        }}
        keyboardAvoidingViewProps={{ keyboardVerticalOffset }}
      />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  container_dark: {
    backgroundColor: '#000',
  },
  messagesContainer_dark: {
    backgroundColor: '#000',
  },
  composer_dark: {
    backgroundColor: '#1a1a1a',
    color: '#fff',
  },
})


================================================
FILE: example/components/external-link.tsx
================================================
import { type ComponentProps } from 'react'
import { Href, Link } from 'expo-router'
import { openBrowserAsync, WebBrowserPresentationStyle } from 'expo-web-browser'

type Props = Omit<ComponentProps<typeof Link>, 'href'> & { href: Href & string }

export function ExternalLink ({ href, ...rest }: Props) {
  return (
    <Link
      target='_blank'
      {...rest}
      href={href}
      onPress={async event => {
        console.log('ExternalLink pressed:', href)
        if (process.env.EXPO_OS !== 'web') {
          // Prevent the default behavior of linking to the default browser on native.
          event.preventDefault()
          // Open the link in an in-app browser.
          await openBrowserAsync(href, {
            presentationStyle: WebBrowserPresentationStyle.AUTOMATIC,
          })
        }
      }}
    />
  )
}


================================================
FILE: example/components/haptic-tab.tsx
================================================
import { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs'
import { PlatformPressable } from '@react-navigation/elements'
import * as Haptics from 'expo-haptics'

export function HapticTab (props: BottomTabBarButtonProps) {
  return (
    <PlatformPressable
      {...props}
      onPressIn={ev => {
        if (process.env.EXPO_OS === 'ios')
          // Add a soft haptic feedback when pressing down on the tabs.
          Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)

        props.onPressIn?.(ev)
      }}
    />
  )
}


================================================
FILE: example/components/hello-wave.tsx
================================================
import Animated from 'react-native-reanimated'

export function HelloWave () {
  return (
    <Animated.Text
      style={{
        fontSize: 28,
        lineHeight: 32,
        marginTop: -6,
        animationName: {
          '50%': { transform: [{ rotate: '25deg' }] },
        },
        animationIterationCount: 4,
        animationDuration: '300ms',
      }}
    >
      👋
    </Animated.Text>
  )
}


================================================
FILE: example/components/parallax-scroll-view.tsx
================================================
import type { PropsWithChildren, ReactElement } from 'react'
import { StyleSheet } from 'react-native'
import Animated, {
  interpolate,
  useAnimatedRef,
  useAnimatedStyle,
  useScrollOffset,
} from 'react-native-reanimated'

import { ThemedView } from '@/components/themed-view'
import { useColorScheme } from '@/hooks/use-color-scheme'
import { useThemeColor } from '@/hooks/use-theme-color'

const HEADER_HEIGHT = 250

type Props = PropsWithChildren<{
  headerImage: ReactElement
  headerBackgroundColor: { dark: string, light: string }
}>

export default function ParallaxScrollView ({
  children,
  headerImage,
  headerBackgroundColor,
}: Props) {
  const backgroundColor = useThemeColor({}, 'background')
  const colorScheme = useColorScheme() ?? 'light'
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const scrollRef = useAnimatedRef<Animated.ScrollView>()
  const scrollOffset = useScrollOffset(scrollRef)
  const headerAnimatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        {
          translateY: interpolate(
            scrollOffset.value,
            [-HEADER_HEIGHT, 0, HEADER_HEIGHT],
            [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75]
          ),
        },
        {
          scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]),
        },
      ],
    }
  })

  return (
    <Animated.ScrollView
      ref={scrollRef}
      style={{ backgroundColor, flex: 1 }}
      scrollEventThrottle={16}
    >
      <Animated.View
        style={[
          styles.header,
          { backgroundColor: headerBackgroundColor[colorScheme] },
          headerAnimatedStyle,
        ]}
      >
        {headerImage}
      </Animated.View>
      <ThemedView style={styles.content}>{children}</ThemedView>
    </Animated.ScrollView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  header: {
    height: HEADER_HEIGHT,
    overflow: 'hidden',
  },
  content: {
    flex: 1,
    padding: 32,
    gap: 16,
    overflow: 'hidden',
  },
})


================================================
FILE: example/components/themed-text.tsx
================================================
import { StyleSheet, Text, type TextProps } from 'react-native'

import { useThemeColor } from '@/hooks/use-theme-color'

export type ThemedTextProps = TextProps & {
  lightColor?: string
  darkColor?: string
  type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link'
}

export function ThemedText ({
  style,
  lightColor,
  darkColor,
  type = 'default',
  ...rest
}: ThemedTextProps) {
  const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text')

  return (
    <Text
      style={[
        { color },
        type === 'default' ? styles.default : undefined,
        type === 'title' ? styles.title : undefined,
        type === 'defaultSemiBold' ? styles.defaultSemiBold : undefined,
        type === 'subtitle' ? styles.subtitle : undefined,
        type === 'link' ? styles.link : undefined,
        style,
      ]}
      {...rest}
    />
  )
}

const styles = StyleSheet.create({
  default: {
    fontSize: 16,
    lineHeight: 24,
  },
  defaultSemiBold: {
    fontSize: 16,
    lineHeight: 24,
    fontWeight: '600',
  },
  title: {
    fontSize: 32,
    fontWeight: 'bold',
    lineHeight: 32,
  },
  subtitle: {
    fontSize: 20,
    fontWeight: 'bold',
  },
  link: {
    lineHeight: 30,
    fontSize: 16,
    color: '#0a7ea4',
  },
})


================================================
FILE: example/components/themed-view.tsx
================================================
import { View, type ViewProps } from 'react-native'

import { useThemeColor } from '@/hooks/use-theme-color'

export type ThemedViewProps = ViewProps & {
  lightColor?: string
  darkColor?: string
}

export function ThemedView ({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {
  const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background')

  return <View style={[{ backgroundColor }, style]} {...otherProps} />
}


================================================
FILE: example/components/ui/collapsible.tsx
================================================
import { PropsWithChildren, useState } from 'react'
import { StyleSheet } from 'react-native'
import { RectButton } from 'react-native-gesture-handler'

import { ThemedText } from '@/components/themed-text'
import { ThemedView } from '@/components/themed-view'
import { IconSymbol } from '@/components/ui/icon-symbol'
import { Colors } from '@/constants/theme'
import { useColorScheme } from '@/hooks/use-color-scheme'

export function Collapsible ({ children, title }: PropsWithChildren & { title: string }) {
  const [isOpen, setIsOpen] = useState(false)
  const theme = useColorScheme() ?? 'light'

  return (
    <ThemedView>
      <RectButton
        style={styles.heading}
        onPress={() => setIsOpen(value => !value)}
      >
        <IconSymbol
          name='chevron.right'
          size={18}
          weight='medium'
          color={theme === 'light' ? Colors.light.icon : Colors.dark.icon}
          style={{ transform: [{ rotate: isOpen ? '90deg' : '0deg' }] }}
        />

        <ThemedText type='defaultSemiBold'>{title}</ThemedText>
      </RectButton>
      {isOpen && <ThemedView style={styles.content}>{children}</ThemedView>}
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  heading: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 6,
  },
  content: {
    marginTop: 6,
    marginLeft: 24,
  },
})


================================================
FILE: example/components/ui/icon-symbol.ios.tsx
================================================
import { StyleProp, ViewStyle } from 'react-native'
import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols'

export function IconSymbol ({
  name,
  size = 24,
  color,
  style,
  weight = 'regular',
}: {
  name: SymbolViewProps['name']
  size?: number
  color: string
  style?: StyleProp<ViewStyle>
  weight?: SymbolWeight
}) {
  return (
    <SymbolView
      weight={weight}
      tintColor={color}
      resizeMode='scaleAspectFit'
      name={name}
      style={[
        {
          width: size,
          height: size,
        },
        style,
      ]}
    />
  )
}


================================================
FILE: example/components/ui/icon-symbol.tsx
================================================
// Fallback for using MaterialIcons on Android and web.

import { ComponentProps } from 'react'
import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native'
import MaterialIcons from '@expo/vector-icons/MaterialIcons'
import { SymbolWeight, SymbolViewProps } from 'expo-symbols'

type IconMapping = Record<SymbolViewProps['name'], ComponentProps<typeof MaterialIcons>['name']>
type IconSymbolName = keyof typeof MAPPING

/**
 * Add your SF Symbols to Material Icons mappings here.
 * - see Material Icons in the [Icons Directory](https://icons.expo.fyi).
 * - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app.
 */
const MAPPING = {
  'house.fill': 'home',
  'paperplane.fill': 'send',
  'chevron.left.forwardslash.chevron.right': 'code',
  'chevron.right': 'chevron-right',
} as IconMapping

/**
 * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web.
 * This ensures a consistent look across platforms, and optimal resource usage.
 * Icon `name`s are based on SF Symbols and require manual mapping to Material Icons.
 */
export function IconSymbol ({
  name,
  size = 24,
  color,
  style,
}: {
  name: IconSymbolName
  size?: number
  color: string | OpaqueColorValue
  style?: StyleProp<TextStyle>
  weight?: SymbolWeight
}) {
  return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />
}


================================================
FILE: example/constants/theme.ts
================================================
/**
 * Below are the colors that are used in the app. The colors are defined in the light and dark mode.
 * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
 */

import { Platform } from 'react-native'

const tintColorLight = '#0a7ea4'
const tintColorDark = '#fff'

export const Colors = {
  light: {
    text: '#11181C',
    background: '#fff',
    tint: tintColorLight,
    icon: '#687076',
    tabIconDefault: '#687076',
    tabIconSelected: tintColorLight,
  },
  dark: {
    text: '#ECEDEE',
    background: '#151718',
    tint: tintColorDark,
    icon: '#9BA1A6',
    tabIconDefault: '#9BA1A6',
    tabIconSelected: tintColorDark,
  },
}

export const Fonts = Platform.select({
  ios: {
    /** iOS `UIFontDescriptorSystemDesignDefault` */
    sans: 'system-ui',
    /** iOS `UIFontDescriptorSystemDesignSerif` */
    serif: 'ui-serif',
    /** iOS `UIFontDescriptorSystemDesignRounded` */
    rounded: 'ui-rounded',
    /** iOS `UIFontDescriptorSystemDesignMonospaced` */
    mono: 'ui-monospace',
  },
  default: {
    sans: 'normal',
    serif: 'serif',
    rounded: 'normal',
    mono: 'monospace',
  },
  web: {
    sans: 'system-ui, -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Helvetica, Arial, sans-serif',
    serif: 'Georgia, \'Times New Roman\', serif',
    rounded: '\'SF Pro Rounded\', \'Hiragino Maru Gothic ProN\', Meiryo, \'MS PGothic\', sans-serif',
    mono: 'SFMono-Regular, Menlo, Monaco, Consolas, \'Liberation Mono\', \'Courier New\', monospace',
  },
})


================================================
FILE: example/eslint.config.js
================================================
import stylistic from '@stylistic/eslint-plugin'
import typescriptEslint from '@typescript-eslint/eslint-plugin'
import typescriptParser from '@typescript-eslint/parser'
import importPlugin from 'eslint-plugin-import'
import jestPlugin from 'eslint-plugin-jest'
import perfectionistPlugin from 'eslint-plugin-perfectionist'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'

export default [
  {
    ignores: ['**/node_modules/**', '**/lib/**', '**/build/**', '**/.expo/**', '**/android/**', '**/ios/**'],
  },
  {
    files: ['**/*.{js,jsx,ts,tsx}'],
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      parser: typescriptParser,
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
      },
      globals: {
        fetch: 'readonly',
        navigator: 'readonly',
        __DEV__: 'readonly',
        XMLHttpRequest: 'readonly',
        FormData: 'readonly',
        React$Element: 'readonly',
        requestAnimationFrame: 'readonly',

        // Node.js globals for build scripts and configuration files
        require: 'readonly',
        module: 'readonly',
        process: 'readonly',
        global: 'readonly',
        console: 'readonly',
        setTimeout: 'readonly',
        clearTimeout: 'readonly',
        setInterval: 'readonly',
        clearInterval: 'readonly',

        // Jest globals
        describe: 'readonly',
        test: 'readonly',
        it: 'readonly',
        jest: 'readonly',
        expect: 'readonly',
        beforeAll: 'readonly',
        beforeEach: 'readonly',
        afterAll: 'readonly',
        afterEach: 'readonly',
      },
    },
    plugins: {
      '@stylistic': stylistic,
      '@typescript-eslint': typescriptEslint,
      'import': importPlugin,
      'perfectionist': perfectionistPlugin,
      'react': react,
      'react-hooks': reactHooks,
    },
    settings: {
      react: {
        version: 'detect',
      },
    },
    rules: {
      // React rules
      'react/react-in-jsx-scope': 'off',
      'react/no-unknown-property': 'off',
      'react/display-name': 'off',
      'react/prop-types': 'off',

      // React Hooks
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': [
        'warn',
        {
          additionalHooks:
            '(useAnimatedStyle|useSharedValue|useAnimatedGestureHandler|useAnimatedScrollHandler|useAnimatedProps|useDerivedValue|useAnimatedRef|useAnimatedReact|useAnimatedReaction|useCallbackDebounced|useCallbackThrottled)',
        },
      ],

      // TypeScript rules
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-unused-vars': ['error'],

      // Stylistic rules
      '@stylistic/semi': ['error', 'never'],
      '@stylistic/member-delimiter-style': [
        'error',
        {
          multiline: {
            delimiter: 'none',
            requireLast: true,
          },
          singleline: {
            delimiter: 'comma',
            requireLast: false,
          },
        },
      ],
      '@stylistic/indent': [
        'error',
        2,
        {
          SwitchCase: 1,
          VariableDeclarator: 'first',
          ignoredNodes: ['TemplateLiteral'],
        },
      ],
      '@stylistic/quotes': ['error', 'single'],
      '@stylistic/jsx-quotes': ['error', 'prefer-single'],
      '@stylistic/comma-dangle': [
        'error',
        {
          arrays: 'always-multiline',
          objects: 'always-multiline',
          imports: 'always-multiline',
          exports: 'never',
          functions: 'never',
        },
      ],
      '@stylistic/arrow-parens': ['error', 'as-needed'],
      '@stylistic/template-curly-spacing': 'off',
      '@stylistic/linebreak-style': ['off', 'unix'],
      '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: false }],
      '@stylistic/jsx-closing-bracket-location': ['error', 'line-aligned'],

      // General rules
      'no-func-assign': 'off',
      'no-class-assign': 'off',
      'no-useless-escape': 'off',
      'no-unused-vars': 'off', // Use @typescript-eslint/no-unused-vars instead
      'no-unreachable': 'error',
      'curly': [2, 'multi', 'consistent'],
      'nonblock-statement-body-position': ['error', 'below'],

      // Perfectionist rules
      'perfectionist/sort-imports': [
        'error',
        {
          groups: [
            'react',
            'external',
            'internal',
            ['parent', 'sibling'],
            'index',
          ],
          customGroups: {
            value: {
              react: ['^react$', '^react-native$'],
            },
          },
          newlinesBetween: 'ignore',
        },
      ],
      'perfectionist/sort-interfaces': 'off',
    },
  },
  {
    files: ['tests/**/*', 'src/__tests__/**/*'],
    plugins: {
      jest: jestPlugin,
    },
    rules: {
      ...jestPlugin.configs.recommended.rules,
    },
  },
]


================================================
FILE: example/example-expo/AccessoryBar.tsx
================================================
import React from 'react'
import { StyleSheet, View, useColorScheme } from 'react-native'
import { MaterialIcons } from '@expo/vector-icons'
import { RectButton } from 'react-native-gesture-handler'

import { IMessage, User } from '../../src'
import {
  getLocationAsync,
  pickImageAsync,
  takePictureAsync,
} from './mediaUtils'

export default function AccessoryBar ({ onSend, isTyping, user }: { onSend: (messages: IMessage[]) => void, isTyping: () => void, user: User }) {
  const colorScheme = useColorScheme()
  const isDark = colorScheme === 'dark'

  const handlePickImage = async () => {
    const images = await pickImageAsync()
    if (!images)
      return

    const messages: IMessage[] = images.map(image => ({
      _id: Math.random().toString(36).substring(7),
      image,
      text: '',
      createdAt: new Date(),
      user,
    }))
    onSend(messages)
  }

  const handleTakePicture = async () => {
    const images = await takePictureAsync()
    if (!images)
      return

    const messages: IMessage[] = images.map(image => ({
      _id: Math.random().toString(36).substring(7),
      image,
      text: '',
      createdAt: new Date(),
      user,
    }))
    onSend(messages)
  }

  const handleSendLocation = async () => {
    const location = await getLocationAsync()
    if (!location)
      return

    const message: IMessage = {
      _id: Math.random().toString(36).substring(7),
      location,
      text: '',
      createdAt: new Date(),
      user,
    }
    onSend([message])
  }

  const containerColorStyle = colorScheme === 'dark' ? styles.container_dark : {}

  return (
    <View style={[styles.container, containerColorStyle]}>
      <Button
        onPress={handlePickImage}
        name='photo'
        color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.5)'}
      />
      <Button
        onPress={handleTakePicture}
        name='camera'
        color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.5)'}
      />
      <Button
        onPress={handleSendLocation}
        name='my-location'
        color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.5)'}
      />
      <Button
        onPress={() => {
          isTyping()
        }}
        name='chat'
        color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.5)'}
      />
    </View>
  )
}

const Button = ({
  onPress,
  size = 30,
  color = 'rgba(0,0,0,0.5)',
  name,
}: {
  onPress: () => void
  size?: number
  color?: string
  name: React.ComponentProps<typeof MaterialIcons>['name']
}) => (
  <RectButton onPress={onPress}>
    <MaterialIcons size={size} color={color} name={name} />
  </RectButton>
)

const styles = StyleSheet.create({
  container: {
    backgroundColor: 'white',
    flexDirection: 'row',
    justifyContent: 'space-around',
    alignItems: 'center',
    borderTopWidth: StyleSheet.hairlineWidth,
    borderTopColor: 'rgba(0,0,0,0.3)',
    paddingVertical: 5,
  },
  container_dark: {
    backgroundColor: '#1a1a1a',
    borderTopColor: 'rgba(255,255,255,0.2)',
  },
})


================================================
FILE: example/example-expo/CustomActions.tsx
================================================
import React, { useCallback } from 'react'
import {
  StyleProp,
  ViewStyle,
  TextStyle,
  StyleSheet,
  Text,
  View,
  useColorScheme,
} from 'react-native'
import { useActionSheet } from '@expo/react-native-action-sheet'
import { RectButton } from 'react-native-gesture-handler'
import { IMessage, User } from '../../src'
import {
  getLocationAsync,
  pickImageAsync,
  takePictureAsync,
} from './mediaUtils'

interface Props {
  renderIcon?: () => React.ReactNode
  wrapperStyle?: StyleProp<ViewStyle>
  containerStyle?: StyleProp<ViewStyle>
  iconTextStyle?: StyleProp<TextStyle>
  onSend: (messages: IMessage[]) => void
  user: User
}

const CustomActions = ({
  renderIcon,
  iconTextStyle,
  containerStyle,
  wrapperStyle,
  onSend,
  user,
}: Props) => {
  const { showActionSheetWithOptions } = useActionSheet()
  const colorScheme = useColorScheme()

  const handlePickImage = useCallback(async () => {
    const images = await pickImageAsync()
    if (!images)
      return

    const messages: IMessage[] = images.map(image => ({
      _id: Math.random().toString(36).substring(7),
      image,
      text: '',
      createdAt: new Date(),
      user,
    }))
    onSend(messages)
  }, [onSend, user])

  const handleTakePicture = useCallback(async () => {
    const images = await takePictureAsync()
    if (!images)
      return

    const messages: IMessage[] = images.map(image => ({
      _id: Math.random().toString(36).substring(7),
      image,
      text: '',
      createdAt: new Date(),
      user,
    }))
    onSend(messages)
  }, [onSend, user])

  const handleSendLocation = useCallback(async () => {
    const location = await getLocationAsync()
    if (!location)
      return

    const message: IMessage = {
      _id: Math.random().toString(36).substring(7),
      location,
      text: '',
      createdAt: new Date(),
      user,
    }
    onSend([message])
  }, [onSend, user])

  const onActionsPress = useCallback(() => {
    const options: { title: string, action?: () => Promise<void> }[] = [
      { title: 'Choose From Library', action: handlePickImage },
      { title: 'Take Picture', action: handleTakePicture },
      { title: 'Send Location', action: handleSendLocation },
      { title: 'Cancel' },
    ]
    const cancelButtonIndex = options.length - 1

    showActionSheetWithOptions(
      {
        options: options.map(o => o.title),
        cancelButtonIndex,
      },
      async buttonIndex => {
        if (buttonIndex !== undefined) {
          const selectedOption = options[buttonIndex]
          selectedOption?.action?.()
        }
      }
    )
  }, [showActionSheetWithOptions, handlePickImage, handleTakePicture, handleSendLocation])

  const renderIconComponent = useCallback(() => {
    if (renderIcon)
      return renderIcon()

    const wrapperColorStyle = colorScheme === 'dark' ? styles.wrapper_dark : {}
    const iconTextColorStyle = colorScheme === 'dark' ? styles.iconText_dark : {}

    return (
      <View style={[styles.wrapper, wrapperColorStyle, wrapperStyle]}>
        <Text style={[styles.iconText, iconTextColorStyle, iconTextStyle]}>+</Text>
      </View>
    )
  }, [renderIcon, wrapperStyle, iconTextStyle, colorScheme])

  return (
    <RectButton
      style={[styles.container, containerStyle]}
      onPress={onActionsPress}
    >
      {renderIconComponent()}
    </RectButton>
  )
}

export default CustomActions

const styles = StyleSheet.create({
  container: {
    paddingLeft: 8,
    paddingRight: 4,
    paddingVertical: 7,
  },
  wrapper: {
    width: 26,
    height: 26,
    borderRadius: 13,
    borderColor: '#b2b2b2',
    borderWidth: 2,
    alignItems: 'center',
    justifyContent: 'center',
  },
  wrapper_dark: {
    borderColor: '#666',
  },
  iconText: {
    color: '#b2b2b2',
    fontWeight: 'bold',
    fontSize: 16,
    lineHeight: 16,
    backgroundColor: 'transparent',
    textAlign: 'center',
  },
  iconText_dark: {
    color: '#999',
  },
})


================================================
FILE: example/example-expo/CustomView/index.tsx
================================================
import React, { useCallback } from 'react'
import { Platform, View, Text } from 'react-native'
import Constants from 'expo-constants'
import * as Linking from 'expo-linking'
import { RectButton } from 'react-native-gesture-handler'
import MapView from 'react-native-maps'
import commonStyles from '../../styles'
import styles from './styles'
import type { CustomViewProps } from './types'

const CustomView = ({
  currentMessage,
  containerStyle,
  mapViewStyle,
}: CustomViewProps) => {
  const openMapAsync = useCallback(async () => {
    if (Platform.OS === 'web') {
      alert('Opening the map is not supported.')
      return
    }

    const { location } = currentMessage

    const url = Platform.select({
      ios: `http://maps.apple.com/?ll=${location.latitude},${location.longitude}`,
      default: `http://maps.google.com/?q=${location.latitude},${location.longitude}`,
    })

    try {
      const supported = await Linking.canOpenURL(url)
      if (supported)
        return Linking.openURL(url)

      alert('Opening the map is not supported.')
    } catch (e) {
      alert(e.message)
    }
  }, [currentMessage])

  if (currentMessage.location) {
    // Check if Google Maps API key is configured for Android
    const androidApiKey = Constants.expoConfig?.android?.config?.googleMaps?.apiKey
    const hasAndroidApiKey = androidApiKey && androidApiKey !== 'YOUR_GOOGLE_MAPS_API_KEY'
    const shouldShowPlaceholder = Platform.OS === 'android' && !hasAndroidApiKey

    // Use native MapView for iOS or Android with API key
    return (
      <RectButton
        style={containerStyle}
        onPress={openMapAsync}
      >
        {
          shouldShowPlaceholder
            ? (
              <View style={[commonStyles.center, styles.mapView, mapViewStyle]}>
                <Text style={commonStyles.textCenter}>Google Maps API key is not configured.</Text>
              </View>
            ) : (
              <MapView
                style={[styles.mapView, mapViewStyle]}
                region={{
                  latitude: currentMessage.location.latitude,
                  longitude: currentMessage.location.longitude,
                  latitudeDelta: 0.0922,
                  longitudeDelta: 0.0421,
                }}
                scrollEnabled={false}
                zoomEnabled={false}
              />
            )
        }
      </RectButton>
    )
  }

  return null
}

export default CustomView


================================================
FILE: example/example-expo/CustomView/index.web.tsx
================================================
import React, { useCallback } from 'react'
import {
  View,
  Text,
} from 'react-native'
import { RectButton } from 'react-native-gesture-handler'
import styles from './styles'
import type { CustomViewProps } from './types'

const CustomView = ({
  currentMessage,
  containerStyle,
}: CustomViewProps) => {
  const openMapAsync = useCallback(async () => {
    alert('Opening the map is not supported.')
  }, [])

  if (currentMessage.location)
    return (
      <RectButton onPress={openMapAsync}>
        <View style={[styles.mapView, containerStyle]}>
          <Text style={styles.text}>
            Maps are not supported on web, sorry!
          </Text>
        </View>
      </RectButton>
    )

  return null
}

export default CustomView


================================================
FILE: example/example-expo/CustomView/styles.ts
================================================
import { StyleSheet } from 'react-native'

export default StyleSheet.create({
  mapView: {
    width: 150,
    height: 100,
    borderRadius: 13,
    alignItems: 'center',
    justifyContent: 'center',
    margin: 3,
  },

  text: {
    color: 'tomato',
    fontWeight: 'bold',
  },
})


================================================
FILE: example/example-expo/CustomView/types.ts
================================================
import { StyleProp, ViewStyle } from 'react-native'
import { IMessage } from '../../../src'

export interface CustomViewProps {
  currentMessage: IMessage
  containerStyle?: StyleProp<ViewStyle>
  mapViewStyle?: StyleProp<ViewStyle>
}


================================================
FILE: example/example-expo/data/earlierMessages.ts
================================================
import dayjs from 'dayjs'
import { IMessage } from 'react-native-gifted-chat'

const date = dayjs().subtract(1, 'year')

export default (): IMessage[] => [
  {
    _id: Math.round(Math.random() * 1000000),
    text:
      'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text:
      'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text:
      'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text:
      'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text: 'React Native lets you build mobile apps using only JavaScript',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text: 'React Native lets you build mobile apps using only JavaScript',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text: 'React Native lets you build mobile apps using only JavaScript',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text: 'React Native lets you build mobile apps using only JavaScript',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text: 'React Native lets you build mobile apps using only JavaScript',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text: 'React Native lets you build mobile apps using only JavaScript',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text: 'React Native lets you build mobile apps using only JavaScript',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text: 'React Native lets you build mobile apps using only JavaScript',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text: 'React Native lets you build mobile apps using only JavaScript',
    createdAt: date.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    _id: Math.round(Math.random() * 1000000),
    text: 'This is a system message.',
    createdAt: date.toDate(),
    system: true,
    user: {
      _id: 0,
    },
  },
]


================================================
FILE: example/example-expo/data/messages.ts
================================================
import dayjs from 'dayjs'
import { IMessage } from 'react-native-gifted-chat'

const date1 = dayjs()
const date2 = date1.clone().subtract(1, 'day')
const date3 = date2.clone().subtract(1, 'week')

const messages: IMessage[] = [
  {
    text: '',
    createdAt: date3.toDate(),
    audio:
      'https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3',
    user: {
      _id: 2,
      name: 'John Doe',
    },
  },
  {
    text: '',
    createdAt: date3.toDate(),
    video: 'https://media.giphy.com/media/3o6ZthZjk09Xx4ktZ6/giphy.mp4',
    user: {
      _id: 2,
      name: 'John Doe',
    },
  },
  {
    text: 'This is a quick reply. Do you love Gifted Chat? (checkbox)',
    createdAt: date3.toDate(),
    quickReplies: {
      type: 'checkbox', // or 'checkbox',
      values: [
        {
          title: 'Yes',
          value: 'yes',
        },
        {
          title: 'Yes, let me show you with a picture!',
          value: 'yes_picture',
        },
        {
          title: 'Nope. What?',
          value: 'no',
        },
      ],
    },
    user: {
      _id: 2,
      name: 'John Doe',
    },
  },
  {
    text: 'This is a quick reply. Do you love Gifted Chat? (radio) KEEP IT',
    createdAt: date3.toDate(),
    quickReplies: {
      type: 'radio', // or 'checkbox',
      keepIt: true,
      values: [
        {
          title: '😋 Yes',
          value: 'yes',
        },
        {
          title:
            '📷 Yes, let me show you with a picture!',
          value: 'yes_picture',
        },
        {
          title: '😞 Nope. What?',
          value: 'no',
        },
      ],
    },
    user: {
      _id: 2,
      name: 'John Doe',
    },
  },
  {
    text: 'Are you building a chat app?',
    createdAt: date3.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    text: 'Yes, and I use #GiftedChat!',
    createdAt: date3.toDate(),
    user: {
      _id: 2,
      name: 'John Doe',
    },
    sent: true,
    received: true,
  },
  {
    text: 'Where are you?',
    createdAt: date3.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    text: '',
    createdAt: date2.toDate(),
    user: {
      _id: 2,
      name: 'John Doe',
    },
    sent: true,
    received: true,
    location: {
      latitude: 48.864601,
      longitude: 2.398704,
    },
  },
  {
    text: 'Send me a picture!',
    createdAt: date2.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    text: 'Paris',
    createdAt: date2.toDate(),
    user: {
      _id: 2,
      name: 'John Doe',
    },
    image:
      'https://static.vecteezy.com/system/resources/thumbnails/003/407/768/small/eiffel-tower-at-paris-france-free-photo.jpg',
    sent: true,
    received: true,
  },
  {
    text: '#awesome',
    createdAt: date1.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
  },
  {
    text: '#awesome 2',
    createdAt: date1.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
    sent: true,
    received: true,
  },
  {
    text: '#awesome 3',
    createdAt: date1.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
    sent: true,
    received: true,
  },
  {
    text: 'Hi',
    createdAt: date1.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
    sent: true,
    received: true,
  },
  {
    text: '👋',
    createdAt: date1.toDate(),
    user: {
      _id: 1,
      name: 'Developer',
    },
    sent: true,
    received: true,
  },
].map((message, index) => ({
  ...message,
  _id: index + 1,
})).reverse()

export default messages


================================================
FILE: example/example-expo/mediaUtils.ts
================================================
import * as ImagePicker from 'expo-image-picker'

import { getCurrentPositionAsync, LocationObjectCoords, requestForegroundPermissionsAsync } from 'expo-location'

export async function getLocationAsync (): Promise<LocationObjectCoords | undefined> {
  const response = await requestForegroundPermissionsAsync()
  if (!response.granted)
    return

  const location = await getCurrentPositionAsync()
  if (!location)
    return

  return location.coords
}

export async function pickImageAsync (): Promise<string[] | undefined> {
  const response = await ImagePicker.requestMediaLibraryPermissionsAsync()
  if (!response.granted)
    return

  const result = await ImagePicker.launchImageLibraryAsync({
    allowsEditing: true,
    aspect: [4, 3],
  })

  if (result.canceled)
    return

  return result.assets.map(({ uri }) => uri)
}

export async function takePictureAsync (): Promise<string[] | undefined> {
  const response = await ImagePicker.requestCameraPermissionsAsync()
  if (!response.granted)
    return

  const result = await ImagePicker.launchCameraAsync({
    allowsEditing: true,
    aspect: [4, 3],
  })

  if (result.canceled)
    return

  return result.assets.map(({ uri }) => uri)
}


================================================
FILE: example/example-gifted-chat/README.md
================================================
# example-gifted-chat

Lots of people using `react-native-gifted-chat` might want to know that...

1. There are so many render props could use, but what should I pass?
2. How could I customize each component, but leaving its functionality.

> For example, said that you want to customize the `send button`, when you pass your own component to `renderSend`, after that you will lose the functionality of clean up text when a message has been sent.

---

I made this for anyone who wants to know how to use the render Props properly.

##### Such as:

- renderInputToolbar
- renderActions
- renderComposer
- renderSend
- renderAvatar
- renderBubble
- renderSystemMessage
- renderMessage
- renderMessageText
- renderCustomView

<img src="https://i.imgur.com/dbkc7I4.png" alt="Example for customize components" width="300">

================================================
FILE: example/example-gifted-chat/src/Chats.tsx
================================================
import React, { useState, useEffect } from 'react'
import { useColorScheme } from 'react-native'
import { GiftedChat, IMessage } from 'react-native-gifted-chat'
import { useKeyboardVerticalOffset } from '../../hooks/useKeyboardVerticalOffset'
import {
  renderAvatar,
  renderBubble,
  renderSystemMessage,
  renderMessage,
  renderMessageText,
  renderCustomView,
} from './customComponents'
import { RenderInputToolbar, RenderActions, RenderComposer, RenderSend } from './InputToolbar'
import initialMessages from './messages'

const Chats = () => {
  const [text, setText] = useState('')
  const [messages, setMessages] = useState<IMessage[]>([])
  const colorScheme = useColorScheme()
  const isDark = colorScheme === 'dark'
  const keyboardVerticalOffset = useKeyboardVerticalOffset()

  useEffect(() => {
    setMessages(initialMessages.reverse())
  }, [])

  const onSend = (newMessages: IMessage[] = []) => {
    setMessages(prevMessages => GiftedChat.append(prevMessages, newMessages))
  }

  return (
    <GiftedChat
      messages={messages}
      text={text}
      onSend={onSend}
      user={{
        _id: 1,
        name: 'Aaron',
        avatar: 'https://placeimg.com/150/150/any',
      }}
      isAlignedTop
      isSendButtonAlwaysVisible
      isScrollToBottomEnabled
      // isUserAvatarVisible
      isAvatarOnTop
      isUsernameVisible
      onPressAvatar={console.log}
      renderInputToolbar={RenderInputToolbar}
      renderActions={RenderActions}
      renderComposer={RenderComposer}
      renderSend={RenderSend}
      renderAvatar={renderAvatar}
      renderBubble={renderBubble}
      renderSystemMessage={renderSystemMessage}
      renderMessage={renderMessage}
      renderMessageText={renderMessageText}
      // renderMessageImage
      renderCustomView={renderCustomView}
      isCustomViewBottom
      messagesContainerStyle={{ backgroundColor: isDark ? '#1a1a1a' : 'indigo' }}
      textInputProps={{
        style: isDark && { backgroundColor: '#2a2a2a', color: '#fff' },
        onChangeText: setText,
      }}
      keyboardAvoidingViewProps={{ keyboardVerticalOffset }}
    />
  )
}

export default Chats


================================================
FILE: example/example-gifted-chat/src/InputToolbar.tsx
================================================
import React from 'react'
import { Image, useColorScheme } from 'react-native'
import {
  InputToolbar,
  Actions,
  Composer,
  Send,
  IMessage,
  InputToolbarProps,
  ActionsProps,
  ComposerProps,
  SendProps,
} from 'react-native-gifted-chat'

// These are React components (not render functions) so they can use hooks
export const RenderInputToolbar = React.memo((props: InputToolbarProps<IMessage>) => {
  const colorScheme = useColorScheme()
  const isDark = colorScheme === 'dark'

  return (
    <InputToolbar
      {...props}
      containerStyle={{
        backgroundColor: isDark ? '#1a1a1a' : '#222B45',
        paddingTop: 6,
      }}
      primaryStyle={{ alignItems: 'center' }}
    />
  )
})

export const RenderActions = React.memo((props: ActionsProps) => {
  const colorScheme = useColorScheme()
  const isDark = colorScheme === 'dark'

  return (
    <Actions
      {...props}
      containerStyle={{
        width: 44,
        height: 44,
        alignItems: 'center',
        justifyContent: 'center',
        marginLeft: 4,
        marginRight: 4,
        marginBottom: 0,
      }}
      icon={() => (
        <Image
          style={{ width: 32, height: 32 }}
          source={{
            uri: 'https://placeimg.com/32/32/any',
          }}
        />
      )}
      options={{
        'Choose From Library': () => {
          console.log('Choose From Library')
        },
        Cancel: () => {
          console.log('Cancel')
        },
      }}
      optionTintColor={isDark ? '#ffffff' : '#222B45'}
    />
  )
})

export const RenderComposer = React.memo((props: ComposerProps) => {
  const colorScheme = useColorScheme()
  const isDark = colorScheme === 'dark'

  return (
    <Composer
      {...props}
      textInputProps={{
        style: {
          color: isDark ? '#ffffff' : '#222B45',
          backgroundColor: isDark ? '#2a2a2a' : '#EDF1F7',
          borderWidth: 1,
          borderRadius: 5,
          borderColor: isDark ? '#3a3a3a' : '#E4E9F2',
          paddingTop: 8.5,
          paddingHorizontal: 12,
          marginLeft: 0,
        },
        placeholderTextColor: isDark ? '#888' : undefined,
      }}
    />
  )
})

export const RenderSend = React.memo((props: SendProps<IMessage>) => (
  <Send
    {...props}
    isDisabled={!props.text}
    containerStyle={{
      width: 44,
      height: 44,
      alignItems: 'center',
      justifyContent: 'center',
      marginHorizontal: 4,
    }}
  >
    <Image
      style={{ width: 32, height: 32 }}
      source={{
        uri: 'https://placeimg.com/32/32/any',
      }}
    />
  </Send>
))


================================================
FILE: example/example-gifted-chat/src/customComponents.tsx
================================================
import React from 'react'
import { View, Text } from 'react-native'
import {
  Avatar,
  Bubble,
  SystemMessage,
  Message,
  MessageText,
  IMessage,
  User,
  AvatarProps,
  BubbleProps,
  SystemMessageProps,
  MessageProps,
  MessageTextProps,
} from 'react-native-gifted-chat'

export const renderAvatar = (props: AvatarProps<IMessage>) => (
  <Avatar
    {...props}
    containerStyle={{ left: { borderWidth: 3, borderColor: 'red' } }}
    imageStyle={{ left: { borderWidth: 3, borderColor: 'blue' } }}
  />
)

export const renderBubble = (props: BubbleProps<IMessage>) => (
  <Bubble
    {...props}
    // renderTime={() => <Text>Time</Text>}
    // renderTicks={() => <Text>Ticks</Text>}
    containerStyle={{
      left: { borderColor: 'teal', borderWidth: 8 },
    }}
    wrapperStyle={{
      left: { borderColor: 'tomato', borderWidth: 4 },
    }}
    bottomContainerStyle={{
      left: { borderColor: 'purple', borderWidth: 4 },
    }}
    usernameStyle={{ color: 'tomato', fontWeight: '100' }}
    containerToNextStyle={{
      left: { borderColor: 'navy', borderWidth: 4 },
    }}
    containerToPreviousStyle={{
      left: { borderColor: 'mediumorchid', borderWidth: 4 },
    }}
  />
)

export const renderSystemMessage = (props: SystemMessageProps<IMessage>) => (
  <SystemMessage
    {...props}
    containerStyle={{ backgroundColor: 'pink' }}
    wrapperStyle={{ borderWidth: 10, borderColor: 'white' }}
    textStyle={{ color: 'crimson', fontWeight: '900' }}
  />
)

export const renderMessage = (props: MessageProps<IMessage>) => (
  <Message
    {...props}
    // renderDay={() => <Text>Date</Text>}
    containerStyle={{
      left: { backgroundColor: 'lime' },
      right: { backgroundColor: 'gold' },
    }}
  />
)

export const renderMessageText = (props: MessageTextProps<IMessage>) => (
  <MessageText
    {...props}
    containerStyle={{
      left: { backgroundColor: 'yellow' },
      right: { backgroundColor: 'purple' },
    }}
    textStyle={{
      left: { color: 'red' },
      right: { color: 'green' },
    }}
    linkStyle={{
      left: { color: 'orange' },
      right: { color: 'orange' },
    }}
    customTextStyle={{ fontSize: 24, lineHeight: 24 }}
  />
)

interface CustomViewProps {
  user: User
}

export const renderCustomView = ({ user }: CustomViewProps) => (
  <View style={{ minHeight: 20, alignItems: 'center' }}>
    <Text>
      Current user:
      {user.name}
    </Text>
    <Text>From CustomView</Text>
  </View>
)


================================================
FILE: example/example-gifted-chat/src/messages.ts
================================================
import { IMessage } from 'react-native-gifted-chat'

const messages: IMessage[] = [
  {
    text: `Hello this is an example of the ParsedText, links like http://www.google.com or http://www.facebook.com are clickable and phone number 444-555-6666 can call too.
        But you can also do more with this package, for example Bob will change style and David too. foo@gmail.com
        And the magic number is 42!
        #react #react-native`,
    createdAt: new Date(Date.UTC(2016, 5, 13, 17, 20, 0)),
    user: {
      _id: 1,
      name: 'John Doe',
      avatar: 'https://placeimg.com/140/140/any',
    },
  },
  {
    text: 'Come on!',
    createdAt: new Date(Date.UTC(2016, 5, 15, 18, 20, 0)),
    user: {
      _id: 2,
      name: 'John Doe',
      avatar: 'https://placeimg.com/140/140/any',
    },
  },
  {
    text: 'This is a quick reply. Do you love Gifted Chat? (checkbox)',
    createdAt: new Date(Date.UTC(2016, 5, 15, 17, 20, 0)),
    user: {
      _id: 2,
      name: 'John Doe',
      avatar: 'https://placeimg.com/140/140/any',
    },
    quickReplies: {
      type: 'checkbox', // or 'radio',
      values: [
        {
          title: 'Yes',
          value: 'yes',
        },
        {
          title: 'Yes, let me show you with a picture!',
          value: 'yes_picture',
        },
        {
          title: 'Nope. What?',
          value: 'no',
        },
      ],
    },
  },
  {
    text: 'This is a quick reply. Do you love Gifted Chat? (radio) KEEP IT',
    createdAt: new Date(Date.UTC(2016, 5, 14, 17, 20, 0)),
    user: {
      _id: 2,
      name: 'John Doe',
      avatar: 'https://placeimg.com/140/140/any',
    },
    quickReplies: {
      type: 'radio', // or 'checkbox',
      keepIt: true,
      values: [
        {
          title: '😋 Yes',
          value: 'yes',
        },
        {
          title: '📷 Yes, let me show you with a picture!',
          value: 'yes_picture',
        },
        {
          title: '😞 Nope. What?',
          value: 'no',
        },
      ],
    },
  },
  {
    text: 'Hi! I work from home today!',
    createdAt: new Date(Date.UTC(2016, 5, 13, 17, 20, 0)),
    user: {
      _id: 1,
      name: 'John Doe',
      avatar: 'https://placeimg.com/140/140/any',
    },
    image: 'https://placeimg.com/960/540/any',
  },
  {
    text: 'Hello developer',
    createdAt: new Date(Date.UTC(2016, 5, 12, 17, 20, 0)),
    user: {
      _id: 2,
      name: 'John Doe',
      avatar: 'https://placeimg.com/140/140/any',
    },
  },
  {
    text: 'This is a system message',
    createdAt: new Date(Date.UTC(2016, 5, 11, 17, 20, 0)),
    system: true,
    user: {
      _id: 0,
    },
  },
].map((message, index) => ({
  ...message,
  _id: index + 1,
})).reverse()

export default messages


================================================
FILE: example/example-slack-message/README.md
================================================
# "Slack" style UI example

Credit and inspiration comes from [Slack](https://slack.com/).

Screenshots to compare:

| Default style | "Slack" style |
|:-------------:|:-------------:|
| <img src="./example-default-style.png" alt="Example with default style" width="300"> | <img src="./example-slack-style.png" alt="Example with Slack style" width="300"> |


================================================
FILE: example/example-slack-message/src/SlackBubble.tsx
================================================
import React, { useCallback, useMemo } from 'react'
import {
  Text,
  StyleSheet,
  View,
  Platform,
  StyleProp,
  ViewStyle,
  TextStyle,
} from 'react-native'
import * as Clipboard from 'expo-clipboard'
import { RectButton } from 'react-native-gesture-handler'
import {
  MessageText,
  MessageImage,
  Time,
  utils,
  useChatContext,
  IMessage,
  User,
} from 'react-native-gifted-chat'

const { isSameUser, isSameDay } = utils

interface Props {
  touchableProps: object
  onLongPress?: (context: unknown, currentMessage: object) => void
  renderMessageImage?: (props: Props) => React.ReactNode
  renderMessageText?: (props: Props) => React.ReactNode
  renderCustomView?: (props: Props) => React.ReactNode
  renderUsername?: (props: Props) => React.ReactNode
  renderTime?: (props: Props) => React.ReactNode
  renderTicks?: (currentMessage: IMessage) => React.ReactNode
  currentMessage: IMessage
  nextMessage: IMessage
  previousMessage: IMessage
  user: User
  containerStyle?: {
    left?: StyleProp<ViewStyle>
    right?: StyleProp<ViewStyle>
  }
  wrapperStyle?: {
    left?: StyleProp<ViewStyle>
    right?: StyleProp<ViewStyle>
  }
  messageTextStyle: StyleProp<TextStyle>
  usernameStyle: StyleProp<TextStyle>
  tickStyle: StyleProp<TextStyle>
  containerToNextStyle: {
    left: StyleProp<ViewStyle>
    right: StyleProp<ViewStyle>
  }
  containerToPreviousStyle: {
    left: StyleProp<ViewStyle>
    right: StyleProp<ViewStyle>
  }
  imageStyle?: StyleProp<ViewStyle>
  textStyle?: StyleProp<TextStyle>
  position: 'left' | 'right'
}

const Bubble = (props: Props) => {
  const {
    touchableProps,
    onLongPress,
    renderCustomView,
    currentMessage,
    previousMessage,
    user,
    containerStyle,
    wrapperStyle,
    usernameStyle,
    tickStyle,
    position,
  } = props

  const context = useChatContext()

  const handleLongPress = useCallback(() => {
    if (onLongPress) {
      onLongPress(context, currentMessage)
      return
    }

    if (!currentMessage.text)
      return

    const options = ['Copy Text', 'Cancel']
    const cancelButtonIndex = options.length - 1
    context.actionSheet().showActionSheetWithOptions(
      {
        options,
        cancelButtonIndex,
      },
      (buttonIndex?: number) => {
        switch (buttonIndex) {
          case 0:
            Clipboard.setStringAsync(currentMessage.text)
            break
        }
      }
    )
  }, [context, currentMessage, onLongPress])

  const renderMessageText = useCallback(() => {
    if (currentMessage.text) {
      if (props.renderMessageText)
        return props.renderMessageText(props)

      return (
        <MessageText
          {...props}
          textStyle={{
            left: [
              styles.standardFont,
              styles.slackMessageText,
              props.textStyle,
              props.messageTextStyle,
            ],
          }}
        />
      )
    }

    return null
  }, [currentMessage.text, props])

  const renderMessageImage = useCallback(() => {
    if (currentMessage.image) {
      if (props.renderMessageImage)
        return props.renderMessageImage(props)

      return (
        <MessageImage
          {...props}
          imageStyle={[styles.slackImage, props.imageStyle]}
        />
      )
    }

    return null
  }, [currentMessage.image, props])

  const renderTicks = useCallback(() => {
    const { currentMessage } = props

    if (props.renderTicks)
      return props.renderTicks(currentMessage)

    if (currentMessage.user._id !== user._id)
      return null

    if (currentMessage.sent || currentMessage.received)
      return (
        <View style={[styles.headerItem, styles.tickView]}>
          {currentMessage.sent && (
            <Text
              style={[styles.standardFont, styles.tick, tickStyle]}
            >
              ✓
            </Text>
          )}
          {currentMessage.received && (
            <Text
              style={[styles.standardFont, styles.tick, tickStyle]}
            >
              ✓
            </Text>
          )}
        </View>
      )

    return null
  }, [props, tickStyle, user._id])

  const renderUsername = useCallback(() => {
    const username = currentMessage.user.name
    if (username) {
      if (props.renderUsername)
        return props.renderUsername(props)

      return (
        <Text
          style={[
            styles.standardFont,
            styles.headerItem,
            styles.username,
            usernameStyle,
          ]}
        >
          {username}
        </Text>
      )
    }
    return null
  }, [currentMessage.user.name, props, usernameStyle])

  const renderTime = useCallback(() => {
    if (currentMessage.createdAt) {
      if (props.renderTime)
        return props.renderTime(props)

      return (
        <Time
          {...props}
          containerStyle={{ left: [styles.timeContainer] }}
          textStyle={{
            left: [
              styles.standardFont,
              styles.headerItem,
              styles.time,
              props.textStyle,
            ],
          }}
        />
      )
    }

    return null
  }, [currentMessage.createdAt, props])

  const isSameThread = useMemo(() =>
    isSameUser(currentMessage, previousMessage) &&
    isSameDay(currentMessage, previousMessage)
  , [currentMessage, previousMessage])

  const messageHeader = useMemo(() => {
    if (isSameThread)
      return null

    return (
      <View style={styles.headerView}>
        {renderUsername()}
        {renderTime()}
        {renderTicks()}
      </View>
    )
  }, [isSameThread, renderUsername, renderTime, renderTicks])

  return (
    <View style={[styles.container, containerStyle?.[position]]}>
      <RectButton
        onLongPress={handleLongPress}
        accessibilityRole='text'
        {...touchableProps}
      >
        <View style={[styles.wrapper, wrapperStyle?.[position]]}>
          <View>
            {renderCustomView?.(props)}
            {messageHeader}
            {renderMessageImage()}
            {renderMessageText()}
          </View>
        </View>
      </RectButton>
    </View>
  )
}

// Note: Everything is forced to be "left" positioned with this component.
// The "right" position is only used in the default Bubble.
const styles = StyleSheet.create({
  standardFont: {
    fontSize: 15,
  },
  slackMessageText: {
    marginLeft: 0,
    marginRight: 0,
  },
  container: {
    flex: 1,
    alignItems: 'flex-start',
  },
  wrapper: {
    marginRight: 60,
    minHeight: 20,
    justifyContent: 'flex-end',
  },
  username: {
    fontWeight: 'bold',
  },
  time: {
    textAlign: 'left',
    fontSize: 12,
  },
  timeContainer: {
    marginLeft: 0,
    marginRight: 0,
    marginBottom: 0,
  },
  headerItem: {
    marginRight: 10,
  },
  headerView: {
    // Try to align it better with the avatar on Android.
    marginTop: Platform.OS === 'android' ? -2 : 0,
    flexDirection: 'row',
    alignItems: 'baseline',
  },
  tick: {
    backgroundColor: 'transparent',
    color: 'white',
  },
  tickView: {
    flexDirection: 'row',
  },
  slackImage: {
    borderRadius: 3,
    marginLeft: 0,
    marginRight: 0,
  },
})

export default Bubble


================================================
FILE: example/example-slack-message/src/SlackMessage.tsx
================================================
import React, { useCallback, useMemo } from 'react'
import {
  View,
  StyleSheet,
  StyleProp,
  ViewStyle,
} from 'react-native'

import { Avatar, Day, utils } from 'react-native-gifted-chat'
import type { DayProps, BubbleProps, AvatarProps, IMessage } from 'react-native-gifted-chat'
import Bubble from './SlackBubble'

const { isSameUser, isSameDay } = utils

interface Props {
  renderAvatar?: (props: AvatarProps<IMessage>) => void
  renderBubble?: (props: BubbleProps<IMessage>) => void
  renderDay?: (props: DayProps) => void
  currentMessage: any
  nextMessage?: any
  previousMessage?: any
  containerStyle?: {
    left: StyleProp<ViewStyle>
    right: StyleProp<ViewStyle>
  }
}

const Message = (props: Props) => {
  const {
    currentMessage,
    nextMessage,
    previousMessage,
    containerStyle,
  } = props

  const getInnerComponentProps = useCallback(() => {
    return {
      ...props,
      position: 'left',
      isSameUser,
      isSameDay,
      containerStyle: props.containerStyle?.left,
    }
  }, [props])

  const renderDay = useCallback(() => {
    if (currentMessage.createdAt) {
      const dayProps = getInnerComponentProps()

      if (props.renderDay)
        return props.renderDay(dayProps)

      return <Day {...dayProps} />
    }

    return null
  }, [currentMessage.createdAt, getInnerComponentProps, props])

  const renderBubble = useCallback(() => {
    const bubbleProps = getInnerComponentProps()

    if (props.renderBubble)
      return props.renderBubble(bubbleProps)

    return <Bubble {...bubbleProps} />
  }, [getInnerComponentProps, props])

  const renderAvatar = useCallback(() => {
    let extraStyle
    if (
      isSameUser(currentMessage, previousMessage) &&
      isSameDay(currentMessage, previousMessage)
    )
      // Set the invisible avatar height to 0, but keep the width, padding, etc.
      extraStyle = { height: 0 }

    const avatarProps = getInnerComponentProps()

    if (props.renderAvatar)
      return props.renderAvatar(avatarProps)

    return (
      <Avatar
        {...avatarProps}
        imageStyle={{
          left: [styles.slackAvatar, avatarProps.imageStyle, extraStyle],
        }}
      />
    )
  }, [currentMessage, previousMessage, getInnerComponentProps, props])

  const marginBottom = useMemo(() =>
    isSameUser(
      currentMessage,
      nextMessage
    )
      ? 2
      : 10
  , [currentMessage, nextMessage])

  return (
    <View>
      {renderDay()}
      <View
        style={[
          styles.container,
          { marginBottom },
          containerStyle,
        ]}
      >
        {renderAvatar()}
        {renderBubble()}
      </View>
    </View>
  )
}

export default Message

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    alignItems: 'flex-end',
    justifyContent: 'flex-start',
    marginLeft: 8,
    marginRight: 0,
  },
  slackAvatar: {
    // The bottom should roughly line up with the first line of message text.
    height: 40,
    width: 40,
    borderRadius: 3,
  },
})


================================================
FILE: example/hooks/use-color-scheme.ts
================================================
export { useColorScheme } from 'react-native'


================================================
FILE: example/hooks/use-color-scheme.web.ts
================================================
import { useEffect, useState } from 'react'
import { useColorScheme as useRNColorScheme } from 'react-native'

/**
 * To support static rendering, this value needs to be re-calculated on the client side for web
 */
export function useColorScheme () {
  const [hasHydrated, setHasHydrated] = useState(false)

  useEffect(() => {
    setHasHydrated(true)
  }, [])

  const colorScheme = useRNColorScheme()

  if (hasHydrated)
    return colorScheme

  return 'light'
}


================================================
FILE: example/hooks/use-theme-color.ts
================================================
/**
 * Learn more about light and dark modes:
 * https://docs.expo.dev/guides/color-schemes/
 */

import { Colors } from '@/constants/theme'
import { useColorScheme } from '@/hooks/use-color-scheme'

export function useThemeColor (
  props: { light?: string, dark?: string },
  colorName: keyof typeof Colors.light & keyof typeof Colors.dark
) {
  const theme = useColorScheme() ?? 'light'
  const colorFromProps = props[theme]

  if (colorFromProps)
    return colorFromProps
  else
    return Colors[theme][colorName]
}


================================================
FILE: example/hooks/useKeyboardVerticalOffset.ts
================================================
import { useHeaderHeight } from '@react-navigation/elements'

/**
 * Hook to get the correct keyboardVerticalOffset for GiftedChat.
 *
 * The offset equals the distance from the screen top to the GiftedChat container top.
 * Uses useHeaderHeight() which includes status bar + navigation header height on iOS.
 *
 * Note: This hook requires the component to be rendered inside a proper navigation screen
 * (not conditional rendering). If useHeaderHeight returns 0, ensure your chat screen
 * is a real navigation screen with a visible header.
 *
 * @returns {number} keyboardVerticalOffset to pass to keyboardAvoidingViewProps
 */
export function useKeyboardVerticalOffset () {
  // useHeaderHeight() returns status bar + navigation header height on iOS
  return useHeaderHeight()
}


================================================
FILE: example/ios/.gitignore
================================================
# OSX
#
.DS_Store

# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
.xcode.env.local

# Bundle artifacts
*.jsbundle

# CocoaPods
/Pods/


================================================
FILE: example/ios/.xcode.env
================================================
# This `.xcode.env` file is versioned and is used to source the environment
# used when running script phases inside Xcode.
# To customize your local environment, you can create an `.xcode.env.local`
# file that is not versioned.

# NODE_BINARY variable contains the PATH to the node executable.
#
# Customize the NODE_BINARY variable here.
# For example, to use nvm with brew, add the following line
# . "$(brew --prefix nvm)/nvm.sh" --no-use
export NODE_BINARY=$(command -v node)


================================================
FILE: example/ios/Podfile
================================================
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")

require 'json'
podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}

def ccache_enabled?(podfile_properties)
  # Environment variable takes precedence
  return ENV['USE_CCACHE'] == '1' if ENV['USE_CCACHE']
  
  # Fall back to Podfile properties
  podfile_properties['apple.ccacheEnabled'] == 'true'
end

ENV['RCT_NEW_ARCH_ENABLED'] ||= '0' if podfile_properties['newArchEnabled'] == 'false'
ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] ||= podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
ENV['RCT_USE_RN_DEP'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
ENV['RCT_USE_PREBUILT_RNCORE'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'
platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'

prepare_react_native_project!

target 'example' do
  use_expo_modules!

  if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
    config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
  else
    config_command = [
      'node',
      '--no-warnings',
      '--eval',
      'require(\'expo/bin/autolinking\')',
      'expo-modules-autolinking',
      'react-native-config',
      '--json',
      '--platform',
      'ios'
    ]
  end

# @generated begin react-native-maps - expo prebuild (DO NOT MODIFY) sync-e9cc66c360abe50bc66d89fffb3c55b034d7d369
  pod 'react-native-google-maps', path: File.dirname(`node --print "require.resolve('react-native-maps/package.json')"`)
# @generated end react-native-maps
  config = use_native_modules!(config_command)

  use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
  use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']

  use_react_native!(
    :path => config[:reactNativePath],
    :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
    # An absolute path to your application root.
    :app_path => "#{Pod::Config.instance.installation_root}/..",
    :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
  )

  post_install do |installer|
    react_native_post_install(
      installer,
      config[:reactNativePath],
      :mac_catalyst_enabled => false,
      :ccache_enabled => ccache_enabled?(podfile_properties),
    )
  end
end


================================================
FILE: example/ios/Podfile.properties.json
================================================
{
  "expo.jsEngine": "hermes",
  "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
  "newArchEnabled": "true"
}


================================================
FILE: example/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images": [
    {
      "filename": "App-Icon-1024x1024@1x.png",
      "idiom": "universal",
      "platform": "ios",
      "size": "1024x1024"
    }
  ],
  "info": {
    "version": 1,
    "author": "expo"
  }
}

================================================
FILE: example/ios/example/Images.xcassets/Contents.json
================================================
{
  "info" : {
    "version" : 1,
    "author" : "expo"
  }
}


================================================
FILE: example/ios/example/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>CADisableMinimumFrameDurationOnPhone</key>
    <true/>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleDisplayName</key>
    <string>example</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0.0</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleURLTypes</key>
    <array>
      <dict>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>example</string>
          <string>com.anonymous.example</string>
        </array>
      </dict>
    </array>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>GMSApiKey</key>
    <string>YOUR_GOOGLE_MAPS_API_KEY</string>
    <key>LSMinimumSystemVersion</key>
    <string>12.0</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>NSAppTransportSecurity</key>
    <dict>
      <key>NSAllowsArbitraryLoads</key>
      <false/>
      <key>NSAllowsLocalNetworking</key>
      <true/>
    </dict>
    <key>NSCameraUsageDescription</key>
    <string>Allow $(PRODUCT_NAME) to access your camera</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>Allow $(PRODUCT_NAME) to access your location</string>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>Allow $(PRODUCT_NAME) to access your location</string>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>Allow $(PRODUCT_NAME) to access your location</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>Allow $(PRODUCT_NAME) to access your microphone</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>Allow $(PRODUCT_NAME) to access your photos</string>
    <key>NSUserActivityTypes</key>
    <array>
      <string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
    </array>
    <key>RCTNewArchEnabled</key>
    <true/>
    <key>UILaunchStoryboardName</key>
    <string>SplashScreen</string>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
      <string>arm64</string>
    </array>
    <key>UIRequiresFullScreen</key>
    <false/>
    <key>UIStatusBarStyle</key>
    <string>UIStatusBarStyleDefault</string>
    <key>UISupportedInterfaceOrientations</key>
    <array>
      <string>UIInterfaceOrientationPortrait</string>
      <string>UIInterfaceOrientationPortraitUpsideDown</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
      <string>UIInterfaceOrientationPortrait</string>
      <string>UIInterfaceOrientationPortraitUpsideDown</string>
      <string>UIInterfaceOrientationLandscapeLeft</string>
      <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UIUserInterfaceStyle</key>
    <string>Automatic</string>
    <key>UIViewControllerBasedStatusBarAppearance</key>
    <false/>
  </dict>
</plist>

================================================
FILE: example/ios/example/PrivacyInfo.xcprivacy
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>NSPrivacyAccessedAPITypes</key>
	<array>
		<dict>
			<key>NSPrivacyAccessedAPIType</key>
			<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>CA92.1</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyAccessedAPIType</key>
			<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>0A2A.1</string>
				<string>3B52.1</string>
				<string>C617.1</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyAccessedAPIType</key>
			<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>E174.1</string>
				<string>85F4.1</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyAccessedAPIType</key>
			<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>35F9.1</string>
			</array>
		</dict>
	</array>
	<key>NSPrivacyCollectedDataTypes</key>
	<array/>
	<key>NSPrivacyTracking</key>
	<false/>
</dict>
</plist>


================================================
FILE: example/ios/example/SplashScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EXPO-VIEWCONTROLLER-1">
    <device id="retina6_12" orientation="portrait" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24053.1"/>
        <capability name="Named colors" minToolsVersion="9.0"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="System colors in document resources" minToolsVersion="11.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <scene sceneID="EXPO-SCENE-1">
            <objects>
                <viewController storyboardIdentifier="SplashScreenViewController" id="EXPO-VIEWCONTROLLER-1" sceneMemberID="viewController">
                    <view key="view" userInteractionEnabled="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="EXPO-ContainerView" userLabel="ContainerView">
                        <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                        <subviews>
                            <imageView id="EXPO-SplashScreen" userLabel="SplashScreenLogo" image="SplashScreenLogo" contentMode="scaleAspectFit" clipsSubviews="true" userInteractionEnabled="false" translatesAutoresizingMaskIntoConstraints="false">
                                <rect key="frame" x="96.5" y="326" width="200" height="200"/>
                            </imageView>
                        </subviews>
                        <viewLayoutGuide key="safeArea" id="Rmq-lb-GrQ"/>
                        <constraints>
                            <constraint firstItem="EXPO-SplashScreen" firstAttribute="centerX" secondItem="EXPO-ContainerView" secondAttribute="centerX" id="cad2ab56f97c5429bf29decf850647a4216861d4"/>
                            <constraint firstItem="EXPO-SplashScreen" firstAttribute="centerY" secondItem="EXPO-ContainerView" secondAttribute="centerY" id="1a145271b085b6ce89b1405a310f5b1bb7656595"/>
                        </constraints>
                        <color key="backgroundColor" name="SplashScreenBackground"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="0.0" y="0.0"/>
        </scene>
    </scenes>
    <resources>
        <image name="SplashScreenLogo" width="200" height="200"/>
        <systemColor name="systemBackgroundColor">
            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
        <namedColor name="SplashScreenBackground">
            <color alpha="1.000" blue="1.00000000000000" green="1.00000000000000" red="1.00000000000000" customColorSpace="sRGB" colorSpace="custom"/>
        </namedColor>
    </resources>
</document>

================================================
FILE: example/ios/example/Supporting/Expo.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>EXUpdatesCheckOnLaunch</key>
    <string>ALWAYS</string>
    <key>EXUpdatesEnabled</key>
    <false/>
    <key>EXUpdatesLaunchWaitMs</key>
    <integer>0</integer>
  </dict>
</plist>

================================================
FILE: example/ios/example.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 54;
	objects = {

/* Begin PBXBuildFile section */
		0EB4AC0857AAFB0D77472CBD /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B945C814238A5FEBE89E96F5 /* libPods-example.a */; };
		13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
		3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
		95774C49D12665FBD013B7B6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1F72D30C6AB981B0917D041F /* PrivacyInfo.xcprivacy */; };
		BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
		D2455EE1B33CDCC769A93446 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A038F507BB039A5B209DF9 /* ExpoModulesProvider.swift */; };
		F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
		13B07F961A680F5B00A75B9A /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; };
		13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = example/Images.xcassets; sourceTree = "<group>"; };
		13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = example/Info.plist; sourceTree = "<group>"; };
		1F72D30C6AB981B0917D041F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = example/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
		2C6A65C7C222B5FD1DFFEB3D /* Pods-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.debug.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.debug.xcconfig"; sourceTree = "<group>"; };
		5ACC43DD9EC8D36FFB24F025 /* Pods-example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.release.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.release.xcconfig"; sourceTree = "<group>"; };
		A9A038F507BB039A5B209DF9 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-example/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
		AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = example/SplashScreen.storyboard; sourceTree = "<group>"; };
		B945C814238A5FEBE89E96F5 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
		BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
		ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
		F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = example/AppDelegate.swift; sourceTree = "<group>"; };
		F11748442D0722820044C1D9 /* example-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "example-Bridging-Header.h"; path = "example/example-Bridging-Header.h"; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				0EB4AC0857AAFB0D77472CBD /* libPods-example.a in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		13B07FAE1A68108700A75B9A /* example */ = {
			isa = PBXGroup;
			children = (
				F11748412D0307B40044C1D9 /* AppDelegate.swift */,
				F11748442D0722820044C1D9 /* example-Bridging-Header.h */,
				BB2F792B24A3F905000567C9 /* Supporting */,
				13B07FB51A68108700A75B9A /* Images.xcassets */,
				13B07FB61A68108700A75B9A /* Info.plist */,
				AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
				1F72D30C6AB981B0917D041F /* PrivacyInfo.xcprivacy */,
			);
			name = example;
			sourceTree = "<group>";
		};
		2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
			isa = PBXGroup;
			children = (
				ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
				B945C814238A5FEBE89E96F5 /* libPods-example.a */,
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
		73C3B28C69CD6BB9738FA204 /* example */ = {
			isa = PBXGroup;
			children = (
				A9A038F507BB039A5B209DF9 /* ExpoModulesProvider.swift */,
			);
			name = example;
			sourceTree = "<group>";
		};
		832341AE1AAA6A7D00B99B32 /* Libraries */ = {
			isa = PBXGroup;
			children = (
			);
			name = Libraries;
			sourceTree = "<group>";
		};
		83CBB9F61A601CBA00E9B192 = {
			isa = PBXGroup;
			children = (
				13B07FAE1A68108700A75B9A /* example */,
				832341AE1AAA6A7D00B99B32 /* Libraries */,
				83CBBA001A601CBA00E9B192 /* Products */,
				2D16E6871FA4F8E400B85C8A /* Frameworks */,
				D52C4A25CB8634558A3AF68D /* Pods */,
				C493ADA3EAB4EF422CF3A61E /* ExpoModulesProviders */,
			);
			indentWidth = 2;
			sourceTree = "<group>";
			tabWidth = 2;
			usesTabs = 0;
		};
		83CBBA001A601CBA00E9B192 /* Products */ = {
			isa = PBXGroup;
			children = (
				13B07F961A680F5B00A75B9A /* example.app */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		BB2F792B24A3F905000567C9 /* Supporting */ = {
			isa = PBXGroup;
			children = (
				BB2F792C24A3F905000567C9 /* Expo.plist */,
			);
			name = Supporting;
			path = example/Supporting;
			sourceTree = "<group>";
		};
		C493ADA3EAB4EF422CF3A61E /* ExpoModulesProviders */ = {
			isa = PBXGroup;
			children = (
				73C3B28C69CD6BB9738FA204 /* example */,
			);
			name = ExpoModulesProviders;
			sourceTree = "<group>";
		};
		D52C4A25CB8634558A3AF68D /* Pods */ = {
			isa = PBXGroup;
			children = (
				2C6A65C7C222B5FD1DFFEB3D /* Pods-example.debug.xcconfig */,
				5ACC43DD9EC8D36FFB24F025 /* Pods-example.release.xcconfig */,
			);
			name = Pods;
			path = Pods;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		13B07F861A680F5B00A75B9A /* example */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */;
			buildPhases = (
				08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
				E3A154A212F22893CC38D97B /* [Expo] Configure project */,
				13B07F871A680F5B00A75B9A /* Sources */,
				13B07F8C1A680F5B00A75B9A /* Frameworks */,
				13B07F8E1A680F5B00A75B9A /* Resources */,
				00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
				800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
				55CE56BF3547841532F83FD6 /* [CP] Embed Pods Frameworks */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = example;
			productName = example;
			productReference = 13B07F961A680F5B00A75B9A /* example.app */;
			productType = "com.apple.product-type.application";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		83CBB9F71A601CBA00E9B192 /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastUpgradeCheck = 1130;
				TargetAttributes = {
					13B07F861A680F5B00A75B9A = {
						LastSwiftMigration = 1250;
					};
				};
			};
			buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "example" */;
			compatibilityVersion = "Xcode 3.2";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = 83CBB9F61A601CBA00E9B192;
			productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				13B07F861A680F5B00A75B9A /* example */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		13B07F8E1A680F5B00A75B9A /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
				13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
				3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
				95774C49D12665FBD013B7B6 /* PrivacyInfo.xcprivacy in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
		00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
			isa = PBXShellScriptBuildPhase;
			alwaysOutOfDate = 1;
			buildActionMask = 2147483647;
			files = (
			);
			inputPaths = (
				"$(SRCROOT)/.xcode.env",
				"$(SRCROOT)/.xcode.env.local",
			);
			name = "Bundle React Native code and images";
			outputPaths = (
			);
			runOnlyForDeploymentPostprocessing = 0;
			shellPath = /bin/sh;
			shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n  source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n  source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n  export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n  # Set the entry JS file using the bundler's entry resolution.\n  export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n  # Use Expo CLI\n  export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n  # Default Expo CLI command for bundling\n  export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n  source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n  source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
		};
		08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
			isa = PBXShellScriptBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			inputFileListPaths = (
			);
			inputPaths = (
				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
				"${PODS_ROOT}/Manifest.lock",
			);
			name = "[CP] Check Pods Manifest.lock";
			outputFileListPaths = (
			);
			outputPaths = (
				"$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt",
			);
			runOnlyForDeploymentPostprocessing = 0;
			shellPath = /bin/sh;
			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
			showEnvVarsInLog = 0;
		};
		55CE56BF3547841532F83FD6 /* [CP] Embed Pods Frameworks */ = {
			isa = PBXShellScriptBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			inputPaths = (
				"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh",
				"${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React",
				"${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies",
				"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
			);
			name = "[CP] Embed Pods Frameworks";
			outputPaths = (
				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework",
				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework",
				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
			);
			runOnlyForDeploymentPostprocessing = 0;
			shellPath = /bin/sh;
			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n";
			showEnvVarsInLog = 0;
		};
		800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
			isa = PBXShellScriptBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			inputPaths = (
				"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh",
				"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
				"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle",
				"${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle",
				"${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle",
				"${PODS_CONFIGURATION_BUILD_DIR}/GoogleMaps/GoogleMapsResources.bundle",
				"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle",
				"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle",
				"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle",
				"${PODS_CONFIGURATION_BUILD_DIR}/react-native-google-maps/GoogleMapsPrivacy.bundle",
				"${PODS_CONFIGURATION_BUILD_DIR}/react-native-maps/ReactNativeMapsPrivacy.bundle",
			);
			name = "[CP] Copy Pods Resources";
			outputPaths = (
				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle",
				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle",
				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle",
				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMapsResources.bundle",
				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle",
				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle",
				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle",
				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMapsPrivacy.bundle",
				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ReactNativeMapsPrivacy.bundle",
			);
			runOnlyForDeploymentPostprocessing = 0;
			shellPath = /bin/sh;
			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
			showEnvVarsInLog = 0;
		};
		E3A154A212F22893CC38D97B /* [Expo] Configure project */ = {
			isa = PBXShellScriptBuildPhase;
			alwaysOutOfDate = 1;
			buildActionMask = 2147483647;
			files = (
			);
			inputFileListPaths = (
			);
			inputPaths = (
				"$(SRCROOT)/.xcode.env",
				"$(SRCROOT)/.xcode.env.local",
				"$(SRCROOT)/example/example.entitlements",
				"$(SRCROOT)/Pods/Target Support Files/Pods-example/expo-configure-project.sh",
			);
			name = "[Expo] Configure project";
			outputFileListPaths = (
			);
			outputPaths = (
				"$(SRCROOT)/Pods/Target Support Files/Pods-example/ExpoModulesProvider.swift",
			);
			runOnlyForDeploymentPostprocessing = 0;
			shellPath = /bin/sh;
			shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-example/expo-configure-project.sh\"\n";
		};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		13B07F871A680F5B00A75B9A /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */,
				D2455EE1B33CDCC769A93446 /* ExpoModulesProvider.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin XCBuildConfiguration section */
		13B07F941A680F5B00A75B9A /* Debug */ = {
			isa = XCBuildConfiguration;
			baseConfigurationReference = 2C6A65C7C222B5FD1DFFEB3D /* Pods-example.debug.xcconfig */;
			buildSettings = {
				ASSETCATAL
Download .txt
gitextract_6i4y2rb6/

├── .expo-shared/
│   └── assets.json
├── .github/
│   ├── FUNDING.yml
│   ├── copilot-instructions.md
│   ├── stale.yml
│   └── workflows/
│       ├── main.yml
│       └── stale.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .npmignore
├── CHANGELOG.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── babel.config.cjs
├── codecov.yml
├── eslint.config.js
├── example/
│   ├── .gitignore
│   ├── README.md
│   ├── app/
│   │   ├── (tabs)/
│   │   │   ├── _layout.tsx
│   │   │   ├── explore.tsx
│   │   │   └── index.tsx
│   │   ├── _layout.tsx
│   │   ├── chat/
│   │   │   ├── _layout.tsx
│   │   │   ├── basic.tsx
│   │   │   ├── customized-rendering.tsx
│   │   │   ├── links.tsx
│   │   │   ├── reply.tsx
│   │   │   └── slack.tsx
│   │   └── modal.tsx
│   ├── app.json
│   ├── babel.config.js
│   ├── components/
│   │   ├── chat-examples/
│   │   │   ├── BasicExample.tsx
│   │   │   ├── CustomizedRenderingExample.tsx
│   │   │   ├── LinksExample.tsx
│   │   │   ├── ReplyExample.tsx
│   │   │   └── SlackExample.tsx
│   │   ├── external-link.tsx
│   │   ├── haptic-tab.tsx
│   │   ├── hello-wave.tsx
│   │   ├── parallax-scroll-view.tsx
│   │   ├── themed-text.tsx
│   │   ├── themed-view.tsx
│   │   └── ui/
│   │       ├── collapsible.tsx
│   │       ├── icon-symbol.ios.tsx
│   │       └── icon-symbol.tsx
│   ├── constants/
│   │   └── theme.ts
│   ├── eslint.config.js
│   ├── example-expo/
│   │   ├── AccessoryBar.tsx
│   │   ├── CustomActions.tsx
│   │   ├── CustomView/
│   │   │   ├── index.tsx
│   │   │   ├── index.web.tsx
│   │   │   ├── styles.ts
│   │   │   └── types.ts
│   │   ├── data/
│   │   │   ├── earlierMessages.ts
│   │   │   └── messages.ts
│   │   └── mediaUtils.ts
│   ├── example-gifted-chat/
│   │   ├── README.md
│   │   └── src/
│   │       ├── Chats.tsx
│   │       ├── InputToolbar.tsx
│   │       ├── customComponents.tsx
│   │       └── messages.ts
│   ├── example-slack-message/
│   │   ├── README.md
│   │   └── src/
│   │       ├── SlackBubble.tsx
│   │       └── SlackMessage.tsx
│   ├── hooks/
│   │   ├── use-color-scheme.ts
│   │   ├── use-color-scheme.web.ts
│   │   ├── use-theme-color.ts
│   │   └── useKeyboardVerticalOffset.ts
│   ├── ios/
│   │   ├── .gitignore
│   │   ├── .xcode.env
│   │   ├── Podfile
│   │   ├── Podfile.properties.json
│   │   ├── example/
│   │   │   ├── Images.xcassets/
│   │   │   │   ├── AppIcon.appiconset/
│   │   │   │   │   └── Contents.json
│   │   │   │   └── Contents.json
│   │   │   ├── Info.plist
│   │   │   ├── PrivacyInfo.xcprivacy
│   │   │   ├── SplashScreen.storyboard
│   │   │   └── Supporting/
│   │   │       └── Expo.plist
│   │   ├── example.xcodeproj/
│   │   │   ├── project.pbxproj
│   │   │   └── xcshareddata/
│   │   │       └── xcschemes/
│   │   │           └── example.xcscheme
│   │   └── example.xcworkspace/
│   │       └── contents.xcworkspacedata
│   ├── metro.config.js
│   ├── package.json
│   ├── scripts/
│   │   └── reset-project.js
│   ├── styles/
│   │   └── index.ts
│   ├── tsconfig.json
│   └── utils/
│       └── styleUtils.ts
├── expoSnack/
│   ├── ExpoSnack.tsx
│   ├── README.md
│   └── package.json
├── jest.config.cjs
├── package.json
├── src/
│   ├── Actions.tsx
│   ├── Avatar.tsx
│   ├── Bubble/
│   │   ├── index.tsx
│   │   ├── styles.ts
│   │   └── types.ts
│   ├── Color.ts
│   ├── Composer.tsx
│   ├── Constant.ts
│   ├── Day/
│   │   ├── index.tsx
│   │   ├── styles.ts
│   │   └── types.ts
│   ├── GiftedAvatar.tsx
│   ├── GiftedChat/
│   │   ├── index.tsx
│   │   ├── styles.ts
│   │   └── types.ts
│   ├── GiftedChatContext.ts
│   ├── InputToolbar.tsx
│   ├── LoadEarlierMessages.tsx
│   ├── Message/
│   │   ├── index.tsx
│   │   ├── styles.ts
│   │   └── types.ts
│   ├── MessageAudio.tsx
│   ├── MessageImage.tsx
│   ├── MessageReply.tsx
│   ├── MessageText.tsx
│   ├── MessageVideo.tsx
│   ├── MessagesContainer/
│   │   ├── components/
│   │   │   ├── DayAnimated/
│   │   │   │   ├── index.tsx
│   │   │   │   ├── styles.ts
│   │   │   │   └── types.ts
│   │   │   └── Item/
│   │   │       ├── index.tsx
│   │   │       └── types.ts
│   │   ├── index.tsx
│   │   ├── styles.ts
│   │   └── types.ts
│   ├── Models.ts
│   ├── QuickReplies.tsx
│   ├── Reply/
│   │   ├── index.ts
│   │   └── types.ts
│   ├── ReplyPreview.tsx
│   ├── Send.tsx
│   ├── SystemMessage.tsx
│   ├── Time.tsx
│   ├── TypingIndicator/
│   │   ├── index.tsx
│   │   ├── styles.ts
│   │   └── types.ts
│   ├── __tests__/
│   │   ├── Actions.test.tsx
│   │   ├── Avatar.test.tsx
│   │   ├── Bubble.test.tsx
│   │   ├── Color.test.tsx
│   │   ├── Composer.test.tsx
│   │   ├── Constant.test.tsx
│   │   ├── Day.test.tsx
│   │   ├── DayAnimated.test.tsx
│   │   ├── GiftedAvatar.test.tsx
│   │   ├── GiftedChat.test.tsx
│   │   ├── InputToolbar.test.tsx
│   │   ├── LoadEarlier.test.tsx
│   │   ├── Message.test.tsx
│   │   ├── MessageImage.test.tsx
│   │   ├── MessageReply.test.tsx
│   │   ├── MessageText.test.tsx
│   │   ├── MessagesContainer.test.tsx
│   │   ├── ReplyPreview.test.tsx
│   │   ├── Send.test.tsx
│   │   ├── SystemMessage.test.tsx
│   │   ├── Time.test.tsx
│   │   ├── __snapshots__/
│   │   │   ├── Actions.test.tsx.snap
│   │   │   ├── Avatar.test.tsx.snap
│   │   │   ├── Bubble.test.tsx.snap
│   │   │   ├── Color.test.tsx.snap
│   │   │   ├── Composer.test.tsx.snap
│   │   │   ├── Constant.test.tsx.snap
│   │   │   ├── Day.test.tsx.snap
│   │   │   ├── DayAnimated.test.tsx.snap
│   │   │   ├── GiftedAvatar.test.tsx.snap
│   │   │   ├── GiftedChat.test.tsx.snap
│   │   │   ├── InputToolbar.test.tsx.snap
│   │   │   ├── LoadEarlier.test.tsx.snap
│   │   │   ├── Message.test.tsx.snap
│   │   │   ├── MessageImage.test.tsx.snap
│   │   │   ├── MessageReply.test.tsx.snap
│   │   │   ├── MessageText.test.tsx.snap
│   │   │   ├── ReplyPreview.test.tsx.snap
│   │   │   ├── Send.test.tsx.snap
│   │   │   ├── SystemMessage.test.tsx.snap
│   │   │   └── Time.test.tsx.snap
│   │   ├── data.ts
│   │   └── utils.test.ts
│   ├── components/
│   │   ├── MessageReply.tsx
│   │   ├── ReplyPreview.tsx
│   │   └── TouchableOpacity.tsx
│   ├── hooks/
│   │   ├── useColorScheme.ts
│   │   └── useUpdateLayoutEffect.ts
│   ├── index.ts
│   ├── linkParser.tsx
│   ├── logging.ts
│   ├── styles.ts
│   ├── types.ts
│   └── utils.ts
├── tests/
│   └── setup.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (149 symbols across 71 files)

FILE: example/app/(tabs)/_layout.tsx
  function TabLayout (line 9) | function TabLayout () {

FILE: example/app/(tabs)/explore.tsx
  type ChatExample (line 11) | type ChatExample = 'basic' | 'customized-rendering' | 'slack' | 'links' ...
  function ExploreScreen (line 21) | function ExploreScreen () {

FILE: example/app/(tabs)/index.tsx
  function HomeScreen (line 10) | function HomeScreen () {

FILE: example/app/_layout.tsx
  function RootLayout (line 16) | function RootLayout () {

FILE: example/app/chat/_layout.tsx
  function ChatLayout (line 6) | function ChatLayout () {

FILE: example/app/modal.tsx
  function ModalScreen (line 7) | function ModalScreen () {

FILE: example/components/chat-examples/BasicExample.tsx
  function BasicExample (line 12) | function BasicExample () {

FILE: example/components/chat-examples/CustomizedRenderingExample.tsx
  function CustomizedRenderingExample (line 6) | function CustomizedRenderingExample () {

FILE: example/components/chat-examples/ReplyExample.tsx
  type IChatMessage (line 9) | interface IChatMessage extends IMessage {
  function ReplyExample (line 13) | function ReplyExample () {

FILE: example/components/chat-examples/SlackExample.tsx
  function SlackExample (line 9) | function SlackExample () {

FILE: example/components/external-link.tsx
  type Props (line 5) | type Props = Omit<ComponentProps<typeof Link>, 'href'> & { href: Href & ...
  function ExternalLink (line 7) | function ExternalLink ({ href, ...rest }: Props) {

FILE: example/components/haptic-tab.tsx
  function HapticTab (line 5) | function HapticTab (props: BottomTabBarButtonProps) {

FILE: example/components/hello-wave.tsx
  function HelloWave (line 3) | function HelloWave () {

FILE: example/components/parallax-scroll-view.tsx
  constant HEADER_HEIGHT (line 14) | const HEADER_HEIGHT = 250
  type Props (line 16) | type Props = PropsWithChildren<{
  function ParallaxScrollView (line 21) | function ParallaxScrollView ({

FILE: example/components/themed-text.tsx
  type ThemedTextProps (line 5) | type ThemedTextProps = TextProps & {
  function ThemedText (line 11) | function ThemedText ({

FILE: example/components/themed-view.tsx
  type ThemedViewProps (line 5) | type ThemedViewProps = ViewProps & {
  function ThemedView (line 10) | function ThemedView ({ style, lightColor, darkColor, ...otherProps }: Th...

FILE: example/components/ui/collapsible.tsx
  function Collapsible (line 11) | function Collapsible ({ children, title }: PropsWithChildren & { title: ...

FILE: example/components/ui/icon-symbol.ios.tsx
  function IconSymbol (line 4) | function IconSymbol ({

FILE: example/components/ui/icon-symbol.tsx
  type IconMapping (line 8) | type IconMapping = Record<SymbolViewProps['name'], ComponentProps<typeof...
  type IconSymbolName (line 9) | type IconSymbolName = keyof typeof MAPPING
  constant MAPPING (line 16) | const MAPPING = {
  function IconSymbol (line 28) | function IconSymbol ({

FILE: example/example-expo/AccessoryBar.tsx
  function AccessoryBar (line 13) | function AccessoryBar ({ onSend, isTyping, user }: { onSend: (messages: ...

FILE: example/example-expo/CustomActions.tsx
  type Props (line 20) | interface Props {

FILE: example/example-expo/CustomView/types.ts
  type CustomViewProps (line 4) | interface CustomViewProps {

FILE: example/example-expo/mediaUtils.ts
  function getLocationAsync (line 5) | async function getLocationAsync (): Promise<LocationObjectCoords | undef...
  function pickImageAsync (line 17) | async function pickImageAsync (): Promise<string[] | undefined> {
  function takePictureAsync (line 33) | async function takePictureAsync (): Promise<string[] | undefined> {

FILE: example/example-gifted-chat/src/customComponents.tsx
  type CustomViewProps (line 89) | interface CustomViewProps {

FILE: example/example-slack-message/src/SlackBubble.tsx
  type Props (line 25) | interface Props {

FILE: example/example-slack-message/src/SlackMessage.tsx
  type Props (line 15) | interface Props {

FILE: example/hooks/use-color-scheme.web.ts
  function useColorScheme (line 7) | function useColorScheme () {

FILE: example/hooks/use-theme-color.ts
  function useThemeColor (line 9) | function useThemeColor (

FILE: example/hooks/useKeyboardVerticalOffset.ts
  function useKeyboardVerticalOffset (line 15) | function useKeyboardVerticalOffset () {

FILE: example/utils/styleUtils.ts
  function getColorSchemeStyle (line 1) | function getColorSchemeStyle<T>(styles: T, baseName: string, colorScheme...

FILE: expoSnack/ExpoSnack.tsx
  function getColorSchemeStyle (line 21) | function getColorSchemeStyle<T>(styles: T, baseName: string, colorScheme...
  function getLocationAsync (line 232) | async function getLocationAsync() {
  function pickImageAsync (line 244) | async function pickImageAsync() {
  function takePictureAsync (line 260) | async function takePictureAsync() {
  type CustomActionsProps (line 280) | interface CustomActionsProps {
  type AccessoryBarProps (line 423) | interface AccessoryBarProps {
  type CustomViewProps (line 545) | interface CustomViewProps {
  function ChatExample (line 594) | function ChatExample() {
  function App (line 688) | function App() {

FILE: src/Actions.tsx
  type ActionsProps (line 16) | interface ActionsProps {
  function Actions (line 26) | function Actions ({

FILE: src/Avatar.tsx
  type Styles (line 13) | interface Styles {
  type AvatarProps (line 55) | interface AvatarProps<TMessage extends IMessage> {
  function Avatar (line 70) | function Avatar<TMessage extends IMessage = IMessage> (

FILE: src/Bubble/types.ts
  type RenderMessageImageProps (line 27) | type RenderMessageImageProps<TMessage extends IMessage> = Omit<
  type RenderMessageVideoProps (line 33) | type RenderMessageVideoProps<TMessage extends IMessage> = Omit<
  type RenderMessageAudioProps (line 39) | type RenderMessageAudioProps<TMessage extends IMessage> = Omit<
  type RenderMessageTextProps (line 45) | type RenderMessageTextProps<TMessage extends IMessage> = Omit<
  type BubbleReplyProps (line 52) | interface BubbleReplyProps<TMessage extends IMessage> extends MessageRep...
  type BubbleProps (line 59) | interface BubbleProps<TMessage extends IMessage> {

FILE: src/Composer.tsx
  type ComposerProps (line 15) | interface ComposerProps {
  function Composer (line 21) | function Composer ({

FILE: src/Constant.ts
  constant DATE_FORMAT (line 1) | const DATE_FORMAT = 'D MMMM'
  constant TIME_FORMAT (line 2) | const TIME_FORMAT = 'LT'
  constant TEST_ID (line 4) | const TEST_ID = {

FILE: src/Day/index.tsx
  function Day (line 22) | function Day ({

FILE: src/Day/types.ts
  type DayProps (line 7) | interface DayProps {

FILE: src/GiftedAvatar.tsx
  type GiftedAvatarProps (line 43) | interface GiftedAvatarProps {
  function GiftedAvatar (line 51) | function GiftedAvatar (

FILE: src/GiftedChat/index.tsx
  function GiftedChat (line 36) | function GiftedChat<TMessage extends IMessage = IMessage> (
  function GiftedChatWrapper (line 348) | function GiftedChatWrapper<TMessage extends IMessage = IMessage> (props:...

FILE: src/GiftedChat/types.ts
  type GiftedChatProps (line 34) | interface GiftedChatProps<TMessage extends IMessage> extends Partial<Omi...

FILE: src/GiftedChatContext.ts
  type IGiftedChatContext (line 6) | interface IGiftedChatContext {

FILE: src/InputToolbar.tsx
  type InputToolbarProps (line 15) | interface InputToolbarProps<TMessage extends IMessage> {
  function InputToolbar (line 39) | function InputToolbar<TMessage extends IMessage = IMessage> (

FILE: src/LoadEarlierMessages.tsx
  type LoadEarlierMessagesProps (line 15) | interface LoadEarlierMessagesProps {

FILE: src/Message/index.tsx
  type ReplyIconProps (line 19) | interface ReplyIconProps {

FILE: src/Message/types.ts
  type MessageProps (line 10) | interface MessageProps<TMessage extends IMessage> {

FILE: src/MessageAudio.tsx
  function MessageAudio (line 16) | function MessageAudio () {

FILE: src/MessageImage.tsx
  type ModalContentProps (line 30) | interface ModalContentProps {
  function ModalContent (line 38) | function ModalContent({ isVisible, imageSource, modalImageDimensions, im...
  type MessageImageProps (line 107) | interface MessageImageProps<TMessage extends IMessage> {
  function MessageImage (line 115) | function MessageImage<TMessage extends IMessage = IMessage> ({

FILE: src/MessageReply.tsx
  type MessageReplyProps (line 18) | interface MessageReplyProps<TMessage extends IMessage> {
  function MessageReply (line 29) | function MessageReply<TMessage extends IMessage = IMessage> ({

FILE: src/MessageText.tsx
  type MessageTextProps (line 14) | type MessageTextProps<TMessage extends IMessage> = {
  function MessageText (line 38) | function MessageText<TMessage extends IMessage>({

FILE: src/MessageVideo.tsx
  function MessageVideo (line 16) | function MessageVideo () {

FILE: src/MessagesContainer/components/DayAnimated/types.ts
  type DayAnimatedProps (line 5) | interface DayAnimatedProps extends Omit<DayProps, 'createdAt'> {

FILE: src/MessagesContainer/components/Item/types.ts
  type ItemProps (line 4) | interface ItemProps<TMessage extends IMessage> extends MessagesContainer...

FILE: src/MessagesContainer/types.ts
  type AnimatedListProps (line 30) | type AnimatedListProps<TMessage extends IMessage = IMessage> = Partial<
  type AnimatedList (line 36) | type AnimatedList<TMessage> = FlatList<TMessage>
  type MessagesContainerProps (line 38) | interface MessagesContainerProps<TMessage extends IMessage = IMessage>
  type State (line 90) | interface State {
  type ViewLayout (line 95) | interface ViewLayout {
  type DaysPositions (line 102) | type DaysPositions = { [key: string]: ViewLayout & { createdAt: number } }

FILE: src/Models.ts
  type Omit (line 3) | type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
  type LeftRightStyle (line 5) | interface LeftRightStyle<T> {
  type renderFunction (line 10) | type renderFunction = (x: unknown) => React.ReactNode
  type User (line 12) | interface User {
  type Reply (line 18) | interface Reply {
  type QuickReplies (line 24) | interface QuickReplies {
  type ReplyMessage (line 30) | interface ReplyMessage extends Pick<IMessage, '_id' | 'text' | 'user' | ...
  type IMessage (line 32) | interface IMessage {
  type IChatMessage (line 52) | type IChatMessage = IMessage
  type MessageVideoProps (line 54) | interface MessageVideoProps<TMessage extends IMessage> {
  type MessageAudioProps (line 61) | interface MessageAudioProps<TMessage extends IMessage> {

FILE: src/QuickReplies.tsx
  type QuickRepliesProps (line 44) | interface QuickRepliesProps<TMessage extends IMessage = IMessage> {
  function QuickReplies (line 62) | function QuickReplies ({

FILE: src/Reply/types.ts
  type SwipeToReplyProps (line 11) | interface SwipeToReplyProps<TMessage extends IMessage> {
  type ReplyPreviewStyleProps (line 31) | interface ReplyPreviewStyleProps {
  type MessageReplyStyleProps (line 43) | interface MessageReplyStyleProps {
  type ReplyProps (line 63) | interface ReplyProps<TMessage extends IMessage> {

FILE: src/ReplyPreview.tsx
  type ReplyPreviewProps (line 9) | interface ReplyPreviewProps {
  function ReplyPreview (line 19) | function ReplyPreview ({

FILE: src/Send.tsx
  type SendProps (line 18) | interface SendProps<TMessage extends IMessage> {

FILE: src/SystemMessage.tsx
  type SystemMessageProps (line 14) | interface SystemMessageProps<TMessage extends IMessage> {
  function SystemMessage (line 23) | function SystemMessage<TMessage extends IMessage> ({

FILE: src/Time.tsx
  type TimeProps (line 25) | interface TimeProps<TMessage extends IMessage> {

FILE: src/TypingIndicator/types.ts
  type TypingIndicatorProps (line 3) | interface TypingIndicatorProps {

FILE: src/__tests__/data.ts
  constant DEFAULT_TEST_MESSAGE (line 3) | const DEFAULT_TEST_MESSAGE: IMessage = {

FILE: src/components/MessageReply.tsx
  type MessageReplyProps (line 17) | interface MessageReplyProps<TMessage extends IMessage = IMessage> {
  function MessageReply (line 86) | function MessageReply<TMessage extends IMessage = IMessage> ({

FILE: src/components/ReplyPreview.tsx
  constant ANIMATION_DURATION (line 25) | const ANIMATION_DURATION = 200
  constant ANIMATION_EASING (line 26) | const ANIMATION_EASING = Easing.bezier(0.25, 0.1, 0.25, 1)
  constant DEFAULT_HEIGHT (line 27) | const DEFAULT_HEIGHT = 68
  type ReplyPreviewProps (line 29) | interface ReplyPreviewProps {
  function ReplyPreview (line 108) | function ReplyPreview ({

FILE: src/components/TouchableOpacity.tsx
  type TouchableOpacityProps (line 9) | type TouchableOpacityProps = Omit<React.ComponentProps<typeof BaseButton...

FILE: src/hooks/useColorScheme.ts
  function useColorScheme (line 10) | function useColorScheme() {

FILE: src/hooks/useUpdateLayoutEffect.ts
  function useUpdateLayoutEffect (line 9) | function useUpdateLayoutEffect (

FILE: src/linkParser.tsx
  type LinkType (line 4) | type LinkType = 'url' | 'email' | 'phone' | 'mention' | 'hashtag'
  type ParsedLink (line 6) | interface ParsedLink {
  type LinkMatcher (line 14) | interface LinkMatcher {
  type LinkParserProps (line 25) | interface LinkParserProps {
  constant DEFAULT_MATCHERS (line 42) | const DEFAULT_MATCHERS: LinkMatcher[] = [
  function parseLinks (line 80) | function parseLinks(text: string, matchers: LinkMatcher[]): ParsedLink[] {
  function removeOverlaps (line 110) | function removeOverlaps(links: ParsedLink[]): ParsedLink[] {
  function LinkParser (line 133) | function LinkParser({

FILE: src/styles.ts
  function getColorSchemeStyle (line 13) | function getColorSchemeStyle<T>(styles: T, baseName: string, colorScheme...
  function getStyleWithPosition (line 18) | function getStyleWithPosition<T>(styles: T, baseName: string, position?:...

FILE: src/utils.ts
  function renderComponentOrElement (line 5) | function renderComponentOrElement<TProps extends Record<string, any>>(
  function isSameDay (line 39) | function isSameDay (
  function isSameUser (line 55) | function isSameUser (
  function processCallbackArguments (line 67) | function processCallbackArguments (args: unknown[]): unknown[] {
  function useCallbackDebounced (line 83) | function useCallbackDebounced<T extends (...args: any[]) => any>(callbac...
  function useCallbackThrottled (line 106) | function useCallbackThrottled<T extends (...args: any[]) => any>(callbac...
Condensed preview — 193 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (497K chars).
[
  {
    "path": ".expo-shared/assets.json",
    "chars": 687,
    "preview": "{\n  \"5c6d215cbde93d15ae63d2ea43dfe8bf8a79a53146382cf7f3f0089bec2fc5d6\": true,\n  \"d0e86e9f72936ac85597d9cd6415cf22a3c2085"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 655,
    "preview": "# These are supported funding model platforms\n\ngithub: [faridsafi, kesha-antonov, xcarpentier, johan-dutoit]\npatreon: # "
  },
  {
    "path": ".github/copilot-instructions.md",
    "chars": 6219,
    "preview": "# React Native Gifted Chat\n\nThe most complete chat UI for React Native & Web. This is a TypeScript React Native componen"
  },
  {
    "path": ".github/stale.yml",
    "chars": 705,
    "preview": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a "
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 671,
    "preview": "name: Main CI\n\non:\n  pull_request:\n    branches:\n      - master\n\njobs:\n  checks:\n    runs-on: ubuntu-latest\n\n    strateg"
  },
  {
    "path": ".github/workflows/stale.yml",
    "chars": 558,
    "preview": "name: Close stale issues\n\non:\n  schedule:\n    - cron: '0 0 * * *' # runs daily\n\njobs:\n  stale:\n    runs-on: ubuntu-lates"
  },
  {
    "path": ".gitignore",
    "chars": 326,
    "preview": ".DS_Store\nnode_modules/\n.expo/\nnpm-debug.log\nlib/\nTODO.md\n.idea\n.vscode\nExponent-*.app\n*.log\ncoverage/\nweb-build/\n.eslin"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 17,
    "preview": "yarn lint-staged\n"
  },
  {
    "path": ".npmignore",
    "chars": 338,
    "preview": ".expo/\n.expo-shared/\n.circleci/\n.github/\n.vscode/\nexample/\nexample-expo/\nexample-slack-message/\nexample-gifted-chat/\nscr"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 16586,
    "preview": "# Changelog\n\n## [3.3.2] - 2026-01-22\n\n### 🐛 Bug Fixes\n- Fixed `React.memo` and `React.forwardRef` components not renderi"
  },
  {
    "path": "ISSUE_TEMPLATE.md",
    "chars": 421,
    "preview": "#### Issue Description\n\n[FILL THIS OUT]\n\n#### Steps to Reproduce / Code Snippets\n\n[FILL THIS OUT]\n\n#### Expected Results"
  },
  {
    "path": "LICENSE",
    "chars": 1082,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2019 Farid from Safi\n\nPermission is hereby granted, free of charge, to any person o"
  },
  {
    "path": "README.md",
    "chars": 34208,
    "preview": "<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/react-native-gifted-chat\"><img alt=\"npm version\" src=\"https:"
  },
  {
    "path": "babel.config.cjs",
    "chars": 357,
    "preview": "module.exports = function (api) {\n  api.cache(true)\n\n  return {\n    presets: [\n      '@babel/preset-env',\n      'module:"
  },
  {
    "path": "codecov.yml",
    "chars": 50,
    "preview": "coverage:\n  status:\n    patch:\n      default: off\n"
  },
  {
    "path": "eslint.config.js",
    "chars": 11318,
    "preview": "import stylistic from '@stylistic/eslint-plugin'\nimport typescriptEslint from '@typescript-eslint/eslint-plugin'\nimport "
  },
  {
    "path": "example/.gitignore",
    "chars": 453,
    "preview": "# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files\n\n# dependencies\nnode_modules"
  },
  {
    "path": "example/README.md",
    "chars": 456,
    "preview": "# How to use\n\n1. Install dependencies:\n\n   ```bash\n   yarn install\n   cd example\n   yarn install\n   ```\n\n2. Install iOS "
  },
  {
    "path": "example/app/(tabs)/_layout.tsx",
    "chars": 966,
    "preview": "import React from 'react'\nimport { Tabs } from 'expo-router'\n\nimport { HapticTab } from '@/components/haptic-tab'\nimport"
  },
  {
    "path": "example/app/(tabs)/explore.tsx",
    "chars": 3079,
    "preview": "import React from 'react'\nimport { ScrollView, StyleSheet, View } from 'react-native'\nimport { useRouter } from 'expo-ro"
  },
  {
    "path": "example/app/(tabs)/index.tsx",
    "chars": 1722,
    "preview": "import { StyleSheet } from 'react-native'\nimport { Image } from 'expo-image'\n\nimport { ExternalLink } from '@/components"
  },
  {
    "path": "example/app/_layout.tsx",
    "chars": 1142,
    "preview": "import { LogBox, StyleSheet } from 'react-native'\nimport { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigat"
  },
  {
    "path": "example/app/chat/_layout.tsx",
    "chars": 1489,
    "preview": "import { TouchableOpacity, Text, StyleSheet } from 'react-native'\nimport { Ionicons } from '@expo/vector-icons'\nimport {"
  },
  {
    "path": "example/app/chat/basic.tsx",
    "chars": 96,
    "preview": "import BasicExample from '@/components/chat-examples/BasicExample'\n\nexport default BasicExample\n"
  },
  {
    "path": "example/app/chat/customized-rendering.tsx",
    "chars": 138,
    "preview": "import CustomizedRenderingExample from '@/components/chat-examples/CustomizedRenderingExample'\n\nexport default Customize"
  },
  {
    "path": "example/app/chat/links.tsx",
    "chars": 96,
    "preview": "import LinksExample from '@/components/chat-examples/LinksExample'\n\nexport default LinksExample\n"
  },
  {
    "path": "example/app/chat/reply.tsx",
    "chars": 96,
    "preview": "import ReplyExample from '@/components/chat-examples/ReplyExample'\n\nexport default ReplyExample\n"
  },
  {
    "path": "example/app/chat/slack.tsx",
    "chars": 96,
    "preview": "import SlackExample from '@/components/chat-examples/SlackExample'\n\nexport default SlackExample\n"
  },
  {
    "path": "example/app/modal.tsx",
    "chars": 698,
    "preview": "import { StyleSheet } from 'react-native'\nimport { Link } from 'expo-router'\n\nimport { ThemedText } from '@/components/t"
  },
  {
    "path": "example/app.json",
    "chars": 1495,
    "preview": "{\n  \"expo\": {\n    \"name\": \"example\",\n    \"slug\": \"example\",\n    \"version\": \"1.0.0\",\n    \"orientation\": \"portrait\",\n    \""
  },
  {
    "path": "example/babel.config.js",
    "chars": 636,
    "preview": "const fs = require('fs')\nconst path = require('path')\n\nconst root = path.resolve(__dirname, '..')\nconst rootPak = JSON.p"
  },
  {
    "path": "example/components/chat-examples/BasicExample.tsx",
    "chars": 3223,
    "preview": "import React, { useCallback, useMemo, useState } from 'react'\nimport { StyleSheet, View, useColorScheme } from 'react-na"
  },
  {
    "path": "example/components/chat-examples/CustomizedRenderingExample.tsx",
    "chars": 597,
    "preview": "import React from 'react'\nimport { StyleSheet, View, useColorScheme } from 'react-native'\nimport Chats from '../../examp"
  },
  {
    "path": "example/components/chat-examples/LinksExample.tsx",
    "chars": 6874,
    "preview": "import React, { useCallback, useMemo, useState } from 'react'\nimport { Linking, StyleSheet, Text, View } from 'react-nat"
  },
  {
    "path": "example/components/chat-examples/ReplyExample.tsx",
    "chars": 2312,
    "preview": "import React, { useCallback, useMemo, useState } from 'react'\nimport { StyleSheet, View, useColorScheme } from 'react-na"
  },
  {
    "path": "example/components/chat-examples/SlackExample.tsx",
    "chars": 1945,
    "preview": "import React, { useCallback, useMemo, useState } from 'react'\nimport { StyleSheet, View, useColorScheme } from 'react-na"
  },
  {
    "path": "example/components/external-link.tsx",
    "chars": 837,
    "preview": "import { type ComponentProps } from 'react'\nimport { Href, Link } from 'expo-router'\nimport { openBrowserAsync, WebBrows"
  },
  {
    "path": "example/components/haptic-tab.tsx",
    "chars": 546,
    "preview": "import { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs'\nimport { PlatformPressable } from '@react-naviga"
  },
  {
    "path": "example/components/hello-wave.tsx",
    "chars": 406,
    "preview": "import Animated from 'react-native-reanimated'\n\nexport function HelloWave () {\n  return (\n    <Animated.Text\n      style"
  },
  {
    "path": "example/components/parallax-scroll-view.tsx",
    "chars": 2049,
    "preview": "import type { PropsWithChildren, ReactElement } from 'react'\nimport { StyleSheet } from 'react-native'\nimport Animated, "
  },
  {
    "path": "example/components/themed-text.tsx",
    "chars": 1277,
    "preview": "import { StyleSheet, Text, type TextProps } from 'react-native'\n\nimport { useThemeColor } from '@/hooks/use-theme-color'"
  },
  {
    "path": "example/components/themed-view.tsx",
    "chars": 464,
    "preview": "import { View, type ViewProps } from 'react-native'\n\nimport { useThemeColor } from '@/hooks/use-theme-color'\n\nexport typ"
  },
  {
    "path": "example/components/ui/collapsible.tsx",
    "chars": 1357,
    "preview": "import { PropsWithChildren, useState } from 'react'\nimport { StyleSheet } from 'react-native'\nimport { RectButton } from"
  },
  {
    "path": "example/components/ui/icon-symbol.ios.tsx",
    "chars": 591,
    "preview": "import { StyleProp, ViewStyle } from 'react-native'\nimport { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symb"
  },
  {
    "path": "example/components/ui/icon-symbol.tsx",
    "chars": 1410,
    "preview": "// Fallback for using MaterialIcons on Android and web.\n\nimport { ComponentProps } from 'react'\nimport { OpaqueColorValu"
  },
  {
    "path": "example/constants/theme.ts",
    "chars": 1639,
    "preview": "/**\n * Below are the colors that are used in the app. The colors are defined in the light and dark mode.\n * There are ma"
  },
  {
    "path": "example/eslint.config.js",
    "chars": 4955,
    "preview": "import stylistic from '@stylistic/eslint-plugin'\nimport typescriptEslint from '@typescript-eslint/eslint-plugin'\nimport "
  },
  {
    "path": "example/example-expo/AccessoryBar.tsx",
    "chars": 3024,
    "preview": "import React from 'react'\nimport { StyleSheet, View, useColorScheme } from 'react-native'\nimport { MaterialIcons } from "
  },
  {
    "path": "example/example-expo/CustomActions.tsx",
    "chars": 3966,
    "preview": "import React, { useCallback } from 'react'\nimport {\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n  StyleSheet,\n  Text,\n  View,"
  },
  {
    "path": "example/example-expo/CustomView/index.tsx",
    "chars": 2446,
    "preview": "import React, { useCallback } from 'react'\nimport { Platform, View, Text } from 'react-native'\nimport Constants from 'ex"
  },
  {
    "path": "example/example-expo/CustomView/index.web.tsx",
    "chars": 748,
    "preview": "import React, { useCallback } from 'react'\nimport {\n  View,\n  Text,\n} from 'react-native'\nimport { RectButton } from 're"
  },
  {
    "path": "example/example-expo/CustomView/styles.ts",
    "chars": 286,
    "preview": "import { StyleSheet } from 'react-native'\n\nexport default StyleSheet.create({\n  mapView: {\n    width: 150,\n    height: 1"
  },
  {
    "path": "example/example-expo/CustomView/types.ts",
    "chars": 235,
    "preview": "import { StyleProp, ViewStyle } from 'react-native'\nimport { IMessage } from '../../../src'\n\nexport interface CustomView"
  },
  {
    "path": "example/example-expo/data/earlierMessages.ts",
    "chars": 3503,
    "preview": "import dayjs from 'dayjs'\nimport { IMessage } from 'react-native-gifted-chat'\n\nconst date = dayjs().subtract(1, 'year')\n"
  },
  {
    "path": "example/example-expo/data/messages.ts",
    "chars": 3618,
    "preview": "import dayjs from 'dayjs'\nimport { IMessage } from 'react-native-gifted-chat'\n\nconst date1 = dayjs()\nconst date2 = date1"
  },
  {
    "path": "example/example-expo/mediaUtils.ts",
    "chars": 1206,
    "preview": "import * as ImagePicker from 'expo-image-picker'\n\nimport { getCurrentPositionAsync, LocationObjectCoords, requestForegro"
  },
  {
    "path": "example/example-gifted-chat/README.md",
    "chars": 818,
    "preview": "# example-gifted-chat\n\nLots of people using `react-native-gifted-chat` might want to know that...\n\n1. There are so many "
  },
  {
    "path": "example/example-gifted-chat/src/Chats.tsx",
    "chars": 2150,
    "preview": "import React, { useState, useEffect } from 'react'\nimport { useColorScheme } from 'react-native'\nimport { GiftedChat, IM"
  },
  {
    "path": "example/example-gifted-chat/src/InputToolbar.tsx",
    "chars": 2596,
    "preview": "import React from 'react'\nimport { Image, useColorScheme } from 'react-native'\nimport {\n  InputToolbar,\n  Actions,\n  Com"
  },
  {
    "path": "example/example-gifted-chat/src/customComponents.tsx",
    "chars": 2477,
    "preview": "import React from 'react'\nimport { View, Text } from 'react-native'\nimport {\n  Avatar,\n  Bubble,\n  SystemMessage,\n  Mess"
  },
  {
    "path": "example/example-gifted-chat/src/messages.ts",
    "chars": 2752,
    "preview": "import { IMessage } from 'react-native-gifted-chat'\n\nconst messages: IMessage[] = [\n  {\n    text: `Hello this is an exam"
  },
  {
    "path": "example/example-slack-message/README.md",
    "chars": 357,
    "preview": "# \"Slack\" style UI example\n\nCredit and inspiration comes from [Slack](https://slack.com/).\n\nScreenshots to compare:\n\n| D"
  },
  {
    "path": "example/example-slack-message/src/SlackBubble.tsx",
    "chars": 7191,
    "preview": "import React, { useCallback, useMemo } from 'react'\nimport {\n  Text,\n  StyleSheet,\n  View,\n  Platform,\n  StyleProp,\n  Vi"
  },
  {
    "path": "example/example-slack-message/src/SlackMessage.tsx",
    "chars": 3038,
    "preview": "import React, { useCallback, useMemo } from 'react'\nimport {\n  View,\n  StyleSheet,\n  StyleProp,\n  ViewStyle,\n} from 'rea"
  },
  {
    "path": "example/hooks/use-color-scheme.ts",
    "chars": 46,
    "preview": "export { useColorScheme } from 'react-native'\n"
  },
  {
    "path": "example/hooks/use-color-scheme.web.ts",
    "chars": 467,
    "preview": "import { useEffect, useState } from 'react'\nimport { useColorScheme as useRNColorScheme } from 'react-native'\n\n/**\n * To"
  },
  {
    "path": "example/hooks/use-theme-color.ts",
    "chars": 522,
    "preview": "/**\n * Learn more about light and dark modes:\n * https://docs.expo.dev/guides/color-schemes/\n */\n\nimport { Colors } from"
  },
  {
    "path": "example/hooks/useKeyboardVerticalOffset.ts",
    "chars": 783,
    "preview": "import { useHeaderHeight } from '@react-navigation/elements'\n\n/**\n * Hook to get the correct keyboardVerticalOffset for "
  },
  {
    "path": "example/ios/.gitignore",
    "chars": 321,
    "preview": "# OSX\n#\n.DS_Store\n\n# Xcode\n#\nbuild/\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.p"
  },
  {
    "path": "example/ios/.xcode.env",
    "chars": 482,
    "preview": "# This `.xcode.env` file is versioned and is used to source the environment\n# used when running script phases inside Xco"
  },
  {
    "path": "example/ios/Podfile",
    "chars": 2793,
    "preview": "require File.join(File.dirname(`node --print \"require.resolve('expo/package.json')\"`), \"scripts/autolinking\")\nrequire Fi"
  },
  {
    "path": "example/ios/Podfile.properties.json",
    "chars": 105,
    "preview": "{\n  \"expo.jsEngine\": \"hermes\",\n  \"EX_DEV_CLIENT_NETWORK_INSPECTOR\": \"true\",\n  \"newArchEnabled\": \"true\"\n}\n"
  },
  {
    "path": "example/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 215,
    "preview": "{\n  \"images\": [\n    {\n      \"filename\": \"App-Icon-1024x1024@1x.png\",\n      \"idiom\": \"universal\",\n      \"platform\": \"ios\""
  },
  {
    "path": "example/ios/example/Images.xcassets/Contents.json",
    "chars": 62,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"expo\"\n  }\n}\n"
  },
  {
    "path": "example/ios/example/Info.plist",
    "chars": 3359,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "example/ios/example/PrivacyInfo.xcprivacy",
    "chars": 1283,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "example/ios/example/SplashScreen.storyboard",
    "chars": 3440,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "example/ios/example/Supporting/Expo.plist",
    "chars": 364,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "example/ios/example.xcodeproj/project.pbxproj",
    "chars": 24122,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "example/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme",
    "chars": 3270,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1130\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "example/ios/example.xcworkspace/contents.xcworkspacedata",
    "chars": 225,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:example.xcodep"
  },
  {
    "path": "example/metro.config.js",
    "chars": 1377,
    "preview": "const escape = require('escape-string-regexp')\nconst { getDefaultConfig } = require('expo/metro-config')\nconst fs = requ"
  },
  {
    "path": "example/package.json",
    "chars": 2448,
    "preview": "{\n  \"name\": \"example\",\n  \"main\": \"expo-router/entry\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"reset-project\": \"node ./"
  },
  {
    "path": "example/scripts/reset-project.js",
    "chars": 3528,
    "preview": "#!/usr/bin/env node\n\n/**\n * This script is used to reset the project to a blank state.\n * It deletes or moves the /app, "
  },
  {
    "path": "example/styles/index.ts",
    "chars": 200,
    "preview": "import { StyleSheet } from 'react-native'\n\nexport default StyleSheet.create({\n  center: {\n    justifyContent: 'center',\n"
  },
  {
    "path": "example/tsconfig.json",
    "chars": 242,
    "preview": "{\n  \"extends\": \"expo/tsconfig.base\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"paths\": {\n      \"@/*\": [\n        \"."
  },
  {
    "path": "example/utils/styleUtils.ts",
    "chars": 187,
    "preview": "export function getColorSchemeStyle<T>(styles: T, baseName: string, colorScheme: string | null | undefined) {\n  const ke"
  },
  {
    "path": "expoSnack/ExpoSnack.tsx",
    "chars": 17199,
    "preview": "import React, { useCallback, useMemo, useState } from 'react'\nimport {\n  StyleSheet,\n  Text,\n  View,\n  useColorScheme,\n}"
  },
  {
    "path": "expoSnack/README.md",
    "chars": 6546,
    "preview": "# React Native Gifted Chat - Expo Snack\n\nThis directory contains a self-contained Expo Snack example that demonstrates t"
  },
  {
    "path": "expoSnack/package.json",
    "chars": 1145,
    "preview": "{\n  \"dependencies\": {\n    \"dayjs\": \"*\",\n    \"expo-font\": \"~14.0.9\",\n    \"expo-image\": \"~3.0.10\",\n    \"expo-router\": \"~6."
  },
  {
    "path": "jest.config.cjs",
    "chars": 540,
    "preview": "module.exports = {\n  preset: 'react-native',\n  resetMocks: true,\n  setupFilesAfterEnv: [\n    './node_modules/react-nativ"
  },
  {
    "path": "package.json",
    "chars": 3858,
    "preview": "{\n  \"name\": \"react-native-gifted-chat\",\n  \"version\": \"3.3.2\",\n  \"description\": \"The most complete chat UI for React Nati"
  },
  {
    "path": "src/Actions.tsx",
    "chars": 2494,
    "preview": "import React, { ReactNode, useCallback } from 'react'\nimport {\n  StyleSheet,\n  View,\n  StyleProp,\n  ViewStyle,\n  TextSty"
  },
  {
    "path": "src/Avatar.tsx",
    "chars": 3601,
    "preview": "import React, { ReactNode, useCallback } from 'react'\nimport {\n  ImageStyle,\n  StyleSheet,\n  TextStyle,\n  View,\n  ViewSt"
  },
  {
    "path": "src/Bubble/index.tsx",
    "chars": 10651,
    "preview": "import React, { useCallback, useMemo } from 'react'\nimport {\n  View,\n  Pressable,\n} from 'react-native'\n\nimport { Text }"
  },
  {
    "path": "src/Bubble/styles.ts",
    "chars": 1228,
    "preview": "import { StyleSheet } from 'react-native'\nimport { Color } from '../Color'\n\nconst styles = StyleSheet.create({\n  wrapper"
  },
  {
    "path": "src/Bubble/types.ts",
    "chars": 3542,
    "preview": "import React, { ComponentProps } from 'react'\nimport {\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n  Pressable,\n} from 'react"
  },
  {
    "path": "src/Color.ts",
    "chars": 407,
    "preview": "export const Color = {\n  defaultColor: '#b2b2b2',\n  backgroundTransparent: 'transparent',\n  defaultBlue: '#0084ff',\n  le"
  },
  {
    "path": "src/Composer.tsx",
    "chars": 2913,
    "preview": "import React, { useCallback, useMemo, useState } from 'react'\nimport {\n  Platform,\n  StyleSheet,\n  TextInputChangeEvent,"
  },
  {
    "path": "src/Constant.ts",
    "chars": 203,
    "preview": "export const DATE_FORMAT = 'D MMMM'\nexport const TIME_FORMAT = 'LT'\n\nexport const TEST_ID = {\n  WRAPPER: 'GC_WRAPPER',\n "
  },
  {
    "path": "src/Day/index.tsx",
    "chars": 1536,
    "preview": "import React, { useMemo } from 'react'\nimport {\n  View,\n} from 'react-native'\nimport dayjs from 'dayjs'\nimport calendar "
  },
  {
    "path": "src/Day/styles.ts",
    "chars": 422,
    "preview": "import { StyleSheet } from 'react-native'\nimport { Color } from '../Color'\n\nexport default StyleSheet.create({\n  contain"
  },
  {
    "path": "src/Day/types.ts",
    "chars": 383,
    "preview": "import {\n  StyleProp,\n  ViewStyle,\n  TextProps,\n} from 'react-native'\n\nexport interface DayProps {\n  createdAt: Date | n"
  },
  {
    "path": "src/GiftedAvatar.tsx",
    "chars": 4063,
    "preview": "import React, { useCallback, useMemo } from 'react'\nimport {\n  Image,\n  View,\n  StyleSheet,\n  StyleProp,\n  ImageStyle,\n "
  },
  {
    "path": "src/GiftedChat/index.tsx",
    "chars": 10621,
    "preview": "import React, {\n  createRef,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  useCallback,\n  RefObject,\n} from 'react'\nim"
  },
  {
    "path": "src/GiftedChat/styles.ts",
    "chars": 193,
    "preview": "import { StyleSheet } from 'react-native'\n\nexport default StyleSheet.create({\n  fill: {\n    flex: 1,\n  },\n  contentConta"
  },
  {
    "path": "src/GiftedChat/types.ts",
    "chars": 7694,
    "preview": "import React, { RefObject } from 'react'\nimport {\n  TextInput,\n  StyleProp,\n  TextStyle,\n  ViewStyle,\n} from 'react-nati"
  },
  {
    "path": "src/GiftedChatContext.ts",
    "chars": 682,
    "preview": "import { createContext, useContext } from 'react'\nimport {\n  ActionSheetOptions,\n} from '@expo/react-native-action-sheet"
  },
  {
    "path": "src/InputToolbar.tsx",
    "chars": 4864,
    "preview": "import React, { useCallback, useMemo } from 'react'\nimport { StyleSheet, View, StyleProp, ViewStyle, TextStyle } from 'r"
  },
  {
    "path": "src/LoadEarlierMessages.tsx",
    "chars": 2309,
    "preview": "import React from 'react'\nimport {\n  ActivityIndicator,\n  StyleSheet,\n  View,\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n} f"
  },
  {
    "path": "src/Message/index.tsx",
    "chars": 7536,
    "preview": "import React, { useCallback, useMemo, useRef } from 'react'\nimport { View, StyleSheet } from 'react-native'\nimport Reani"
  },
  {
    "path": "src/Message/styles.ts",
    "chars": 359,
    "preview": "import { StyleSheet } from 'react-native'\n\nexport default StyleSheet.create({\n  container: {\n    flexDirection: 'row',\n "
  },
  {
    "path": "src/Message/types.ts",
    "chars": 1022,
    "preview": "import { ViewStyle, LayoutChangeEvent } from 'react-native'\n\nimport { AvatarProps } from '../Avatar'\nimport { BubbleProp"
  },
  {
    "path": "src/MessageAudio.tsx",
    "chars": 696,
    "preview": "import React, { useMemo } from 'react'\nimport { View, StyleSheet } from 'react-native'\nimport { Text } from 'react-nativ"
  },
  {
    "path": "src/MessageImage.tsx",
    "chars": 7174,
    "preview": "import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n  Image,\n  StyleSheet,\n  View,"
  },
  {
    "path": "src/MessageReply.tsx",
    "chars": 3780,
    "preview": "import React, { useMemo, useCallback } from 'react'\nimport {\n  StyleSheet,\n  ViewStyle,\n  View,\n  Pressable,\n  Image,\n  "
  },
  {
    "path": "src/MessageText.tsx",
    "chars": 2555,
    "preview": "import React, { useMemo, useCallback } from 'react'\nimport {\n  StyleSheet,\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n  View"
  },
  {
    "path": "src/MessageVideo.tsx",
    "chars": 696,
    "preview": "import React, { useMemo } from 'react'\nimport { View, StyleSheet } from 'react-native'\nimport { Text } from 'react-nativ"
  },
  {
    "path": "src/MessagesContainer/components/DayAnimated/index.tsx",
    "chars": 5024,
    "preview": "import React, { useCallback, useEffect, useMemo, useState } from 'react'\nimport { LayoutChangeEvent } from 'react-native"
  },
  {
    "path": "src/MessagesContainer/components/DayAnimated/styles.ts",
    "chars": 226,
    "preview": "import { StyleSheet } from 'react-native'\n\nexport default StyleSheet.create({\n  dayAnimated: {\n    position: 'absolute',"
  },
  {
    "path": "src/MessagesContainer/components/DayAnimated/types.ts",
    "chars": 402,
    "preview": "import { DayProps } from '../../../Day'\nimport { IMessage } from '../../../Models'\nimport { DaysPositions } from '../../"
  },
  {
    "path": "src/MessagesContainer/components/Item/index.tsx",
    "chars": 6581,
    "preview": "import React, { useCallback, useMemo } from 'react'\nimport { LayoutChangeEvent, View } from 'react-native'\nimport Animat"
  },
  {
    "path": "src/MessagesContainer/components/Item/types.ts",
    "chars": 460,
    "preview": "import { IMessage } from '../../../Models'\nimport { MessagesContainerProps, DaysPositions } from '../../types'\n\nexport i"
  },
  {
    "path": "src/MessagesContainer/index.tsx",
    "chars": 13632,
    "preview": "import React, { useCallback, useEffect, useMemo, useState } from 'react'\nimport {\n  View,\n  LayoutChangeEvent,\n  ListRen"
  },
  {
    "path": "src/MessagesContainer/styles.ts",
    "chars": 859,
    "preview": "import { Platform, StyleSheet } from 'react-native'\nimport { Color } from '../Color'\n\nexport default StyleSheet.create({"
  },
  {
    "path": "src/MessagesContainer/types.ts",
    "chars": 4436,
    "preview": "import { RefObject } from 'react'\nimport {\n  FlatListProps,\n  StyleProp,\n  ViewStyle,\n} from 'react-native'\nimport { Fla"
  },
  {
    "path": "src/Models.ts",
    "chars": 1430,
    "preview": "import { StyleProp, ViewStyle } from 'react-native'\n\nexport type Omit<T, K> = Pick<T, Exclude<keyof T, K>>\n\nexport inter"
  },
  {
    "path": "src/QuickReplies.tsx",
    "chars": 4750,
    "preview": "import React, { useState, useMemo, useCallback } from 'react'\nimport {\n  StyleSheet,\n  View,\n  StyleProp,\n  ViewStyle,\n "
  },
  {
    "path": "src/Reply/index.ts",
    "chars": 24,
    "preview": "export * from './types'\n"
  },
  {
    "path": "src/Reply/types.ts",
    "chars": 2914,
    "preview": "import { ImageStyle, StyleProp, TextStyle, ViewStyle } from 'react-native'\nimport { SharedValue } from 'react-native-rea"
  },
  {
    "path": "src/ReplyPreview.tsx",
    "chars": 3145,
    "preview": "import React, { useMemo } from 'react'\nimport { StyleSheet, View, StyleProp, ViewStyle, TextStyle, Pressable } from 'rea"
  },
  {
    "path": "src/Send.tsx",
    "chars": 3162,
    "preview": "import React, { useMemo, useCallback, useEffect } from 'react'\nimport {\n  StyleSheet,\n  StyleProp,\n  ViewStyle,\n  TextSt"
  },
  {
    "path": "src/SystemMessage.tsx",
    "chars": 1927,
    "preview": "import React from 'react'\nimport {\n  StyleSheet,\n  View,\n  ViewStyle,\n  StyleProp,\n  TextStyle,\n} from 'react-native'\nim"
  },
  {
    "path": "src/Time.tsx",
    "chars": 1578,
    "preview": "import React, { useMemo } from 'react'\nimport { StyleSheet, View, ViewStyle, TextStyle } from 'react-native'\nimport dayj"
  },
  {
    "path": "src/TypingIndicator/index.tsx",
    "chars": 3586,
    "preview": "import React, { useCallback, useEffect, useState, useMemo } from 'react'\nimport { View } from 'react-native'\nimport Anim"
  },
  {
    "path": "src/TypingIndicator/styles.ts",
    "chars": 427,
    "preview": "import { StyleSheet } from 'react-native'\nimport { Color } from '../Color'\n\nexport default StyleSheet.create({\n  contain"
  },
  {
    "path": "src/TypingIndicator/types.ts",
    "chars": 147,
    "preview": "import { StyleProp, ViewStyle } from 'react-native'\n\nexport interface TypingIndicatorProps {\n  isTyping?: boolean\n  styl"
  },
  {
    "path": "src/__tests__/Actions.test.tsx",
    "chars": 259,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Actions } from '..'\n\nit('shou"
  },
  {
    "path": "src/__tests__/Avatar.test.tsx",
    "chars": 423,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Avatar } from '..'\nimport { D"
  },
  {
    "path": "src/__tests__/Bubble.test.tsx",
    "chars": 405,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Bubble } from '..'\nimport { D"
  },
  {
    "path": "src/__tests__/Color.test.tsx",
    "chars": 120,
    "preview": "import { Color } from '../Color'\n\nit('should compare Color with snapshot', () => {\n  expect(Color).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/Composer.test.tsx",
    "chars": 263,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Composer } from '..'\n\nit('sho"
  },
  {
    "path": "src/__tests__/Constant.test.tsx",
    "chars": 133,
    "preview": "import * as Constant from '../Constant'\n\nit('should compare Constant with snapshot', () => {\n  expect(Constant).toMatchS"
  },
  {
    "path": "src/__tests__/Day.test.tsx",
    "chars": 579,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Day } from '..'\nimport { DEFA"
  },
  {
    "path": "src/__tests__/DayAnimated.test.tsx",
    "chars": 1352,
    "preview": "import React from 'react'\nimport { View, Text } from 'react-native'\nimport { render } from '@testing-library/react-nativ"
  },
  {
    "path": "src/__tests__/GiftedAvatar.test.tsx",
    "chars": 275,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { GiftedAvatar } from '..'\n\nit("
  },
  {
    "path": "src/__tests__/GiftedChat.test.tsx",
    "chars": 1131,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { GiftedChat } from '..'\n\nconst"
  },
  {
    "path": "src/__tests__/InputToolbar.test.tsx",
    "chars": 275,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { InputToolbar } from '..'\n\nit("
  },
  {
    "path": "src/__tests__/LoadEarlier.test.tsx",
    "chars": 345,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { LoadEarlierMessages } from '."
  },
  {
    "path": "src/__tests__/Message.test.tsx",
    "chars": 1514,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Message } from '..'\nimport { "
  },
  {
    "path": "src/__tests__/MessageImage.test.tsx",
    "chars": 686,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { MessageImage } from '..'\nimpo"
  },
  {
    "path": "src/__tests__/MessageReply.test.tsx",
    "chars": 1170,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { MessageReply } from '../compo"
  },
  {
    "path": "src/__tests__/MessageText.test.tsx",
    "chars": 374,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { MessageText } from '..'\nimpor"
  },
  {
    "path": "src/__tests__/MessagesContainer.test.tsx",
    "chars": 928,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { MessagesContainer } from '..'"
  },
  {
    "path": "src/__tests__/ReplyPreview.test.tsx",
    "chars": 922,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { ReplyPreview } from '../compo"
  },
  {
    "path": "src/__tests__/Send.test.tsx",
    "chars": 662,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Send } from '..'\n\ndescribe('S"
  },
  {
    "path": "src/__tests__/SystemMessage.test.tsx",
    "chars": 654,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { SystemMessage } from '..'\nimp"
  },
  {
    "path": "src/__tests__/Time.test.tsx",
    "chars": 632,
    "preview": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Time } from '..'\nimport { DEF"
  },
  {
    "path": "src/__tests__/__snapshots__/Actions.test.tsx.snap",
    "chars": 847,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <Actions /> and compare with snapshot 1`] = `\n<View\n "
  },
  {
    "path": "src/__tests__/__snapshots__/Avatar.test.tsx.snap",
    "chars": 249,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <Avatar /> and compare with snapshot 1`] = `\n<View\n  "
  },
  {
    "path": "src/__tests__/__snapshots__/Bubble.test.tsx.snap",
    "chars": 2589,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <Bubble /> and compare with snapshot 1`] = `\n<View>\n "
  },
  {
    "path": "src/__tests__/__snapshots__/Color.test.tsx.snap",
    "chars": 515,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should compare Color with snapshot 1`] = `\n{\n  \"alizarin\": \"#e74c3c"
  },
  {
    "path": "src/__tests__/__snapshots__/Composer.test.tsx.snap",
    "chars": 1089,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <Composer /> and compare with snapshot 1`] = `\n<View\n"
  },
  {
    "path": "src/__tests__/__snapshots__/Constant.test.tsx.snap",
    "chars": 295,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should compare Constant with snapshot 1`] = `\n{\n  \"DATE_FORMAT\": \"D"
  },
  {
    "path": "src/__tests__/__snapshots__/Day.test.tsx.snap",
    "chars": 1659,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Day should not render <Day /> and compare with snapshot 1`] = `\n<Vi"
  },
  {
    "path": "src/__tests__/__snapshots__/DayAnimated.test.tsx.snap",
    "chars": 210,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`DayAnimated should render DayAnimated with default Day component 1`"
  },
  {
    "path": "src/__tests__/__snapshots__/GiftedAvatar.test.tsx.snap",
    "chars": 433,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <GiftedAvatar /> and compare with snapshot 1`] = `\n<V"
  },
  {
    "path": "src/__tests__/__snapshots__/GiftedChat.test.tsx.snap",
    "chars": 3190,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <GiftedChat/> and compare with snapshot 1`] = `\n<View"
  },
  {
    "path": "src/__tests__/__snapshots__/InputToolbar.test.tsx.snap",
    "chars": 2748,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <InputToolbar /> and compare with snapshot 1`] = `\n<V"
  },
  {
    "path": "src/__tests__/__snapshots__/LoadEarlier.test.tsx.snap",
    "chars": 758,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <LoadEarlierMessages /> and compare with snapshot 1`]"
  },
  {
    "path": "src/__tests__/__snapshots__/Message.test.tsx.snap",
    "chars": 11844,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Message component should NOT render <Message /> 1`] = `null`;\n\nexpo"
  },
  {
    "path": "src/__tests__/__snapshots__/MessageImage.test.tsx.snap",
    "chars": 5047,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`MessageImage should  render <MessageImage /> and compare with snaps"
  },
  {
    "path": "src/__tests__/__snapshots__/MessageReply.test.tsx.snap",
    "chars": 3536,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <MessageReply /> and compare with snapshot 1`] = `\n<V"
  },
  {
    "path": "src/__tests__/__snapshots__/MessageText.test.tsx.snap",
    "chars": 409,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <MessageText /> and compare with snapshot 1`] = `\n<Vi"
  },
  {
    "path": "src/__tests__/__snapshots__/ReplyPreview.test.tsx.snap",
    "chars": 6931,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <ReplyPreview /> and compare with snapshot 1`] = `\n<V"
  },
  {
    "path": "src/__tests__/__snapshots__/Send.test.tsx.snap",
    "chars": 3075,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Send should always render <Send /> and compare with snapshot 1`] = "
  },
  {
    "path": "src/__tests__/__snapshots__/SystemMessage.test.tsx.snap",
    "chars": 1507,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`SystemMessage should not render <SystemMessage /> and compare with "
  },
  {
    "path": "src/__tests__/__snapshots__/Time.test.tsx.snap",
    "chars": 409,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Time should not render <Time /> and compare with snapshot 1`] = `nu"
  },
  {
    "path": "src/__tests__/data.ts",
    "chars": 190,
    "preview": "import { IMessage } from '../Models'\n\nexport const DEFAULT_TEST_MESSAGE: IMessage = {\n  _id: 'test',\n  text: 'test',\n  u"
  },
  {
    "path": "src/__tests__/utils.test.ts",
    "chars": 568,
    "preview": "import { isSameDay, isSameUser } from '../utils'\n\nit('should test if same day', () => {\n  const now = new Date()\n  expec"
  },
  {
    "path": "src/components/MessageReply.tsx",
    "chars": 3827,
    "preview": "import React, { useMemo } from 'react'\nimport {\n  Image,\n  ImageStyle,\n  Pressable,\n  StyleProp,\n  StyleSheet,\n  Text,\n "
  },
  {
    "path": "src/components/ReplyPreview.tsx",
    "chars": 5116,
    "preview": "import React, { useEffect } from 'react'\nimport {\n  Image,\n  ImageStyle,\n  Pressable,\n  StyleProp,\n  StyleSheet,\n  Text,"
  },
  {
    "path": "src/components/TouchableOpacity.tsx",
    "chars": 1724,
    "preview": "import React, { useCallback } from 'react'\nimport { BaseButton } from 'react-native-gesture-handler'\nimport Animated, {\n"
  },
  {
    "path": "src/hooks/useColorScheme.ts",
    "chars": 661,
    "preview": "import { useColorScheme as useRNColorScheme } from 'react-native'\nimport { useChatContext } from '../GiftedChatContext'\n"
  },
  {
    "path": "src/hooks/useUpdateLayoutEffect.ts",
    "chars": 623,
    "preview": "import { DependencyList, useLayoutEffect, useRef } from 'react'\n\n/**\n * A custom useEffect hook that only triggers on up"
  },
  {
    "path": "src/index.ts",
    "chars": 1094,
    "preview": "import * as utils from './utils'\n\nexport * from './GiftedChat'\nexport * from './Constant'\nexport { utils }\nexport * from"
  },
  {
    "path": "src/linkParser.tsx",
    "chars": 7049,
    "preview": "import React from 'react'\nimport { Text, TextStyle, StyleProp, Linking } from 'react-native'\n\nexport type LinkType = 'ur"
  },
  {
    "path": "src/logging.ts",
    "chars": 330,
    "preview": "const styleString = (color: string) => `color: ${color}; font-weight: bold`\nconst headerLog = '%c[react-native-gifted-ch"
  },
  {
    "path": "src/styles.ts",
    "chars": 787,
    "preview": "import { StyleProp, StyleSheet, TextStyle, ViewStyle } from 'react-native'\n\nexport default StyleSheet.create({\n  fill: {"
  },
  {
    "path": "src/types.ts",
    "chars": 1039,
    "preview": "export * from './Models'\n\nexport type { ActionsProps } from './Actions'\nexport type { AvatarProps } from './Avatar'\nexpo"
  },
  {
    "path": "src/utils.ts",
    "chars": 4594,
    "preview": "import React, { useCallback, useEffect, useRef } from 'react'\nimport dayjs from 'dayjs'\nimport { IMessage } from './Mode"
  },
  {
    "path": "tests/setup.ts",
    "chars": 670,
    "preview": "jest.mock('react-native-worklets', () =>\n  require('react-native-worklets/lib/module/mock')\n)\n\njest.mock('react-native-r"
  },
  {
    "path": "tsconfig.json",
    "chars": 1061,
    "preview": "{\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"outDir\": \"./lib\",\n    \"strict\": true,\n    \"jsx\": \"react-native\",\n"
  }
]

About this extraction

This page contains the full source code of the FaridSafi/react-native-gifted-chat GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 193 files (452.1 KB), approximately 121.3k tokens, and a symbol index with 149 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!