[
  {
    "path": ".expo-shared/assets.json",
    "content": "{\n  \"5c6d215cbde93d15ae63d2ea43dfe8bf8a79a53146382cf7f3f0089bec2fc5d6\": true,\n  \"d0e86e9f72936ac85597d9cd6415cf22a3c208505d5586ac04de09d4dd305707\": true,\n  \"b884dbf3daca9d0a4de2f6552aa4dbdda9cbff26e2677bd76a514d71af2e7e2c\": true,\n  \"30bf2d1edfc90d2841794660cf30a94bb134b89d4808bd4b05cac2304cf6fad7\": true,\n  \"01d8b00b4e3d1dfab70e1ef3354b373f13bdcc3ad29122b3afacf34afd043960\": true,\n  \"36cb6cfb9a281169f9ba1eb7c345fba1d56a0c7115fbf8c4b3753427aad8edaa\": true,\n  \"3232d6cbd4824ece99982787e431ff1425df1d22288961602506b046c50bc516\": true,\n  \"5c8d230c038116f9327c1a38157e7b5d25e1d6bbfbb0ba4e86310f097c3d0f9f\": true,\n  \"250d0d32ab3051aee4b8f9d26a3299e6b3d8e6ee137dffe8a7e183e5478a2040\": true\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [faridsafi, kesha-antonov, xcarpentier, johan-dutoit]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with a custom sponsorship URL\n"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "# React Native Gifted Chat\n\nThe 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.\n\nAlways reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.\n\n## Working Effectively\n\n### Bootstrap and build the repository:\n- `yarn install` -- NEVER CANCEL: takes 58 seconds. Set timeout to 120+ seconds.\n- `yarn build` -- builds TypeScript library, takes 3 seconds. Set timeout to 30+ seconds.\n- `yarn lint` -- lints source code, takes 3 seconds. Currently has warnings but no errors. Set timeout to 30+ seconds.\n- `yarn test` -- NEVER CANCEL: runs Jest test suite, takes 9 seconds. Set timeout to 60+ seconds.\n\n### Full validation before publishing:\n- `yarn prepublishOnly` -- NEVER CANCEL: runs lint + build + test, takes 11 seconds total. Set timeout to 60+ seconds.\n\n### Known Issues:\n- If tests fail due to snapshot mismatches after fresh dependency install, run `yarn test -u` to update snapshots\n- Snapshot tests may need updates when React Native or dependency versions change\n\n### Example app development:\n- `cd example && yarn install` -- NEVER CANCEL: takes 38 seconds. Set timeout to 90+ seconds.\n- Install Expo CLI globally: `npm install -g @expo/cli` or use `npx expo` commands\n- Native development: `cd example && npx expo start`\n- Web development: `cd example && npx expo start --web` (requires additional dependencies)\n- The example app starts Metro bundler on http://localhost:8081\n- Expect dependency version warnings in offline/CI mode - these are normal\n\n### Type checking and development:\n- `yarn tsc:watch` -- runs TypeScript compiler in watch mode for development\n- `yarn tsc:write` -- compiles TypeScript and writes output to /lib directory\n\n## Requirements and Setup\n\n### System Requirements:\n- Node.js >= 18 (tested with 20.19.4)\n- Yarn package manager 1.22.22+ (do NOT use npm for this project)\n- TypeScript compiler (included in devDependencies)\n\n### Dependencies are already installed if yarn.lock exists\nThe repository includes both package-lock.json and yarn.lock but ALWAYS use yarn commands, never npm commands.\n\n## Validation\n\n### Always run full validation before completing changes:\n1. `yarn lint` -- check for code style issues (warnings are acceptable, errors are not)\n2. `yarn build` -- verify TypeScript compilation succeeds\n3. `yarn test` -- NEVER CANCEL: ensure all 19 test suites and 29 tests pass. Takes 31 seconds.\n\n### Manual validation scenarios:\nAfter making code changes, you should test basic functionality by:\n1. Building the library: `yarn build` \n2. Running the test suite: `yarn test`\n3. 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\n4. ALWAYS test that the TypeScript declarations in /lib are correctly generated\n\n### Testing approach:\n- All tests are located in `src/__tests__/` directory\n- Tests use Jest with React Test Renderer\n- Test coverage can be viewed with `yarn test:coverage`\n- Snapshot tests are used extensively (27 snapshots)\n\n## Project Structure\n\n### Key directories:\n- `/src` -- main library source code (TypeScript)\n- `/lib` -- compiled JavaScript output (generated by `yarn build`, do not edit manually)  \n- `/example` -- example React Native app demonstrating library usage\n- `/src/__tests__` -- Jest test files\n- `/.github/workflows/main.yml` -- CI/CD pipeline (tests Node 18 and 20)\n\n### Important files:\n- `package.json` -- main project configuration and scripts\n- `tsconfig.json` -- TypeScript compiler configuration\n- `jest.config.cjs` -- Jest test configuration\n- `.eslintrc.cjs` -- ESLint linting rules\n- `babel.config.cjs` -- Babel transformation configuration\n- `example/package.json` -- example app dependencies\n\n### Build output:\nThe `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.\n\n## Common Tasks\n\n### Making code changes:\n1. Edit source files in `/src` directory\n2. Run `yarn lint` to check style\n3. Run `yarn build` to compile TypeScript\n4. Run `yarn test` to verify tests pass\n5. Test manually with example app if UI changes\n\n### Adding or modifying tests:\n- Tests are in `src/__tests__/` directory\n- Follow existing test patterns using React Test Renderer\n- Update snapshots if needed with `yarn test -u`\n- Ensure all tests pass with `yarn test`\n\n### Working with the example app:\n- Example app uses Expo and demonstrates library functionality\n- Install dependencies: `cd example && yarn install`\n- Start development server: `npx expo start`\n- For web: `npx expo start --web` (requires react-native-web and @expo/metro-runtime)\n\n## CI/CD Integration\n\nThe repository uses GitHub Actions (`.github/workflows/main.yml`) that:\n- Tests on Node.js versions 18 and 20  \n- Runs `yarn install` and `yarn build`\n- Build typically takes 1-2 minutes on CI\n\nAlways ensure your changes pass the full validation pipeline locally before pushing.\n\n## Common Command Reference\n\n### Root directory (library development):\n```bash\nyarn install        # Install dependencies (58 seconds)\nyarn build         # Build TypeScript library (3 seconds)  \nyarn lint          # Lint source code (3 seconds)\nyarn test          # Run test suite (9 seconds)\nyarn prepublishOnly # Full validation pipeline (11 seconds)\nyarn tsc:watch     # TypeScript watch mode\n```\n\n### Example directory (app development):\n```bash\ncd example\nyarn install       # Install example dependencies (38 seconds)\nnpx expo start     # Start native development server\nnpx expo start --web # Start web development server\n```\n\n### Time expectations (NEVER CANCEL these operations):\n- yarn install: 58 seconds (root), 41 seconds (example)\n- yarn build: 3 seconds\n- yarn lint: 3 seconds  \n- yarn test: 9 seconds (after fresh install and snapshot updates)\n- yarn prepublishOnly: 11 seconds total\n\nAlways set timeouts to at least double these times to account for system variations."
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 15\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - pinned\n  - security\n# Label to use when marking an issue as stale\nstaleLabel: wontfix\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  Sorry, but this issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. BTW Thank you\n  for your contributions 😀 !!!\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Main CI\n\non:\n  pull_request:\n    branches:\n      - master\n\njobs:\n  checks:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [20, 22]\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: Node modules\n        run: |\n          yarn install\n\n      - name: Lint\n        run: |\n          yarn lint\n\n      - name: Type Check\n        run: |\n          yarn tsc --noEmit\n\n      - name: Build\n        run: |\n          yarn build\n\n      - name: Test\n        run: |\n          yarn test\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Close stale issues\n\non:\n  schedule:\n    - cron: '0 0 * * *' # runs daily\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v9\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          days-before-issue-stale: 365\n          days-before-issue-close: 0\n          stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed now.'\n          close-issue-message: 'Closing due to inactivity for over a year.'\n          only-issue-labels: ''\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules/\n.expo/\nnpm-debug.log\nlib/\nTODO.md\n.idea\n.vscode\nExponent-*.app\n*.log\ncoverage/\nweb-build/\n.eslintcache\n\n# Yarn\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/sdks\n!.yarn/versions\nyarn-error.log\n\nexample_bare/vendor\nexample_bare/**/build\nexample_bare/ios/Pods\nexample_bare/android/.gradle\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "yarn lint-staged\n"
  },
  {
    "path": ".npmignore",
    "content": ".expo/\n.expo-shared/\n.circleci/\n.github/\n.vscode/\nexample/\nexample-expo/\nexample-slack-message/\nexample-gifted-chat/\nscreenshots/\nbabel.config.js\ntests/\nREADME.md\nISSUE_TEMPLATE.md\ncodecov.yml\nmedia/\nApp.tsx\napp.json\nmetro.config.js\nsrc/\ntsconfig.json\ntslint.json\nyarn.lock\nflow-typedefs/\n.flowconfig\nyarn-error.log\nweb-build/\ntypes.d.ts\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [3.3.2] - 2026-01-22\n\n### 🐛 Bug Fixes\n- Fixed `React.memo` and `React.forwardRef` components not rendering correctly when passed as render props\n  - `renderComponentOrElement` now properly handles components with `$$typeof` property\n- Fixed layout jump on initial render - content now renders with `opacity: 0` until initialized\n- Fixed keyboard vertical offset documentation and examples\n\n### 🔧 Improvements\n- Updated `keyboardVerticalOffset` documentation in README with clearer explanation\n- Added `hidden` style for smoother initial render transitions\n\n### 📝 Documentation\n- Improved `keyboardVerticalOffset` section explaining that it equals distance from screen top to container top\n- Added recommendation to use `useHeaderHeight()` from `@react-navigation/elements`\n\n## [3.3.0] - 2026-01-21\n\n### ✨ Features\n- **Swipe to Reply**: New swipe-to-reply functionality using `ReanimatedSwipeable` (based on #2692)\n  - Replaced deprecated `Swipeable` with `ReanimatedSwipeable` from react-native-gesture-handler\n  - Added `reply` prop to `GiftedChat` with grouped configuration options\n  - Swipe direction support: `'left'` (swipe left, icon on right) or `'right'` (swipe right, icon on left)\n  - Custom swipe action rendering via `renderAction`\n  - Built-in animated `ReplyIcon` component\n  - `ReplyPreview` component with smooth enter/exit animations\n  - Reply message display in `Bubble` component via `messageReply` prop\n- **New Props**:\n  - `scrollToBottomContentStyle` - style for scroll to bottom button content\n\n### 🐛 Bug Fixes\n- Fixed #2702 - typing issues\n- Fixed #2708 - component issues\n- Fixed #2607 - edge case handling\n- Fixed #2701 - rendering issues\n- Fixed #2691 - prop handling\n- Fixed #2688 - style issues\n- Fixed #2687 - component behavior\n- Fixed #2618 - scroll issues\n- Fixed #2677, #2682, #2602 - multiple fixes\n- Fixed #2684, #2686 - component issues\n- Fixed `onScroll` type definition\n- Fixed messages padding\n- Fixed SystemMessage styles\n- Added missing worklets for animations\n- Removed `ts-expect-error` for `requestAnimationFrame` (now properly typed for React Native)\n- Fixed two typing issues (#2698)\n\n### 🔧 Improvements\n- Grouped reply-related props into `ReplyProps` interface for cleaner API\n- Added `SwipeToReplyProps` for Message-level swipe configuration\n- Added `BubbleReplyProps` for Bubble-level reply message styling\n- Added example app to lint command with proper path alias support\n- Improved reply animations (enter/exit transitions)\n- Changes from #2705\n\n### 📝 Documentation\n- Updated README with swipe-to-reply feature documentation and examples\n- Updated license link\n- Added reply message implementation example (#2690)\n\n### 🧪 Testing\n- Updated test snapshots\n- Added tests for `MessageReply` component\n- Added tests for `ReplyPreview` component\n\n## [3.2.3] - 2025-12-XX\n\n### 🐛 Bug Fixes\n- Fixed `onScroll` type definition\n\n## [3.2.0] - 2025-11-25\n\n### ✨ Features\n- **Custom Link Parser**: Replaced `react-native-autolink` dependency with custom link parser implementation for better control and performance\n  - Removed external dependency on `react-native-autolink`\n  - Improved link parsing with custom implementation in `linkParser.tsx`\n  - Updated `MessageText` component to use new parser\n  - Enhanced links example in example app\n\n### 🐛 Bug Fixes\n- Adjusted message bubble styles for better rendering\n- Updated test snapshots to reflect parser changes\n\n## [3.1.5] - 2025-11-25\n\n### ✨ Features\n- **Color Scheme Support**: Added `colorScheme` prop to `GiftedChat` component\n  - New `useColorScheme` hook for consistent color scheme handling\n  - Automatically adapts UI elements (Composer, InputToolbar, Send) based on color scheme\n  - Added comprehensive tests for color scheme functionality\n\n### 📝 Documentation\n- Updated README with `colorScheme` prop documentation\n\n## [3.1.4] - 2025-11-25\n\n### 🐛 Bug Fixes\n- Added left padding to `TextInput` when no accessory is present for better visual alignment\n- Adjusted input toolbar styles for improved layout\n\n## [3.1.3] - 2025-11-25\n\n### 🔧 Improvements\n- Removed unused imports for cleaner codebase\n\n## [3.1.2] - 2025-11-24\n\n### 🐛 Bug Fixes\n- Fixed message bubble styles for small messages\n- Improved rendering of compact message content\n\n### 🧪 Testing\n- Updated test snapshots\n\n## [3.1.1] - 2025-11-24\n\n### 🐛 Bug Fixes\n- Fixed Bubble component styles for better message rendering\n- Corrected style inconsistencies in message bubbles\n\n### 🧪 Testing\n- Updated test snapshots to reflect style fixes\n\n## [3.1.0] - 2025-11-24\n\n### 🔧 Improvements\n- Refactored component styles for better maintainability\n- Updated Expo Snack example with latest changes\n\n### 🧪 Testing\n- Updated test snapshots\n\n## [3.0.1] - 2025-11-24\n\n### 🐛 Bug Fixes\n- Fixed Composer auto-resize height behavior on web platform\n\n### 🧪 Testing\n- Updated test snapshots\n\n## [3.0.0] - 2025-11-23\n\nThis 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.\n\n### 🚨 Breaking Changes\n\n#### Renamed Props (GiftedChat)\n- `onInputTextChanged` → moved to `textInputProps.onChangeText` (follows React Native naming pattern)\n- `alwaysShowSend` → `isSendButtonAlwaysVisible` (consistent boolean naming convention)\n- `onPress` → `onPressMessage` (more specific naming)\n- `onLongPress` → `onLongPressMessage` (more specific naming)\n- `options` → `actions` (better semantic naming, different type signature)\n- `optionTintColor` → `actionSheetOptionTintColor` (clearer naming)\n- `renderUsernameOnMessage` → `isUsernameVisible` (consistent boolean naming)\n- `showUserAvatar` → `isUserAvatarVisible` (consistent boolean naming)\n- `showAvatarForEveryMessage` → `isAvatarVisibleForEveryMessage` (consistent boolean naming)\n- `renderAvatarOnTop` → `isAvatarOnTop` (consistent boolean naming)\n- `focusOnInputWhenOpeningKeyboard` → `shouldFocusInputOnKeyboardOpen` (consistent boolean naming)\n- `messageContainerRef` → `messagesContainerRef` (typo fix)\n- `alignTop` → `isAlignedTop` (consistent boolean naming)\n- `inverted` → `isInverted` (consistent boolean naming)\n\n#### Removed Props (GiftedChat)\n- `bottomOffset` - use `keyboardAvoidingViewProps.keyboardVerticalOffset` instead\n- `disableKeyboardController` - removed keyboard controller configuration\n- `isKeyboardInternallyHandled` - keyboard handling now always uses react-native-keyboard-controller\n- `lightboxProps` - custom Modal implementation replaced react-native-lightbox-v2\n- `placeholder` - moved to `textInputProps.placeholder`\n- `disableComposer` - moved to `textInputProps.editable={false}`\n- `keyboardShouldPersistTaps` - moved to `listProps.keyboardShouldPersistTaps`\n- `maxInputLength` - moved to `textInputProps.maxLength`\n- `extraData` - moved to `listProps.extraData`\n- `infiniteScroll` - use `loadEarlierMessagesProps.isInfiniteScrollEnabled` instead\n- `parsePatterns` - removed, automatic link parsing improved\n\n#### Props Moved to MessagesContainer (via spreading)\nThese props moved from `GiftedChatProps` to `MessagesContainerProps` but are still accessible on `GiftedChat` via prop spreading:\n- `messages` - now in MessagesContainerProps\n- `isTyping` - now in MessagesContainerProps (via TypingIndicatorProps)\n- `loadEarlier` → `loadEarlierMessagesProps.isAvailable`\n- `isLoadingEarlier` → `loadEarlierMessagesProps.isLoading`\n- `onLoadEarlier` → `loadEarlierMessagesProps.onPress`\n- `renderLoadEarlier` - now in MessagesContainerProps\n- `renderDay` - now in MessagesContainerProps\n- `renderMessage` - now in MessagesContainerProps\n- `renderFooter` - now in MessagesContainerProps\n- `renderChatEmpty` - now in MessagesContainerProps\n- `scrollToBottomStyle` - now in MessagesContainerProps\n- `isScrollToBottomEnabled` - now in MessagesContainerProps\n- `scrollToBottomComponent` - now in MessagesContainerProps\n- `onQuickReply` - now in MessagesContainerProps\n- `listViewProps` → `listProps` (renamed in MessagesContainerProps)\n\n#### Type Signature Changes\n- `options`: changed from `{ [key: string]: () => void }` to `Array<{ title: string, action: () => void }>`\n- `textInputProps`: changed from `object` to `Partial<React.ComponentProps<typeof TextInput>>`\n- `renderInputToolbar`: now accepts `React.ComponentType | React.ReactElement | function | null` (can be component, element, function, or null)\n- All callback props now use arrow function syntax instead of function syntax for better type inference\n\n#### Dependency Changes\n- Removed `react-native-lightbox-v2` (replaced with custom Modal implementation)\n- Removed `react-native-iphone-x-helper` (deprecated)\n- Removed `react-native-keyboard-controller` as direct dependency\n- Added `react-native-keyboard-controller` as peer dependency (>=1.0.0)\n- Added `react-native-gesture-handler` as peer dependency (>=2.0.0)\n- Added `react-native-reanimated` support for v3 & v4\n- Added `react-native-safe-area-context` as peer dependency (>=5.0.0)\n\n### ✨ New Features\n\n#### TypeScript Migration\n- Complete conversion from JavaScript to TypeScript/TSX\n- Improved type safety and IntelliSense support\n- Better type definitions for all components and props\n- Refactored types to arrow functions for better readability\n\n#### Keyboard Handling\n- New `keyboardTopToolbarHeight` prop for better keyboard customization\n- New `keyboardAvoidingViewProps` to pass props to KeyboardAvoidingView from react-native-keyboard-controller\n- Improved keyboard behavior and offset handling\n- Consolidated keyboard configuration (removed individual keyboard props in favor of `keyboardAvoidingViewProps`)\n- Fixed auto-grow text input behavior\n- Better keyboard open/close transitions\n- New `OverKeyboardView` component for MessageImage to keep keyboard open\n\n#### Message Rendering\n- `isDayAnimationEnabled` prop to control day separator animations\n- Support for passing custom components in render functions\n- Improved message parsing with better link detection\n- Parse links in system messages (fixes #2105)\n- Better phone number parsing with custom matchers support\n- Improved URL parsing (email, phone, URL detection)\n\n#### UI & Styling\n- Dark theme support in example app\n- Safe area provider included in library\n- Improved LoadEarlier messages logic\n- Better themed styles implementation\n- Fixed press animation for TouchableOpacity\n- Replaced deprecated `TouchableWithoutFeedback` with `Pressable`\n- Better scroll to bottom button behavior on Android\n\n#### Image Viewing\n- Custom Modal implementation replacing react-native-lightbox-v2\n- Better image viewing experience with proper insets handling\n- Improved MessageImage component\n\n#### Accessibility & UX\n- `renderTicks` prop for message status indicators\n- Better scroll to bottom wrapper visibility handling\n- `useCallbackThrottled` for improved scroll performance\n- Allow passing children to SystemMessage\n- Improved load earlier messages functionality\n\n### 🐛 Bug Fixes\n\n- Fixed duplicate paragraph tags in README\n- Fixed scroll to bottom when `isScrollToBottomEnabled=false` (#2652)\n- Fixed TypeScript type inconsistencies and ESLint errors (#2653)\n- Fixed automatic scroll to bottom issues (#2630, #2621, #2644)\n- Fixed DayAnimated test import and added proper test coverage for renderDay prop\n- Fixed not passed `isDayAnimationEnabled` prop\n- Fixed MessageContainer scroll to bottom press on Android\n- Fixed safer change ScrollToBottomWrapper visibility\n- Fixed dependency cycles in imports\n- Fixed MessageText container style\n- Fixed reanimated issue in MessageContainer\n- Fixed construct messages on send in example\n- Fixed web support in example\n- Fixed #2659 (memoization issues)\n- Fixed #2640 (various bug fixes)\n- Fixed show location in example\n- Fixed errors in keyboard handling\n- Fixed load earlier messages functionality\n- Fixed Bubble type parameter to re-enable generics on message prop (#2639)\n- Fixed listViewProps typing with Partial<FlatListProps> (#2628)\n- Fixed MessageContainer to add renderDay prop and insert DayAnimated Component (#2632)\n- Fixed dateFormatCalendar default value in README\n\n### 🔧 Improvements\n\n#### Performance\n- Memoized values & functions for better performance\n- Better scroll performance with throttled callbacks\n- Optimized re-renders\n\n#### Code Quality\n- Added ESLint with import sorting\n- Fixed all examples with ESLint\n- Improved code structure and organization\n- Better error handling\n- Cleaner prop passing and component structure\n\n#### Testing\n- All tests converted to TypeScript\n- Updated snapshots for new components\n- Run tests in correct timezone (Europe/Paris)\n- Improved test coverage\n- Added comprehensive copilot instructions with validated commands\n\n#### Documentation\n- Improved README structure and formatting\n- Better prop documentation and grouping\n- Added matchers example\n- Added working Expo Snack link\n- Better feature documentation\n- Added maintainer section\n- Improved previews and images\n- Added export documentation\n- Fixed formatting issues and typos\n- Better keyboard props documentation\n\n#### Example App\n- Updated to latest React Native and Expo\n- Added tabs with different chat examples\n- Added working link to Expo Snack\n- Better example organization\n- Added dark theme support\n- Removed padding from bottom of toolbar\n- Added custom phone matcher example\n- Switch to dev build in README\n- Android: transparent navigation & status bars by default\n- Better project structure with multiple example types\n\n#### Build & Development\n- Better dependency management\n- Updated to Node.js >= 20\n- Yarn 1.22.22+ as package manager\n- Added stale workflow for issue management\n- Script to rebuild native dependencies\n- Improved local development setup\n\n### 📦 Dependencies\n\n#### Added\n- `@expo/react-native-action-sheet`: ^4.1.1\n- `@types/lodash.isequal`: ^4.5.8\n- `dayjs`: ^1.11.19\n- `lodash.isequal`: ^4.5.0\n- `react-native-zoom-reanimated`: ^1.4.10\n\n#### Peer Dependencies (now required)\n- `react`: >=18.0.0\n- `react-native`: *\n- `react-native-gesture-handler`: >=2.0.0\n- `react-native-keyboard-controller`: >=1.0.0\n- `react-native-reanimated`: >=3.0.0 || ^4.0.0\n- `react-native-safe-area-context`: >=5.0.0\n\n### 🔄 Migration Guide\n\n#### Update Prop Names\n```javascript\n// Before (v2.8.1)\n<GiftedChat\n  messages={messages}\n  onInputTextChanged={handleTextChange}\n  alwaysShowSend={true}\n  onPress={handlePress}\n  onLongPress={handleLongPress}\n  options={{ 'Option 1': action1, 'Option 2': action2 }}\n  optionTintColor=\"#007AFF\"\n  bottomOffset={100}\n  placeholder=\"Type a message...\"\n  maxInputLength={1000}\n  renderUsernameOnMessage={true}\n  showUserAvatar={false}\n  showAvatarForEveryMessage={false}\n  renderAvatarOnTop={false}\n  alignTop={false}\n  inverted={true}\n  loadEarlier={true}\n  isLoadingEarlier={false}\n  onLoadEarlier={handleLoadEarlier}\n/>\n\n// After (v3.0.0)\n<GiftedChat\n  messages={messages}\n  textInputProps={{\n    onChangeText: handleTextChange,\n    placeholder: \"Type a message...\",\n    maxLength: 1000\n  }}\n  isSendButtonAlwaysVisible={true}\n  onPressMessage={handlePress}\n  onLongPressMessage={handleLongPress}\n  actions={[\n    { title: 'Option 1', action: action1 },\n    { title: 'Option 2', action: action2 }\n  ]}\n  actionSheetOptionTintColor=\"#007AFF\"\n  keyboardAvoidingViewProps={{ keyboardVerticalOffset: 100 }}\n  isUsernameVisible={true}\n  isUserAvatarVisible={false}\n  isAvatarVisibleForEveryMessage={false}\n  isAvatarOnTop={false}\n  isAlignedTop={false}\n  isInverted={true}\n  loadEarlierMessagesProps={{\n    isAvailable: true,\n    isLoading: false,\n    onPress: handleLoadEarlier\n  }}\n/>\n```\n\n#### Install Peer Dependencies\n```bash\nnpm install react-native-gesture-handler react-native-keyboard-controller react-native-reanimated react-native-safe-area-context\n# or\nyarn add react-native-gesture-handler react-native-keyboard-controller react-native-reanimated react-native-safe-area-context\n```\n\n#### Update Image Lightbox\nThe 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.\n\n### 📝 Notes\n\n- This version includes 170+ commits since v2.8.1\n- Full TypeScript support with improved type definitions\n- Better React Native compatibility (tested with RN 0.81.5)\n- Improved React 19 support\n- Better Expo integration\n\n### 👥 Contributors\n\nSpecial thanks to all contributors who made this release possible, including fixes and improvements from the community.\n\n---\n\nFor detailed commit history, see: https://github.com/FaridSafi/react-native-gifted-chat/compare/2.8.1...3.0.0\n"
  },
  {
    "path": "ISSUE_TEMPLATE.md",
    "content": "#### Issue Description\n\n[FILL THIS OUT]\n\n#### Steps to Reproduce / Code Snippets\n\n[FILL THIS OUT]\n\n#### Expected Results\n\n[FILL THIS OUT]\n\n#### Additional Information\n\n* Nodejs version: [FILL THIS OUT]\n* React version: [FILL THIS OUT]\n* React Native version: [FILL THIS OUT]\n* react-native-gifted-chat version: [FILL THIS OUT]\n* Platform(s) (iOS, Android, or both?): [FILL THIS OUT]\n* TypeScript version: [FILL THIS OUT]\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2019 Farid from Safi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <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>\n  <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>\n  <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>\n  <img src=\"https://img.shields.io/badge/platforms-iOS%20%7C%20Android%20%7C%20Web-lightgrey.svg\" alt=\"platforms\">\n  <img src=\"https://img.shields.io/badge/TypeScript-supported-blue.svg\" alt=\"TypeScript\">\n  <img src=\"https://img.shields.io/badge/Expo-compatible-000020.svg\" alt=\"Expo compatible\">\n</p>\n\n<h1 align=\"center\">React Native Gifted Chat</h1>\n\n<p align=\"center\">\n  The most complete chat UI for React Native & Web\n</p>\n\n<p align=\"center\">\n  <a href=\"https://snack.expo.dev/@kesha-antonov/gifted-chat-playground\" target=\"_blank\">\n    <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\"/>\n  </a>\n</p>\n\n---\n\n## ✨ Features\n\n- 🎨 **Fully Customizable** - Override any component with your own implementation\n- 📎 **Composer Actions** - Attach photos, files, or trigger custom actions\n- ↩️ **Reply to Messages** - Swipe-to-reply with reply preview and message threading\n- ⏮️ **Load Earlier Messages** - Infinite scroll with pagination support\n- 📋 **Copy to Clipboard** - Long-press messages to copy text\n- 🔗 **Smart Link Parsing** - Auto-detect URLs, emails, phone numbers, hashtags, mentions\n- 👤 **Avatars** - User initials or custom avatar images\n- 🌍 **Localized Dates** - Full i18n support via Day.js\n- ⌨️ **Keyboard Handling** - Smart keyboard avoidance for all platforms\n- 💬 **System Messages** - Display system notifications in chat\n- ⚡ **Quick Replies** - Bot-style quick reply buttons\n- ✍️ **Typing Indicator** - Show when users are typing\n- ✅ **Message Status** - Tick indicators for sent/delivered/read states\n- ⬇️ **Scroll to Bottom** - Quick navigation button\n- 🌐 **Web Support** - Works with react-native-web\n- 📱 **Expo Support** - Easy integration with Expo projects\n- 📝 **TypeScript** - Complete TypeScript definitions included\n\n<p align=\"center\">\n  <img width=\"200\" src=\"https://github.com/user-attachments/assets/c9da88f5-0b20-471c-8cd7-373bdb767517\" />\n  &nbsp;&nbsp;&nbsp;&nbsp;\n  <img width=\"200\" src=\"https://github.com/user-attachments/assets/f72b17f1-6c2e-43b5-87e7-477011aa3b07\" />\n  &nbsp;&nbsp;&nbsp;&nbsp;\n  <img width=\"200\" src=\"https://github.com/user-attachments/assets/86711e73-ee3c-4527-b38d-e4dab47a44fe\" />\n</p>\n\n---\n\n<h3 align=\"center\">Sponsors</h3>\n\n<table align=\"center\" border=\"0\" cellspacing=\"20\">\n  <tr>\n    <td align=\"center\" valign=\"middle\">\n      <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>\n    </td>\n    <td align=\"center\" valign=\"middle\">\n      <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>\n    </td>\n    <td align=\"center\" valign=\"middle\">\n      <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>\n    </td>\n  </tr>\n</table>\n\n<p align=\"center\">\n  <a href=\"https://www.lereacteur.io\" target=\"_blank\"><strong>Le Reacteur</strong></a> - Coding Bootcamp in Paris co-founded by Farid Safi\n  <br>\n  <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>)\n  <br>\n  <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>)\n  <br><br>\n  📚 <a href=\"https://amzn.to/3ZmTyb2\" target=\"_blank\">React Key Concepts (2nd ed.)</a>\n</p>\n\n---\n\n## 📖 Table of Contents\n\n- [Features](#-features)\n- [Requirements](#-requirements)\n- [Installation](#-installation)\n- [Usage](#-usage)\n- [Props Reference](#-props-reference)\n- [Data Structure](#-data-structure)\n- [Platform Notes](#-platform-notes)\n- [Example App](#-example-app)\n- [Troubleshooting](#-troubleshooting)\n- [Contributing](#-contributing)\n- [Authors](#-authors)\n- [License](#-license)\n\n---\n\n## 📋 Requirements\n\n| Requirement | Version |\n|-------------|---------|\n| React Native | >= 0.70.0 |\n| iOS | >= 13.4 |\n| Android | API 21+ (Android 5.0) |\n| Expo | SDK 50+ |\n| TypeScript | >= 5.0 (optional) |\n\n---\n\n## 📦 Installation\n\n### Expo Projects\n\n```bash\nnpx expo install react-native-gifted-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller\n```\n\n### Bare React Native Projects\n\n**Step 1:** Install the packages\n\nUsing yarn:\n```bash\nyarn add react-native-gifted-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller\n```\n\nUsing npm:\n```bash\nnpm install --save react-native-gifted-chat react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-keyboard-controller\n```\n\n**Step 2:** Install iOS pods\n\n```bash\nnpx pod-install\n```\n\n**Step 3:** Configure react-native-reanimated\n\nFollow 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.\n\n---\n\n## 🚀 Usage\n\n### Basic Example\n\n```jsx\nimport React, { useState, useCallback, useEffect } from 'react'\nimport { GiftedChat } from 'react-native-gifted-chat'\nimport { useHeaderHeight } from '@react-navigation/elements'\n\nexport function Example() {\n  const [messages, setMessages] = useState([])\n\n  // keyboardVerticalOffset = distance from screen top to GiftedChat container\n  // useHeaderHeight() returns status bar + navigation header height\n  const headerHeight = useHeaderHeight()\n\n  useEffect(() => {\n    setMessages([\n      {\n        _id: 1,\n        text: 'Hello developer',\n        createdAt: new Date(),\n        user: {\n          _id: 2,\n          name: 'John Doe',\n          avatar: 'https://placeimg.com/140/140/any',\n        },\n      },\n    ])\n  }, [])\n\n  const onSend = useCallback((messages = []) => {\n    setMessages(previousMessages =>\n      GiftedChat.append(previousMessages, messages),\n    )\n  }, [])\n\n  return (\n    <GiftedChat\n      messages={messages}\n      onSend={messages => onSend(messages)}\n      user={{\n        _id: 1,\n      }}\n      keyboardAvoidingViewProps={{ keyboardVerticalOffset: headerHeight }}\n    />\n  )\n}\n```\n\n> **💡 Tip:** Check out more examples in the [`example`](example) directory including Slack-style messages, quick replies, and custom components.\n\n---\n\n## 📊 Data Structure\n\nMessages, system messages, and quick replies follow the structure defined in [Models.ts](src/Models.ts).\n\n<details>\n<summary><strong>Message Object Structure</strong></summary>\n\n```typescript\ninterface IMessage {\n  _id: string | number\n  text: string\n  createdAt: Date | number\n  user: User\n  image?: string\n  video?: string\n  audio?: string\n  system?: boolean\n  sent?: boolean\n  received?: boolean\n  pending?: boolean\n  quickReplies?: QuickReplies\n}\n\ninterface User {\n  _id: string | number\n  name?: string\n  avatar?: string | number | (() => React.ReactNode)\n}\n```\n\n</details>\n\n---\n\n## 📖 Props Reference\n\n### Core Configuration\n\n- **`messages`** _(Array)_ - Messages to display\n- **`user`** _(Object)_ - User sending the messages: `{ _id, name, avatar }`\n- **`onSend`** _(Function)_ - Callback when sending a message\n- **`messageIdGenerator`** _(Function)_ - Generate an id for new messages. Defaults to a simple random string generator.\n- **`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'`)\n- **`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`.\n\n### Refs\n\n- **`messagesContainerRef`** _(FlatList ref)_ - Ref to the flatlist\n- **`textInputRef`** _(TextInput ref)_ - Ref to the text input\n\n### Keyboard & Layout\n\n- **`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:\n  - `statusBarTranslucent: true` - Required on Android for correct keyboard height calculation when status bar is translucent (edge-to-edge mode)\n  - `navigationBarTranslucent: true` - Required on Android for correct keyboard height calculation when navigation bar is translucent (edge-to-edge mode)\n- **`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.\n- **`isAlignedTop`** _(Boolean)_ Controls whether or not the message bubbles appear at the top of the chat (Default is false - bubbles align to bottom)\n- **`isInverted`** _(Bool)_ - Reverses display order of `messages`; default is `true`\n\n#### Understanding `keyboardVerticalOffset`\n\nThe [`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).\n\n**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`.\n\n**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:\n- Status bar height\n- Navigation header height (on iOS, `useHeaderHeight()` already includes status bar)\n\n**How to use:**\n\n```jsx\nimport { useHeaderHeight } from '@react-navigation/elements'\n\nfunction ChatScreen() {\n  // useHeaderHeight() returns status bar + navigation header height on iOS\n  const headerHeight = useHeaderHeight()\n\n  return (\n    <GiftedChat\n      keyboardAvoidingViewProps={{ keyboardVerticalOffset: headerHeight }}\n      // ... other props\n    />\n  )\n}\n```\n\n> **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.\n\n**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.\n\n### Text Input & Composer\n\n- **`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.\n- **`initialText`** _(String)_ - Initial text to display in the input field\n- **`isSendButtonAlwaysVisible`** _(Bool)_ - Always show send button in input text composer; default `false`, show only when text input is not empty\n- **`isTextOptional`** _(Bool)_ - Allow sending messages without text (useful for media-only messages); default `false`. Use with `isSendButtonAlwaysVisible` for media attachments.\n- **`minComposerHeight`** _(Object)_ - Custom min-height of the composer.\n- **`maxComposerHeight`** _(Object)_ - Custom max height of the composer.\n- **`minInputToolbarHeight`** _(Integer)_ - Minimum height of the input toolbar; default is `44`\n- **`renderInputToolbar`** _(Component | Function)_ - Custom message composer container\n- **`renderComposer`** _(Component | Function)_ - Custom text input message composer\n- **`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))\n- **`renderActions`** _(Component | Function)_ - Custom action button on the left of the message composer\n- **`renderAccessory`** _(Component | Function)_ - Custom second line of actions below the message composer\n- **`textInputProps`** _(Object)_ - props to be passed to the [`<TextInput>`](https://reactnative.dev/docs/textinput).\n\n### Actions & Action Sheet\n\n- **`onPressActionButton`** _(Function)_ - Callback when the Action button is pressed (if set, the default `actionSheet` will not be used)\n- **`actionSheet`** _(Function)_ - Custom action sheet interface for showing action options\n- **`actions`** _(Array)_ - Custom action options for the input toolbar action button; array of objects with `title` (string) and `action` (function) properties\n- **`actionSheetOptionTintColor`** _(String)_ - Tint color for action sheet options\n\n### Messages & Message Container\n\n- **`messagesContainerStyle`** _(Object)_ - Custom style for the messages container\n- **`renderMessage`** _(Component | Function)_ - Custom message container\n- **`renderLoading`** _(Component | Function)_ - Render a loading view when initializing\n- **`renderChatEmpty`** _(Component | Function)_ - Custom component to render in the ListView when messages are empty\n- **`renderChatFooter`** _(Component | Function)_ - Custom component to render below the MessagesContainer (separate from the ListView)\n- **`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).\n\n### Message Bubbles & Content\n\n- **`renderBubble`** _(Component | Function(`props: BubbleProps`))_ - Custom message bubble. Receives [BubbleProps](src/Bubble/types.ts) as parameter.\n- **`renderMessageText`** _(Component | Function)_ - Custom message text\n- **`renderMessageImage`** _(Component | Function)_ - Custom message image\n- **`renderMessageVideo`** _(Component | Function)_ - Custom message video\n- **`renderMessageAudio`** _(Component | Function)_ - Custom message audio\n- **`renderCustomView`** _(Component | Function)_ - Custom view inside the bubble\n- **`isCustomViewBottom`** _(Bool)_ - Determine whether renderCustomView is displayed before or after the text, image and video views; default is `false`\n- **`onPressMessage`** _(Function(`context`, `message`))_ - Callback when a message bubble is pressed\n- **`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)\n- **`imageProps`** _(Object)_ - Extra props to be passed to the [`<Image>`](https://reactnative.dev/docs/image) component created by the default `renderMessageImage`\n- **`imageStyle`** _(Object)_ - Custom style for message images\n- **`videoProps`** _(Object)_ - Extra props to be passed to the video component created by the required `renderMessageVideo`\n- **`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:\n  - `matchers` - Custom matchers for linking message content (like URLs, phone numbers, hashtags, mentions)\n  - `linkStyle` - Custom style for links\n  - `email` - Enable/disable email parsing (default: true)\n  - `phone` - Enable/disable phone number parsing (default: true)\n  - `url` - Enable/disable URL parsing (default: true)\n  - `hashtag` - Enable/disable hashtag parsing (default: false)\n  - `mention` - Enable/disable mention parsing (default: false)\n  - `hashtagUrl` - Base URL for hashtags (e.g., 'https://x.com/hashtag')\n  - `mentionUrl` - Base URL for mentions (e.g., 'https://x.com')\n  - `stripPrefix` - Strip 'http://' or 'https://' from URL display (default: false)\n  - `TextComponent` - Custom Text component to use (e.g., from react-native-gesture-handler)\n\nExample:\n\n```tsx\n<GiftedChat\n  messageTextProps={{\n    phone: false, // Disable default phone number linking\n    matchers: [\n      {\n        type: 'phone',\n        pattern: /\\+?[1-9][0-9\\-\\(\\) ]{7,}[0-9]/g,\n        getLinkUrl: (replacerArgs: ReplacerArgs): string => {\n          return replacerArgs[0].replace(/[\\-\\(\\) ]/g, '')\n        },\n        getLinkText: (replacerArgs: ReplacerArgs): string => {\n          return replacerArgs[0]\n        },\n        style: styles.linkStyle,\n        onPress: (match: CustomMatch) => {\n          const url = match.getAnchorHref()\n\n          const options: {\n            title: string\n            action?: () => void\n          }[] = [\n            { title: 'Copy', action: () => setStringAsync(url) },\n            { title: 'Call', action: () => Linking.openURL(`tel:${url}`) },\n            { title: 'Send SMS', action: () => Linking.openURL(`sms:${url}`) },\n            { title: 'Cancel' },\n          ]\n\n          showActionSheetWithOptions({\n            options: options.map(o => o.title),\n            cancelButtonIndex: options.length - 1,\n          }, (buttonIndex?: number) => {\n            if (buttonIndex === undefined)\n              return\n\n            const option = options[buttonIndex]\n            option.action?.()\n          })\n        },\n      },\n    ],\n    linkStyle: { left: { color: 'blue' }, right: { color: 'lightblue' } },\n  }}\n/>\n```\n\nSee full example in [LinksExample](example/components/chat-examples/LinksExample.tsx)\n\n### Avatars\n\n- **`renderAvatar`** _(Component | Function)_ - Custom message avatar; set to `null` to not render any avatar for the message\n- **`isUserAvatarVisible`** _(Bool)_ - Whether to render an avatar for the current user; default is `false`, only show avatars for other users\n- **`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`\n- **`onPressAvatar`** _(Function(`user`))_ - Callback when a message avatar is tapped\n- **`onLongPressAvatar`** _(Function(`user`))_ - Callback when a message avatar is long-pressed\n- **`isAvatarOnTop`** _(Bool)_ - Render the message avatar at the top of consecutive messages, rather than the bottom; default is `false`\n\n### Username\n\n- **`isUsernameVisible`** _(Bool)_ - Indicate whether to show the user's username inside the message bubble; default is `false`\n- **`renderUsername`** _(Component | Function)_ - Custom Username container\n\n### Date & Time\n\n- **`timeFormat`** _(String)_ - Format to use for rendering times; default is `'LT'` (see [Day.js Format](https://day.js.org/docs/en/display/format))\n- **`dateFormat`** _(String)_ - Format to use for rendering dates; default is `'D MMMM'` (see [Day.js Format](https://day.js.org/docs/en/display/format))\n- **`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))\n- **`renderDay`** _(Component | Function)_ - Custom day above a message\n- **`dayProps`** _(Object)_ - Props to pass to the Day component:\n  - `containerStyle` - Custom style for the day container\n  - `wrapperStyle` - Custom style for the day wrapper\n  - `textProps` - Props to pass to the Text component (e.g., `style`, `allowFontScaling`, `numberOfLines`)\n- **`renderTime`** _(Component | Function)_ - Custom time inside a message\n- **`timeTextStyle`** _(Object)_ - Custom text style for time inside messages (supports left/right styles)\n- **`isDayAnimationEnabled`** _(Bool)_ - Enable animated day label that appears on scroll; default is `true`\n\n### System Messages\n\n- **`renderSystemMessage`** _(Component | Function)_ - Custom system message\n\n### Load Earlier Messages\n\n- **`loadEarlierMessagesProps`** _(Object)_ - Props to pass to the LoadEarlierMessages component. The button is only visible when `isAvailable` is `true`. Supports the following props:\n  - `isAvailable` - Controls button visibility (default: false)\n  - `onPress` - Callback when button is pressed\n  - `isLoading` - Display loading indicator (default: false)\n  - `isInfiniteScrollEnabled` - Enable infinite scroll up when reaching the top of messages container, automatically calls `onPress` (not yet supported for web)\n  - `label` - Override the default \"Load earlier messages\" text\n  - `containerStyle` - Custom style for the button container\n  - `wrapperStyle` - Custom style for the button wrapper\n  - `textStyle` - Custom style for the button text\n  - `activityIndicatorStyle` - Custom style for the loading indicator\n  - `activityIndicatorColor` - Color of the loading indicator (default: 'white')\n  - `activityIndicatorSize` - Size of the loading indicator (default: 'small')\n- **`renderLoadEarlier`** _(Component | Function)_ - Custom \"Load earlier messages\" button\n\n### Typing Indicator\n\n- **`isTyping`** _(Bool)_ - Typing Indicator state; default `false`. If you use`renderFooter` it will override this.\n- **`renderTypingIndicator`** _(Component | Function)_ - Custom typing indicator component\n- **`typingIndicatorStyle`** _(StyleProp<ViewStyle>)_ - Custom style for the TypingIndicator component.\n- **`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.\n\n### Quick Replies\n\nSee [Quick Replies example in messages.ts](example/example-expo/data/messages.ts)\n\n- **`onQuickReply`** _(Function)_ - Callback when sending a quick reply (to backend server)\n- **`renderQuickReplies`** _(Function)_ - Custom all quick reply view\n- **`quickReplyStyle`** _(StyleProp<ViewStyle>)_ - Custom quick reply view style\n- **`quickReplyTextStyle`** _(StyleProp<TextStyle>)_ - Custom text style for quick reply buttons\n- **`quickReplyContainerStyle`** _(StyleProp<ViewStyle>)_ - Custom container style for quick replies\n- **`renderQuickReplySend`** _(Function)_ - Custom quick reply **send** view\n\n### Reply to Messages\n\nGifted 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.\n\n> **Note:** This feature uses `ReanimatedSwipeable` from `react-native-gesture-handler` and `react-native-reanimated` for smooth, performant animations.\n\n#### Basic Usage\n\n```tsx\n<GiftedChat\n  messages={messages}\n  onSend={onSend}\n  user={{ _id: 1 }}\n  reply={{\n    swipe: {\n      isEnabled: true,\n      direction: 'left', // swipe left to reply\n    },\n  }}\n/>\n```\n\n#### Reply Props (Grouped)\n\nThe `reply` prop accepts an object with the following structure:\n\n```typescript\ninterface ReplyProps<TMessage> {\n  // Swipe gesture configuration\n  swipe?: {\n    isEnabled?: boolean              // Enable swipe-to-reply; default false\n    direction?: 'left' | 'right'     // Swipe direction; default 'left'\n    onSwipe?: (message: TMessage) => void  // Callback when swiped\n    renderAction?: (                 // Custom swipe action component\n      progress: SharedValue<number>,\n      translation: SharedValue<number>,\n      position: 'left' | 'right'\n    ) => React.ReactNode\n    actionContainerStyle?: StyleProp<ViewStyle>\n  }\n\n  // Reply preview styling (above input toolbar)\n  previewStyle?: {\n    containerStyle?: StyleProp<ViewStyle>\n    textStyle?: StyleProp<TextStyle>\n    imageStyle?: StyleProp<ImageStyle>\n  }\n\n  // In-bubble reply styling\n  messageStyle?: {\n    containerStyle?: StyleProp<ViewStyle>\n    containerStyleLeft?: StyleProp<ViewStyle>\n    containerStyleRight?: StyleProp<ViewStyle>\n    textStyle?: StyleProp<TextStyle>\n    textStyleLeft?: StyleProp<TextStyle>\n    textStyleRight?: StyleProp<TextStyle>\n    imageStyle?: StyleProp<ImageStyle>\n  }\n\n  // Callbacks and state\n  message?: ReplyMessage             // Controlled reply state\n  onClear?: () => void               // Called when reply cleared\n  onPress?: (message: TMessage) => void  // Called when reply preview tapped\n\n  // Custom renderers\n  renderPreview?: (props: ReplyPreviewProps) => React.ReactNode\n  renderMessageReply?: (props: MessageReplyProps) => React.ReactNode\n}\n```\n\n#### ReplyMessage Structure\n\nWhen a message has a reply, it includes a `replyMessage` property:\n\n```typescript\ninterface ReplyMessage {\n  _id: string | number\n  text: string\n  user: User\n  image?: string\n  audio?: string\n}\n```\n\n#### Advanced Example with External State\n\n```tsx\nconst [replyMessage, setReplyMessage] = useState<ReplyMessage | null>(null)\n\n<GiftedChat\n  messages={messages}\n  onSend={messages => {\n    const newMessages = messages.map(msg => ({\n      ...msg,\n      replyMessage: replyMessage || undefined,\n    }))\n    setMessages(prev => GiftedChat.append(prev, newMessages))\n    setReplyMessage(null)\n  }}\n  user={{ _id: 1 }}\n  reply={{\n    swipe: {\n      isEnabled: true,\n      direction: 'right',\n      onSwipe: setReplyMessage,\n    },\n    message: replyMessage,\n    onClear: () => setReplyMessage(null),\n    onPress: (msg) => scrollToMessage(msg._id),\n  }}\n/>\n```\n\n#### Smooth Animations\n\nThe reply preview automatically animates when:\n- **Appearing**: Smoothly expands from zero height with fade-in effect\n- **Disappearing**: Smoothly collapses with fade-out effect\n- **Content changes**: Smoothly transitions when replying to a different message\n\nThese animations use `react-native-reanimated` for 60fps performance.\n\n### Scroll to Bottom\n\n- **`isScrollToBottomEnabled`** _(Bool)_ - Enables the scroll to bottom Component (Default is false)\n- **`scrollToBottomComponent`** _(Function)_ - Custom Scroll To Bottom Component container\n- **`scrollToBottomOffset`** _(Integer)_ - Custom Height Offset upon which to begin showing Scroll To Bottom Component (Default is 200)\n- **`scrollToBottomStyle`** _(Object)_ - Custom style for Scroll To Bottom wrapper (position, bottom, right, etc.)\n- **`scrollToBottomContentStyle`** _(Object)_ - Custom style for Scroll To Bottom content (size, background, shadow, etc.)\n\n### Maintaining Scroll Position (AI Chatbots)\n\nFor 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`:\n\n```tsx\n// Basic usage - always maintain scroll position\n<GiftedChat\n  listProps={{\n    maintainVisibleContentPosition: {\n      minIndexForVisible: 0,\n    },\n  }}\n/>\n\n// With auto-scroll threshold - auto-scroll if within 10 pixels of newest content\n<GiftedChat\n  listProps={{\n    maintainVisibleContentPosition: {\n      minIndexForVisible: 0,\n      autoscrollToTopThreshold: 10,\n    },\n  }}\n/>\n\n// Conditionally enable based on scroll state (recommended for chatbots)\nconst [isScrolledUp, setIsScrolledUp] = useState(false)\n\n<GiftedChat\n  listProps={{\n    onScroll: (event) => {\n      setIsScrolledUp(event.contentOffset.y > 50)\n    },\n    maintainVisibleContentPosition: isScrolledUp\n      ? { minIndexForVisible: 0, autoscrollToTopThreshold: 10 }\n      : undefined,\n  }}\n/>\n```\n\n---\n\n## 📱 Platform Notes\n\n### Android\n\n<details>\n<summary><strong>Keyboard configuration</strong></summary>\n\nIf you are using Create React Native App / Expo, no Android specific installation steps are required. Otherwise, we recommend modifying your project configuration:\n\nMake sure you have `android:windowSoftInputMode=\"adjustResize\"` in your `AndroidManifest.xml`:\n\n```xml\n<activity\n  android:name=\".MainActivity\"\n  android:label=\"@string/app_name\"\n  android:windowSoftInputMode=\"adjustResize\"\n  android:configChanges=\"keyboard|keyboardHidden|orientation|screenSize\">\n```\n\nFor **Expo**, you can append `KeyboardAvoidingView` after GiftedChat (Android only):\n\n```jsx\n<View style={{ flex: 1 }}>\n   <GiftedChat />\n   {Platform.OS === 'android' && <KeyboardAvoidingView behavior=\"padding\" />}\n</View>\n```\n\n</details>\n\n### Web (react-native-web)\n\n<details>\n<summary><strong>With create-react-app</strong></summary>\n\n1. Install react-app-rewired: `yarn add -D react-app-rewired`\n2. Create `config-overrides.js`:\n\n```js\nmodule.exports = function override(config, env) {\n  config.module.rules.push({\n    test: /\\.js$/,\n    exclude: /node_modules[/\\\\](?!react-native-gifted-chat)/,\n    use: {\n      loader: 'babel-loader',\n      options: {\n        babelrc: false,\n        configFile: false,\n        presets: [\n          ['@babel/preset-env', { useBuiltIns: 'usage' }],\n          '@babel/preset-react',\n        ],\n        plugins: ['@babel/plugin-proposal-class-properties'],\n      },\n    },\n  })\n  return config\n}\n```\n\n> **Examples:**\n> - [xcarpentier/gifted-chat-web-demo](https://github.com/xcarpentier/gifted-chat-web-demo)\n> - [Gatsby example](https://github.com/xcarpentier/clean-archi-boilerplate/tree/develop/apps/web)\n\n</details>\n\n---\n\n## 🧪 Testing\n\n<details>\n<summary><strong>Triggering layout events in tests</strong></summary>\n\n`TEST_ID` is exported as constants that can be used in your testing library of choice.\n\nGifted Chat uses `onLayout` to determine the height of the chat container. To trigger `onLayout` during your tests:\n\n```typescript\nconst WIDTH = 200\nconst HEIGHT = 2000\n\nconst loadingWrapper = getByTestId(TEST_ID.LOADING_WRAPPER)\nfireEvent(loadingWrapper, 'layout', {\n  nativeEvent: {\n    layout: {\n      width: WIDTH,\n      height: HEIGHT,\n    },\n  },\n})\n```\n\n</details>\n\n---\n\n## 📦 Example App\n\nThe repository includes a comprehensive example app demonstrating all features:\n\n```bash\n# Clone and install\ngit clone https://github.com/FaridSafi/react-native-gifted-chat.git\ncd react-native-gifted-chat/example\nyarn install\n\n# Run on iOS\nnpx expo run:ios\n\n# Run on Android\nnpx expo run:android\n\n# Run on Web\nnpx expo start --web\n```\n\nThe example app showcases:\n- 💬 Basic chat functionality\n- 🎨 Custom message bubbles and avatars\n- ↩️ Reply to messages with swipe gesture\n- ⚡ Quick replies (bot-style)\n- ✍️ Typing indicators\n- 📎 Attachment actions\n- 🔗 Link parsing and custom matchers\n- 🌐 Web compatibility\n\n---\n\n## ❓ Troubleshooting\n\n<details>\n<summary><strong>TextInput is hidden on Android</strong></summary>\n\nMake sure you have `android:windowSoftInputMode=\"adjustResize\"` in your `AndroidManifest.xml`. See [Android configuration](#android) above.\n\n</details>\n\n<details>\n<summary><strong>How to set Bubble color for each user?</strong></summary>\n\nSee [this issue](https://github.com/FaridSafi/react-native-gifted-chat/issues/672) for examples.\n\n</details>\n\n<details>\n<summary><strong>How to customize InputToolbar styles?</strong></summary>\n\nSee [this issue](https://github.com/FaridSafi/react-native-gifted-chat/issues/662) for examples.\n\n</details>\n\n<details>\n<summary><strong>How to manually dismiss the keyboard?</strong></summary>\n\nSee [this issue](https://github.com/FaridSafi/react-native-gifted-chat/issues/647) for examples.\n\n</details>\n\n<details>\n<summary><strong>How to use renderLoading?</strong></summary>\n\nSee [this issue](https://github.com/FaridSafi/react-native-gifted-chat/issues/298) for examples.\n\n</details>\n\n---\n\n## 🤔 Have a Question?\n\n1. Check this README first\n2. Search [existing issues](https://github.com/FaridSafi/react-native-gifted-chat/issues)\n3. Ask on [StackOverflow](https://stackoverflow.com/questions/tagged/react-native-gifted-chat)\n4. Open a new issue if needed\n\n---\n\n## 🤝 Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Install dependencies (`yarn install`)\n4. Make your changes\n5. Run tests (`yarn test`)\n6. Run linting (`yarn lint`)\n7. Build the library (`yarn build`)\n8. Commit your changes (`git commit -m 'Add amazing feature'`)\n9. Push to the branch (`git push origin feature/amazing-feature`)\n10. Open a Pull Request\n\n### Development Setup\n\n```bash\n# Install dependencies\nyarn install\n\n# Build the library\nyarn build\n\n# Run tests\nyarn test\n\n# Run linting\nyarn lint\n\n# Full validation\nyarn prepublishOnly\n```\n\n---\n\n## 👥 Authors\n\n**Original Author:** [Farid Safi](https://www.x.com/FaridSafi)\n\n**Co-author:** [Xavier Carpentier](https://www.x.com/xcapetir) - [Hire Xavier](https://xaviercarpentier.com)\n\n**Maintainer:** [Kesha Antonov](https://github.com/kesha-antonov)\n\n> 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. 💖\n\n---\n\n## 📄 License\n\n[MIT](LICENSE)\n\n---\n\n<p align=\"center\">\n  <sub>Built with ❤️ by the React Native community</sub>\n</p>\n"
  },
  {
    "path": "babel.config.cjs",
    "content": "module.exports = function (api) {\n  api.cache(true)\n\n  return {\n    presets: [\n      '@babel/preset-env',\n      'module:@react-native/babel-preset',\n      '@babel/preset-typescript',\n    ],\n    plugins: [\n      '@babel/plugin-transform-unicode-property-regex',\n      '@babel/plugin-transform-react-jsx',\n      'react-native-reanimated/plugin',\n    ],\n  }\n}\n"
  },
  {
    "path": "codecov.yml",
    "content": "coverage:\n  status:\n    patch:\n      default: off\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import stylistic from '@stylistic/eslint-plugin'\nimport typescriptEslint from '@typescript-eslint/eslint-plugin'\nimport typescriptParser from '@typescript-eslint/parser'\nimport importPlugin from 'eslint-plugin-import'\nimport jestPlugin from 'eslint-plugin-jest'\nimport perfectionistPlugin from 'eslint-plugin-perfectionist'\nimport react from 'eslint-plugin-react'\nimport reactHooks from 'eslint-plugin-react-hooks'\n\nexport default [\n  {\n    ignores: [\n      '**/node_modules/**',\n      '**/lib/**',\n      '**/build/**',\n      '**/.expo/**',\n      '**/android/**',\n      '**/ios/**',\n      // Config files\n      'example/*.js',\n      'example/*.config.js',\n      'example/scripts/**',\n    ],\n  },\n  {\n    files: ['src/**/*.{js,jsx,ts,tsx}', 'tests/**/*.{js,jsx,ts,tsx}'],\n    languageOptions: {\n      ecmaVersion: 'latest',\n      sourceType: 'module',\n      parser: typescriptParser,\n      parserOptions: {\n        ecmaFeatures: {\n          jsx: true,\n        },\n      },\n      globals: {\n        fetch: 'readonly',\n        navigator: 'readonly',\n        __DEV__: 'readonly',\n        XMLHttpRequest: 'readonly',\n        FormData: 'readonly',\n        React$Element: 'readonly',\n        requestAnimationFrame: 'readonly',\n\n        // Node.js globals for build scripts and configuration files\n        require: 'readonly',\n        module: 'readonly',\n        process: 'readonly',\n        global: 'readonly',\n        console: 'readonly',\n        setTimeout: 'readonly',\n        clearTimeout: 'readonly',\n        setInterval: 'readonly',\n        clearInterval: 'readonly',\n\n        // Jest globals\n        describe: 'readonly',\n        test: 'readonly',\n        it: 'readonly',\n        jest: 'readonly',\n        expect: 'readonly',\n        beforeAll: 'readonly',\n        beforeEach: 'readonly',\n        afterAll: 'readonly',\n        afterEach: 'readonly',\n      },\n    },\n    plugins: {\n      '@stylistic': stylistic,\n      '@typescript-eslint': typescriptEslint,\n      'import': importPlugin,\n      'perfectionist': perfectionistPlugin,\n      'react': react,\n      'react-hooks': reactHooks,\n    },\n    settings: {\n      react: {\n        version: 'detect',\n      },\n      'import/parsers': {\n        '@typescript-eslint/parser': ['.ts', '.tsx'],\n      },\n      'import/resolver': {\n        typescript: {\n          alwaysTryTypes: true,\n          project: './tsconfig.json',\n        },\n        node: {\n          extensions: ['.js', '.jsx', '.ts', '.tsx'],\n        },\n      },\n      'import/core-modules': ['react', 'react-native'],\n    },\n    rules: {\n      // Import rules\n      'import/no-unresolved': 'error',\n      'import/named': 'error',\n      'import/default': 'error',\n      'import/namespace': 'error',\n      'import/export': 'error',\n      'import/no-absolute-path': 'error',\n      'import/no-self-import': 'error',\n      'import/no-cycle': 'warn',\n      'import/no-useless-path-segments': 'error',\n      'import/no-duplicates': 'error',\n      'import/first': 'error',\n      'import/newline-after-import': 'warn',\n      'import/extensions': [\n        'error',\n        'ignorePackages',\n        {\n          js: 'never',\n          jsx: 'never',\n          ts: 'never',\n          tsx: 'never',\n        },\n      ],\n\n      // React rules\n      'react/react-in-jsx-scope': 'off',\n      'react/no-unknown-property': 'off',\n      'react/display-name': 'off',\n      'react/prop-types': 'off',\n\n      // React Hooks\n      'react-hooks/rules-of-hooks': 'error',\n      'react-hooks/exhaustive-deps': [\n        'warn',\n        {\n          additionalHooks:\n            '(useAnimatedStyle|useSharedValue|useAnimatedGestureHandler|useAnimatedScrollHandler|useAnimatedProps|useDerivedValue|useAnimatedRef|useAnimatedReact|useAnimatedReaction|useCallbackDebounced|useCallbackThrottled)',\n        },\n      ],\n\n      // TypeScript rules\n      '@typescript-eslint/no-explicit-any': 'off',\n      '@typescript-eslint/no-unused-vars': ['error'],\n\n      // Stylistic rules\n      '@stylistic/semi': ['error', 'never'],\n      '@stylistic/member-delimiter-style': [\n        'error',\n        {\n          multiline: {\n            delimiter: 'none',\n            requireLast: true,\n          },\n          singleline: {\n            delimiter: 'comma',\n            requireLast: false,\n          },\n        },\n      ],\n      '@stylistic/indent': [\n        'error',\n        2,\n        {\n          SwitchCase: 1,\n          VariableDeclarator: 'first',\n          ignoredNodes: ['TemplateLiteral'],\n        },\n      ],\n      '@stylistic/quotes': ['error', 'single'],\n      '@stylistic/jsx-quotes': ['error', 'prefer-single'],\n      '@stylistic/comma-dangle': [\n        'error',\n        {\n          arrays: 'always-multiline',\n          objects: 'always-multiline',\n          imports: 'always-multiline',\n          exports: 'never',\n          functions: 'never',\n        },\n      ],\n      '@stylistic/arrow-parens': ['error', 'as-needed'],\n      '@stylistic/template-curly-spacing': 'off',\n      '@stylistic/linebreak-style': ['off', 'unix'],\n      '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: false }],\n      '@stylistic/jsx-closing-bracket-location': ['error', 'line-aligned'],\n\n      // General rules\n      'no-func-assign': 'off',\n      'no-class-assign': 'off',\n      'no-useless-escape': 'off',\n      'no-unused-vars': 'off', // Use @typescript-eslint/no-unused-vars instead\n      'no-unreachable': 'error',\n      'curly': [2, 'multi', 'consistent'],\n      'nonblock-statement-body-position': ['error', 'below'],\n\n      // Perfectionist rules\n      'perfectionist/sort-imports': [\n        'error',\n        {\n          groups: [\n            'react',\n            'external',\n            'internal',\n            ['parent', 'sibling'],\n            'index',\n          ],\n          customGroups: {\n            value: {\n              react: ['^react$', '^react-native$'],\n            },\n          },\n          newlinesBetween: 'ignore',\n        },\n      ],\n      'perfectionist/sort-interfaces': 'off',\n    },\n  },\n  // Example app configuration with path aliases\n  {\n    files: ['example/**/*.{js,jsx,ts,tsx}'],\n    languageOptions: {\n      ecmaVersion: 'latest',\n      sourceType: 'module',\n      parser: typescriptParser,\n      parserOptions: {\n        ecmaFeatures: {\n          jsx: true,\n        },\n        project: './example/tsconfig.json',\n      },\n      globals: {\n        fetch: 'readonly',\n        navigator: 'readonly',\n        __DEV__: 'readonly',\n        XMLHttpRequest: 'readonly',\n        FormData: 'readonly',\n        React$Element: 'readonly',\n        requestAnimationFrame: 'readonly',\n        require: 'readonly',\n        module: 'readonly',\n        process: 'readonly',\n        global: 'readonly',\n        console: 'readonly',\n        setTimeout: 'readonly',\n        clearTimeout: 'readonly',\n        setInterval: 'readonly',\n        clearInterval: 'readonly',\n      },\n    },\n    plugins: {\n      '@stylistic': stylistic,\n      '@typescript-eslint': typescriptEslint,\n      'import': importPlugin,\n      'perfectionist': perfectionistPlugin,\n      'react': react,\n      'react-hooks': reactHooks,\n    },\n    settings: {\n      react: {\n        version: 'detect',\n      },\n      'import/parsers': {\n        '@typescript-eslint/parser': ['.ts', '.tsx'],\n      },\n      'import/resolver': {\n        typescript: {\n          alwaysTryTypes: true,\n          project: './example/tsconfig.json',\n        },\n        node: {\n          extensions: ['.js', '.jsx', '.ts', '.tsx'],\n        },\n      },\n      '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'],\n    },\n    rules: {\n      // Import rules\n      'import/no-unresolved': 'error',\n      'import/named': 'error',\n      'import/default': 'error',\n      'import/namespace': 'error',\n      'import/export': 'error',\n      'import/no-absolute-path': 'error',\n      'import/no-self-import': 'error',\n      'import/no-cycle': 'warn',\n      'import/no-useless-path-segments': 'error',\n      'import/no-duplicates': 'error',\n      'import/first': 'error',\n      'import/newline-after-import': 'warn',\n      'import/extensions': [\n        'error',\n        'ignorePackages',\n        {\n          js: 'never',\n          jsx: 'never',\n          ts: 'never',\n          tsx: 'never',\n        },\n      ],\n\n      // React rules\n      'react/react-in-jsx-scope': 'off',\n      'react/no-unknown-property': 'off',\n      'react/display-name': 'off',\n      'react/prop-types': 'off',\n\n      // React Hooks\n      'react-hooks/rules-of-hooks': 'error',\n      'react-hooks/exhaustive-deps': [\n        'warn',\n        {\n          additionalHooks:\n            '(useAnimatedStyle|useSharedValue|useAnimatedGestureHandler|useAnimatedScrollHandler|useAnimatedProps|useDerivedValue|useAnimatedRef|useAnimatedReact|useAnimatedReaction|useCallbackDebounced|useCallbackThrottled)',\n        },\n      ],\n\n      // TypeScript rules\n      '@typescript-eslint/no-explicit-any': 'off',\n      '@typescript-eslint/no-unused-vars': ['error'],\n\n      // Stylistic rules\n      '@stylistic/semi': ['error', 'never'],\n      '@stylistic/member-delimiter-style': [\n        'error',\n        {\n          multiline: {\n            delimiter: 'none',\n            requireLast: true,\n          },\n          singleline: {\n            delimiter: 'comma',\n            requireLast: false,\n          },\n        },\n      ],\n      '@stylistic/indent': [\n        'error',\n        2,\n        {\n          SwitchCase: 1,\n          VariableDeclarator: 'first',\n          ignoredNodes: ['TemplateLiteral'],\n        },\n      ],\n      '@stylistic/quotes': ['error', 'single'],\n      '@stylistic/jsx-quotes': ['error', 'prefer-single'],\n      '@stylistic/comma-dangle': [\n        'error',\n        {\n          arrays: 'always-multiline',\n          objects: 'always-multiline',\n          imports: 'always-multiline',\n          exports: 'never',\n          functions: 'never',\n        },\n      ],\n      '@stylistic/arrow-parens': ['error', 'as-needed'],\n      '@stylistic/template-curly-spacing': 'off',\n      '@stylistic/linebreak-style': ['off', 'unix'],\n      '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: false }],\n      '@stylistic/jsx-closing-bracket-location': ['error', 'line-aligned'],\n\n      // General rules\n      'no-func-assign': 'off',\n      'no-class-assign': 'off',\n      'no-useless-escape': 'off',\n      'no-unused-vars': 'off',\n      'no-unreachable': 'error',\n      'curly': [2, 'multi', 'consistent'],\n      'nonblock-statement-body-position': ['error', 'below'],\n\n      // Perfectionist rules\n      'perfectionist/sort-imports': [\n        'error',\n        {\n          groups: [\n            'react',\n            'external',\n            'internal',\n            ['parent', 'sibling'],\n            'index',\n          ],\n          customGroups: {\n            value: {\n              react: ['^react$', '^react-native$'],\n            },\n          },\n          newlinesBetween: 'ignore',\n        },\n      ],\n      'perfectionist/sort-interfaces': 'off',\n    },\n  },\n  {\n    files: ['tests/**/*', 'src/__tests__/**/*'],\n    plugins: {\n      jest: jestPlugin,\n    },\n    rules: {\n      ...jestPlugin.configs.recommended.rules,\n    },\n  },\n]\n"
  },
  {
    "path": "example/.gitignore",
    "content": "# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files\n\n# dependencies\nnode_modules/\n\n# Expo\n.expo/\ndist/\nweb-build/\nexpo-env.d.ts\n\n# Native\n.kotlin/\n*.orig.*\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n\n# Metro\n.metro-health-check*\n\n# debug\nnpm-debug.*\nyarn-debug.*\nyarn-error.*\n\n# macOS\n.DS_Store\n*.pem\n\n# local env files\n.env*.local\n\n# typescript\n*.tsbuildinfo\n\napp-example\n\n# generated native folders\n/ios\n/android\n"
  },
  {
    "path": "example/README.md",
    "content": "# How to use\n\n1. Install dependencies:\n\n   ```bash\n   yarn install\n   cd example\n   yarn install\n   ```\n\n2. Install iOS dev build (only needed once or after rebuilding native dependencies):\n   ```bash\n   yarn installDevBuild:ios\n   ```\n\n   or\n\n   ```bash\n   yarn installDevBuild:android\n   ```\n\n3. Start the example app on iOS simulator:\n\n   ```bash\n   yarn start:ios\n   ```\n\n# Dark Theme Support\n\nTo switch theme press `Cmd + Shift + A` in iOS simulator.\n"
  },
  {
    "path": "example/app/(tabs)/_layout.tsx",
    "content": "import React from 'react'\nimport { Tabs } from 'expo-router'\n\nimport { HapticTab } from '@/components/haptic-tab'\nimport { IconSymbol } from '@/components/ui/icon-symbol'\nimport { Colors } from '@/constants/theme'\nimport { useColorScheme } from '@/hooks/use-color-scheme'\n\nexport default function TabLayout () {\n  const colorScheme = useColorScheme()\n\n  return (\n    <Tabs\n      screenOptions={{\n        tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,\n        headerShown: false,\n        tabBarButton: HapticTab,\n      }}\n    >\n      <Tabs.Screen\n        name='index'\n        options={{\n          title: 'Home',\n          tabBarIcon: ({ color }) => <IconSymbol size={28} name='house.fill' color={color} />,\n        }}\n      />\n      <Tabs.Screen\n        name='explore'\n        options={{\n          title: 'Explore',\n          tabBarIcon: ({ color }) => <IconSymbol size={28} name='paperplane.fill' color={color} />,\n        }}\n      />\n    </Tabs>\n  )\n}\n"
  },
  {
    "path": "example/app/(tabs)/explore.tsx",
    "content": "import React from 'react'\nimport { ScrollView, StyleSheet, View } from 'react-native'\nimport { useRouter } from 'expo-router'\nimport { RectButton } from 'react-native-gesture-handler'\nimport { SafeAreaView } from 'react-native-safe-area-context'\n\nimport { ThemedText } from '@/components/themed-text'\nimport { ThemedView } from '@/components/themed-view'\nimport { useThemeColor } from '@/hooks/use-theme-color'\n\ntype ChatExample = 'basic' | 'customized-rendering' | 'slack' | 'links' | 'reply'\n\nconst examples: Array<{ id: ChatExample, title: string, description: string }> = [\n  { id: 'basic', title: 'Basic Example', description: 'Basic chat with keyboard logging for testing' },\n  { id: 'links', title: 'Links & Patterns', description: 'Phone numbers, emails, URLs, hashtags, and mentions' },\n  { id: 'customized-rendering', title: 'Customized Rendering', description: 'Customized chat with all rendering options' },\n  { id: 'slack', title: 'Slack Style', description: 'Slack-like message styling' },\n  { id: 'reply', title: 'Reply Example', description: 'Example demonstrating reply functionality' },\n]\n\nexport default function ExploreScreen () {\n  const router = useRouter()\n  const backgroundColor = useThemeColor({}, 'background')\n  const borderColor = useThemeColor({ light: '#e0e0e0', dark: '#444' }, 'icon')\n\n  return (\n    <SafeAreaView style={[styles.fill, { backgroundColor }]} edges={['top']}>\n      <ScrollView style={styles.fill}>\n        <ThemedView style={styles.titleContainer}>\n          <ThemedText type='title'>Explore Chat Examples</ThemedText>\n        </ThemedView>\n        <ThemedView style={styles.description}>\n          <ThemedText>\n            Choose from different chat implementations to see various features and styling options.\n          </ThemedText>\n        </ThemedView>\n        <View style={styles.examplesContainer}>\n          {examples.map(example => (\n            <RectButton\n              key={example.id}\n              style={[styles.exampleCard, { borderColor }]}\n              onPress={() => router.push(`/chat/${example.id}`)}\n            >\n              <ThemedText type='subtitle' style={styles.exampleTitle}>\n                {example.title}\n              </ThemedText>\n              <ThemedText style={styles.exampleDescription}>\n                {example.description}\n              </ThemedText>\n              <ThemedText type='link' style={styles.tryButton}>\n                Try it →\n              </ThemedText>\n            </RectButton>\n          ))}\n        </View>\n      </ScrollView>\n    </SafeAreaView>\n  )\n}\n\nconst styles = StyleSheet.create({\n  fill: {\n    flex: 1,\n  },\n  titleContainer: {\n    padding: 20,\n    paddingBottom: 10,\n  },\n  description: {\n    paddingHorizontal: 20,\n    paddingBottom: 20,\n  },\n  examplesContainer: {\n    padding: 20,\n    gap: 15,\n  },\n  exampleCard: {\n    padding: 20,\n    borderRadius: 12,\n    borderWidth: 1,\n    gap: 8,\n  },\n  exampleTitle: {\n    marginBottom: 4,\n  },\n  exampleDescription: {\n    opacity: 0.7,\n    marginBottom: 8,\n  },\n  tryButton: {\n    alignSelf: 'flex-start',\n  },\n})\n"
  },
  {
    "path": "example/app/(tabs)/index.tsx",
    "content": "import { StyleSheet } from 'react-native'\nimport { Image } from 'expo-image'\n\nimport { ExternalLink } from '@/components/external-link'\nimport { HelloWave } from '@/components/hello-wave'\nimport ParallaxScrollView from '@/components/parallax-scroll-view'\nimport { ThemedText } from '@/components/themed-text'\nimport { ThemedView } from '@/components/themed-view'\n\nexport default function HomeScreen () {\n  return (\n    <ParallaxScrollView\n      headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}\n      headerImage={\n        <Image\n          source={require('@/assets/images/partial-react-logo.png')}\n          style={styles.reactLogo}\n        />\n      }\n    >\n      <ThemedView style={styles.titleContainer}>\n        <ThemedText type='title'>Welcome!</ThemedText>\n        <HelloWave />\n      </ThemedView>\n      <ThemedView style={styles.stepContainer}>\n        <ThemedText type='subtitle'>React Native Gifted Chat</ThemedText>\n        <ThemedText>\n          The most complete chat UI for React Native\n        </ThemedText>\n        <ExternalLink href='https://github.com/FaridSafi/react-native-gifted-chat'>\n          <ThemedText type='link'>View on GitHub</ThemedText>\n        </ExternalLink>\n      </ThemedView>\n      <ThemedView style={styles.stepContainer}>\n        <ThemedText>\n          Tap the Explore tab to try different chat examples.\n        </ThemedText>\n      </ThemedView>\n    </ParallaxScrollView>\n  )\n}\n\nconst styles = StyleSheet.create({\n  titleContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    gap: 8,\n  },\n  stepContainer: {\n    gap: 8,\n    marginBottom: 8,\n  },\n  reactLogo: {\n    height: 178,\n    width: 290,\n    bottom: 0,\n    left: 0,\n    position: 'absolute',\n  },\n})\n"
  },
  {
    "path": "example/app/_layout.tsx",
    "content": "import { LogBox, StyleSheet } from 'react-native'\nimport { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'\nimport { Stack } from 'expo-router'\nimport { StatusBar } from 'expo-status-bar'\nimport { GestureHandlerRootView } from 'react-native-gesture-handler'\nimport 'react-native-reanimated'\n\nimport { useColorScheme } from '@/hooks/use-color-scheme'\n\nLogBox.ignoreLogs(['Open debugger to view warnings'])\n\nexport const unstable_settings = {\n  anchor: '(tabs)',\n}\n\nexport default function RootLayout () {\n  const colorScheme = useColorScheme()\n\n  return (\n    <GestureHandlerRootView style={styles.container}>\n      <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>\n        <Stack screenOptions={{ headerShown: false }}>\n          <Stack.Screen name='(tabs)' />\n          <Stack.Screen name='chat' />\n          <Stack.Screen name='modal' options={{ headerShown: true, presentation: 'modal', title: 'Modal' }} />\n        </Stack>\n        <StatusBar style='auto' />\n      </ThemeProvider>\n    </GestureHandlerRootView>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n})\n"
  },
  {
    "path": "example/app/chat/_layout.tsx",
    "content": "import { TouchableOpacity, Text, StyleSheet } from 'react-native'\nimport { Ionicons } from '@expo/vector-icons'\nimport { Stack, useRouter } from 'expo-router'\nimport { useSafeAreaInsets } from 'react-native-safe-area-context'\n\nexport default function ChatLayout () {\n  const insets = useSafeAreaInsets()\n  const router = useRouter()\n\n  return (\n    <Stack\n      screenOptions={{\n        headerShown: true,\n        contentStyle: { paddingBottom: insets.bottom, backgroundColor: '#fff' },\n        headerLeft: () => (\n          <TouchableOpacity onPress={() => router.back()} style={styles.backButton}>\n            <Ionicons name='chevron-back' size={24} color='#007AFF' />\n            <Text style={styles.backText}>Back</Text>\n          </TouchableOpacity>\n        ),\n      }}\n    >\n      <Stack.Screen\n        name='basic'\n        options={{ title: 'Basic Example' }}\n      />\n      <Stack.Screen\n        name='customized-rendering'\n        options={{ title: 'Customized Rendering' }}\n      />\n      <Stack.Screen\n        name='links'\n        options={{ title: 'Links & Patterns' }}\n      />\n      <Stack.Screen\n        name='reply'\n        options={{ title: 'Reply Example' }}\n      />\n      <Stack.Screen\n        name='slack'\n        options={{ title: 'Slack Style' }}\n      />\n    </Stack>\n  )\n}\n\nconst styles = StyleSheet.create({\n  backButton: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    marginLeft: -8,\n  },\n  backText: {\n    color: '#007AFF',\n    fontSize: 17,\n  },\n})\n"
  },
  {
    "path": "example/app/chat/basic.tsx",
    "content": "import BasicExample from '@/components/chat-examples/BasicExample'\n\nexport default BasicExample\n"
  },
  {
    "path": "example/app/chat/customized-rendering.tsx",
    "content": "import CustomizedRenderingExample from '@/components/chat-examples/CustomizedRenderingExample'\n\nexport default CustomizedRenderingExample\n"
  },
  {
    "path": "example/app/chat/links.tsx",
    "content": "import LinksExample from '@/components/chat-examples/LinksExample'\n\nexport default LinksExample\n"
  },
  {
    "path": "example/app/chat/reply.tsx",
    "content": "import ReplyExample from '@/components/chat-examples/ReplyExample'\n\nexport default ReplyExample\n"
  },
  {
    "path": "example/app/chat/slack.tsx",
    "content": "import SlackExample from '@/components/chat-examples/SlackExample'\n\nexport default SlackExample\n"
  },
  {
    "path": "example/app/modal.tsx",
    "content": "import { StyleSheet } from 'react-native'\nimport { Link } from 'expo-router'\n\nimport { ThemedText } from '@/components/themed-text'\nimport { ThemedView } from '@/components/themed-view'\n\nexport default function ModalScreen () {\n  return (\n    <ThemedView style={styles.container}>\n      <ThemedText type='title'>This is a modal</ThemedText>\n      <Link href='/' dismissTo style={styles.link}>\n        <ThemedText type='link'>Go to home screen</ThemedText>\n      </Link>\n    </ThemedView>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    alignItems: 'center',\n    justifyContent: 'center',\n    padding: 20,\n  },\n  link: {\n    marginTop: 15,\n    paddingVertical: 15,\n  },\n})\n"
  },
  {
    "path": "example/app.json",
    "content": "{\n  \"expo\": {\n    \"name\": \"example\",\n    \"slug\": \"example\",\n    \"version\": \"1.0.0\",\n    \"orientation\": \"portrait\",\n    \"icon\": \"./assets/images/icon.png\",\n    \"scheme\": \"example\",\n    \"userInterfaceStyle\": \"automatic\",\n    \"newArchEnabled\": true,\n    \"ios\": {\n      \"supportsTablet\": true,\n      \"bundleIdentifier\": \"com.anonymous.example\",\n      \"config\": {\n        \"googleMapsApiKey\": \"YOUR_GOOGLE_MAPS_API_KEY\"\n      }\n    },\n    \"android\": {\n      \"adaptiveIcon\": {\n        \"backgroundColor\": \"#E6F4FE\",\n        \"foregroundImage\": \"./assets/images/android-icon-foreground.png\",\n        \"backgroundImage\": \"./assets/images/android-icon-background.png\",\n        \"monochromeImage\": \"./assets/images/android-icon-monochrome.png\"\n      },\n      \"edgeToEdgeEnabled\": true,\n      \"predictiveBackGestureEnabled\": false,\n      \"package\": \"com.anonymous.example\",\n      \"config\": {\n        \"googleMaps\": {\n          \"apiKey\": \"YOUR_GOOGLE_MAPS_API_KEY\"\n        }\n      }\n    },\n    \"web\": {\n      \"output\": \"static\",\n      \"favicon\": \"./assets/images/favicon.png\"\n    },\n    \"plugins\": [\n      \"expo-router\",\n      [\n        \"expo-splash-screen\",\n        {\n          \"image\": \"./assets/images/splash-icon.png\",\n          \"imageWidth\": 200,\n          \"resizeMode\": \"contain\",\n          \"backgroundColor\": \"#ffffff\",\n          \"dark\": {\n            \"backgroundColor\": \"#000000\"\n          }\n        }\n      ]\n    ],\n    \"experiments\": {\n      \"typedRoutes\": true,\n      \"reactCompiler\": true\n    }\n  }\n}\n"
  },
  {
    "path": "example/babel.config.js",
    "content": "const fs = require('fs')\nconst path = require('path')\n\nconst root = path.resolve(__dirname, '..')\nconst rootPak = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))\n\nmodule.exports = function (api) {\n  api.cache(true)\n\n  return {\n    presets: ['babel-preset-expo'],\n    plugins: [\n      [\n        'module-resolver',\n        {\n          extensions: ['.tsx', '.ts', '.js', '.json'],\n          alias: {\n            // For development, we want to alias the library to the source\n            [rootPak.name]: path.join(root, rootPak.main),\n          },\n        },\n      ],\n      'react-native-worklets/plugin',\n    ],\n  }\n}\n"
  },
  {
    "path": "example/components/chat-examples/BasicExample.tsx",
    "content": "import React, { useCallback, useMemo, useState } from 'react'\nimport { StyleSheet, View, useColorScheme } from 'react-native'\nimport { GiftedChat, IMessage } from 'react-native-gifted-chat'\nimport AccessoryBar from '../../example-expo/AccessoryBar'\nimport CustomActions from '../../example-expo/CustomActions'\nimport CustomView from '../../example-expo/CustomView'\nimport earlierMessages from '../../example-expo/data/earlierMessages'\nimport messagesData from '../../example-expo/data/messages'\nimport { useKeyboardVerticalOffset } from '../../hooks/useKeyboardVerticalOffset'\nimport { getColorSchemeStyle } from '../../utils/styleUtils'\n\nexport default function BasicExample () {\n  const [messages, setMessages] = useState<IMessage[]>(messagesData)\n  const [isLoadingEarlier, setIsLoadingEarlier] = useState(false)\n  const [isTyping, setIsTyping] = useState(false)\n  const colorScheme = useColorScheme()\n\n  const keyboardVerticalOffset = useKeyboardVerticalOffset()\n\n  const user = useMemo(() => ({\n    _id: 1,\n    name: 'Developer',\n  }), [])\n\n  const onSend = useCallback((newMessages: IMessage[] = []) => {\n    const messagesWithIds = newMessages.map(msg => ({\n      ...msg,\n      _id: msg._id || Math.random().toString(36).substring(7),\n      user: msg.user || user,\n    }))\n    setMessages(previousMessages =>\n      GiftedChat.append(previousMessages, messagesWithIds)\n    )\n  }, [user])\n\n  const onPressLoadEarlierMessages = useCallback(() => {\n    setIsLoadingEarlier(true)\n    setTimeout(() => {\n      setMessages(previousMessages =>\n        GiftedChat.prepend(previousMessages, earlierMessages())\n      )\n      setIsLoadingEarlier(false)\n    }, 1500)\n  }, [])\n\n  const renderAccessory = useCallback(\n    () => <AccessoryBar onSend={onSend} isTyping={() => setIsTyping(isTyping => !isTyping)} user={user} />,\n    [onSend, user]\n  )\n\n  const renderCustomView = useCallback((props: any) => <CustomView {...props} />, [])\n\n  const renderActions = useCallback(\n    (props: any) => <CustomActions {...props} onSend={onSend} user={user} />,\n    [onSend, user]\n  )\n\n  return (\n    <View style={[styles.container, getColorSchemeStyle(styles, 'container', colorScheme)]}>\n      <GiftedChat\n        messages={messages}\n        onSend={onSend}\n        loadEarlierMessagesProps={{ isAvailable: true, isLoading: isLoadingEarlier, onPress: onPressLoadEarlierMessages }}\n        user={user}\n        renderActions={renderActions}\n        renderAccessory={renderAccessory}\n        renderCustomView={renderCustomView}\n        isTyping={isTyping}\n        messagesContainerStyle={getColorSchemeStyle(styles, 'messagesContainer', colorScheme)}\n        textInputProps={{\n          style: getColorSchemeStyle(styles, 'composer', colorScheme),\n        }}\n        messageTextProps={{\n          hashtag: 'twitter',\n        }}\n        keyboardAvoidingViewProps={{ keyboardVerticalOffset }}\n        isScrollToBottomEnabled\n      />\n    </View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: '#fff',\n  },\n  container_dark: {\n    backgroundColor: '#000',\n  },\n  messagesContainer_dark: {\n    backgroundColor: '#000',\n  },\n  composer_dark: {\n    backgroundColor: '#1a1a1a',\n    color: '#fff',\n  },\n})\n"
  },
  {
    "path": "example/components/chat-examples/CustomizedRenderingExample.tsx",
    "content": "import React from 'react'\nimport { StyleSheet, View, useColorScheme } from 'react-native'\nimport Chats from '../../example-gifted-chat/src/Chats'\nimport { getColorSchemeStyle } from '../../utils/styleUtils'\n\nexport default function CustomizedRenderingExample () {\n  const colorScheme = useColorScheme()\n\n  return (\n    <View style={[styles.container, getColorSchemeStyle(styles, 'container', colorScheme)]}>\n      <Chats />\n    </View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: '#fff',\n  },\n  container_dark: {\n    backgroundColor: '#000',\n  },\n})\n"
  },
  {
    "path": "example/components/chat-examples/LinksExample.tsx",
    "content": "import React, { useCallback, useMemo, useState } from 'react'\nimport { Linking, StyleSheet, Text, View } from 'react-native'\nimport { ActionSheetProvider, useActionSheet } from '@expo/react-native-action-sheet'\nimport { setStringAsync } from 'expo-clipboard'\nimport { isValidPhoneNumber, parsePhoneNumberWithError } from 'libphonenumber-js'\nimport { GiftedChat, IMessage, LinkMatcher } from 'react-native-gifted-chat'\nimport { useKeyboardVerticalOffset } from '../../hooks/useKeyboardVerticalOffset'\n\nconst LinksExample: React.FC = () => {\n  const { showActionSheetWithOptions } = useActionSheet()\n  const keyboardVerticalOffset = useKeyboardVerticalOffset()\n\n  const initialMessages: IMessage[] = useMemo(() => [\n    {\n      text: 'Welcome! 👋',\n      createdAt: new Date(),\n      user: {\n        _id: 2,\n        name: 'John Doe',\n      },\n    },\n    {\n      text: 'This example shows how GiftedChat handles different types of links in messages. Try tapping on any link!',\n      createdAt: new Date(Date.now() - 1 * 60000),\n      user: {\n        _id: 2,\n        name: 'John Doe',\n      },\n    },\n    {\n      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.',\n      createdAt: new Date(Date.now() - 2 * 60000),\n      user: {\n        _id: 2,\n        name: 'John Doe',\n      },\n    },\n    {\n      text: 'Email addresses are clickable: cool.guy@example.com or contact@reactnative.dev',\n      createdAt: new Date(Date.now() - 3 * 60000),\n      user: {\n        _id: 2,\n        name: 'John Doe',\n      },\n    },\n    {\n      text: 'Different link formats work:\\n• www.google.com\\n• google.com\\n• https://google.com',\n      createdAt: new Date(Date.now() - 4 * 60000),\n      user: {\n        _id: 2,\n        name: 'John Doe',\n      },\n    },\n    {\n      text: 'Use hashtags to categorize: #giftedchat #reactnative #opensource',\n      createdAt: new Date(Date.now() - 5 * 60000),\n      user: {\n        _id: 2,\n        name: 'John Doe',\n      },\n    },\n    {\n      text: 'You can mention people like @kesha-antonov or @john-doe',\n      createdAt: new Date(Date.now() - 6 * 60000),\n      user: {\n        _id: 2,\n        name: 'John Doe',\n      },\n    },\n    {\n      text: 'System message with link: Check out our documentation at https://github.com/FaridSafi/react-native-gifted-chat',\n      createdAt: new Date(Date.now() - 7 * 60000),\n      user: {\n        _id: 2,\n        name: 'John Doe',\n      },\n      system: true,\n    },\n    {\n      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.',\n      createdAt: new Date(Date.now() - 7 * 60000),\n      user: {\n        _id: 2,\n        name: 'John Doe',\n      },\n      system: true,\n    },\n  ].map((message, index) => ({\n    ...message,\n    _id: index + 1,\n  })).reverse(), [])\n\n  const [messages, setMessages] = useState<IMessage[]>(initialMessages)\n\n  const user = useMemo(() => ({\n    _id: 1,\n    name: 'Developer',\n  }), [])\n\n  const onSend = useCallback((newMessages: IMessage[] = []) => {\n    const messagesWithIds = newMessages.map(msg => ({\n      ...msg,\n      _id: msg._id || Math.random().toString(36).substring(7),\n      user: msg.user || user,\n    }))\n    setMessages(previousMessages =>\n      GiftedChat.append(previousMessages, messagesWithIds)\n    )\n  }, [user])\n\n  const getValidPhoneNumber = useCallback((text: string): string | undefined => {\n    const cleaned = text.replace(/[\\-\\(\\)\\s\\.]/g, '')\n\n    // Validate with libphonenumber-js\n    try {\n      // Try direct validation first\n      if (isValidPhoneNumber(cleaned))\n        return cleaned\n\n      // Try with RU region for local numbers\n      if (isValidPhoneNumber(cleaned, 'RU'))\n        return cleaned\n\n      // Try parsing to check validity\n      const phoneNumber = parsePhoneNumberWithError(cleaned, 'RU')\n      if (phoneNumber && phoneNumber.isValid())\n        return cleaned\n\n    } catch (error) {\n      console.warn('Invalid phone number:', error)\n    }\n\n    return undefined\n  }, [])\n\n  const handlePressPhoneNumber = useCallback((url: string) => {\n    if (!url)\n      return // Skip if validation failed\n\n    const options: {\n      title: string\n      action?: () => void\n    }[] = [\n      { title: 'Call', action: () => Linking.openURL(`tel:${url}`) },\n      { title: 'Send SMS', action: () => Linking.openURL(`sms:${url}`) },\n      { title: 'Copy Phone Number', action: () => setStringAsync(url) },\n      { title: 'Cancel' },\n    ]\n\n    showActionSheetWithOptions({\n      options: options.map(o => o.title),\n      cancelButtonIndex: options.length - 1,\n    }, (buttonIndex?: number) => {\n      if (buttonIndex === undefined)\n        return\n\n      const option = options[buttonIndex]\n      option.action?.()\n    })\n  }, [showActionSheetWithOptions])\n\n  const matchers = useMemo<LinkMatcher[]>(() => [\n    {\n      type: 'phone',\n      // Pattern that excludes numbers adjacent to underscores or part of filenames\n      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,\n      getLinkUrl: (matchedText: string): string => {\n        return getValidPhoneNumber(matchedText) || ''\n      },\n      getLinkText: (text: string): string => {\n        return text\n      },\n      renderLink: (text: string, url: string, index: number) => {\n        const validPhoneNumber = getValidPhoneNumber(text)\n        const isDisabled = !validPhoneNumber || !url\n\n        return (\n          <Text\n            key={index}\n            style={[styles.link, isDisabled && styles.linkDisabled]}\n            onPress={() => !isDisabled && handlePressPhoneNumber(url)}\n          >\n            {text}\n          </Text>\n        )\n      },\n    },\n  ], [getValidPhoneNumber, handlePressPhoneNumber])\n\n  return (\n    <ActionSheetProvider>\n      <View style={styles.container}>\n        <GiftedChat\n          messages={messages}\n          onSend={onSend}\n          user={user}\n          messageTextProps={{\n            phone: false,\n            url: true,\n            matchers,\n            mention: true,\n            hashtag: true,\n            mentionUrl: 'https://x.com',\n            hashtagUrl: 'https://x.com/hashtag',\n          }}\n          keyboardAvoidingViewProps={{ keyboardVerticalOffset }}\n          colorScheme='light'\n        />\n      </View>\n    </ActionSheetProvider>\n  )\n}\n\nconst ExampleContainer = () => (\n  <ActionSheetProvider>\n    <LinksExample />\n  </ActionSheetProvider>\n)\n\nexport default ExampleContainer\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  link: {\n    textDecorationLine: 'underline',\n  },\n  linkDisabled: {\n    textDecorationLine: 'none',\n  },\n})\n"
  },
  {
    "path": "example/components/chat-examples/ReplyExample.tsx",
    "content": "import React, { useCallback, useMemo, useState } from 'react'\nimport { StyleSheet, View, useColorScheme } from 'react-native'\nimport { GiftedChat, IMessage, ReplyMessage } from 'react-native-gifted-chat'\n\nimport messagesData from '../../example-expo/data/messages'\nimport { useKeyboardVerticalOffset } from '../../hooks/useKeyboardVerticalOffset'\nimport { getColorSchemeStyle } from '../../utils/styleUtils'\n\nexport interface IChatMessage extends IMessage {\n  replyMessage?: ReplyMessage\n}\n\nexport default function ReplyExample () {\n  const [messages, setMessages] = useState<IChatMessage[]>(messagesData)\n  const colorScheme = useColorScheme()\n  const keyboardVerticalOffset = useKeyboardVerticalOffset()\n\n  const user = useMemo(() => ({\n    _id: 1,\n    name: 'Developer',\n  }), [])\n\n  const onSend = useCallback((newMessages: IChatMessage[] = []) => {\n    const messagesWithIds = newMessages.map(msg => ({\n      ...msg,\n      _id: msg._id || Math.random().toString(36).substring(7),\n      user: msg.user || user,\n    }))\n    setMessages(previousMessages =>\n      GiftedChat.append(previousMessages, messagesWithIds)\n    )\n  }, [user])\n\n  const handlePressReply = useCallback((reply: ReplyMessage) => {\n    console.log('Pressed reply:', reply)\n    // Could scroll to the original message here\n  }, [])\n\n  return (\n    <View style={[styles.container, getColorSchemeStyle(styles, 'container', colorScheme)]}>\n      <GiftedChat<IChatMessage>\n        messages={messages}\n        onSend={onSend}\n        user={user}\n        messagesContainerStyle={getColorSchemeStyle(styles, 'messagesContainer', colorScheme)}\n        textInputProps={{\n          style: getColorSchemeStyle(styles, 'composer', colorScheme),\n        }}\n        // New grouped reply props\n        reply={{\n          swipe: {\n            isEnabled: true,\n            direction: 'left', // swipe left to reply\n          },\n          onPress: handlePressReply,\n        }}\n        keyboardAvoidingViewProps={{ keyboardVerticalOffset }}\n      />\n    </View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: '#fff',\n  },\n  container_dark: {\n    backgroundColor: '#000',\n  },\n  messagesContainer_dark: {\n    backgroundColor: '#000',\n  },\n  composer_dark: {\n    backgroundColor: '#1a1a1a',\n    color: '#fff',\n  },\n})\n"
  },
  {
    "path": "example/components/chat-examples/SlackExample.tsx",
    "content": "import React, { useCallback, useMemo, useState } from 'react'\nimport { StyleSheet, View, useColorScheme } from 'react-native'\nimport { GiftedChat, IMessage } from 'react-native-gifted-chat'\nimport messagesData from '../../example-expo/data/messages'\nimport SlackMessage from '../../example-slack-message/src/SlackMessage'\nimport { useKeyboardVerticalOffset } from '../../hooks/useKeyboardVerticalOffset'\nimport { getColorSchemeStyle } from '../../utils/styleUtils'\n\nexport default function SlackExample () {\n  const [messages, setMessages] = useState<IMessage[]>(messagesData)\n  const colorScheme = useColorScheme()\n  const keyboardVerticalOffset = useKeyboardVerticalOffset()\n\n  const user = useMemo(() => ({\n    _id: 1,\n    name: 'Developer',\n  }), [])\n\n  const onSend = useCallback((newMessages: IMessage[] = []) => {\n    const messagesWithIds = newMessages.map(msg => ({\n      ...msg,\n      _id: msg._id || Math.random().toString(36).substring(7),\n      user: msg.user || user,\n    }))\n    setMessages(previousMessages =>\n      GiftedChat.append(previousMessages, messagesWithIds)\n    )\n  }, [user])\n\n  return (\n    <View style={[styles.container, getColorSchemeStyle(styles, 'container', colorScheme)]}>\n      <GiftedChat\n        messages={messages}\n        onSend={onSend}\n        user={user}\n        renderMessage={props => <SlackMessage {...props} />}\n        messagesContainerStyle={getColorSchemeStyle(styles, 'messagesContainer', colorScheme)}\n        textInputProps={{\n          style: getColorSchemeStyle(styles, 'composer', colorScheme),\n        }}\n        keyboardAvoidingViewProps={{ keyboardVerticalOffset }}\n      />\n    </View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: '#fff',\n  },\n  container_dark: {\n    backgroundColor: '#000',\n  },\n  messagesContainer_dark: {\n    backgroundColor: '#000',\n  },\n  composer_dark: {\n    backgroundColor: '#1a1a1a',\n    color: '#fff',\n  },\n})\n"
  },
  {
    "path": "example/components/external-link.tsx",
    "content": "import { type ComponentProps } from 'react'\nimport { Href, Link } from 'expo-router'\nimport { openBrowserAsync, WebBrowserPresentationStyle } from 'expo-web-browser'\n\ntype Props = Omit<ComponentProps<typeof Link>, 'href'> & { href: Href & string }\n\nexport function ExternalLink ({ href, ...rest }: Props) {\n  return (\n    <Link\n      target='_blank'\n      {...rest}\n      href={href}\n      onPress={async event => {\n        console.log('ExternalLink pressed:', href)\n        if (process.env.EXPO_OS !== 'web') {\n          // Prevent the default behavior of linking to the default browser on native.\n          event.preventDefault()\n          // Open the link in an in-app browser.\n          await openBrowserAsync(href, {\n            presentationStyle: WebBrowserPresentationStyle.AUTOMATIC,\n          })\n        }\n      }}\n    />\n  )\n}\n"
  },
  {
    "path": "example/components/haptic-tab.tsx",
    "content": "import { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs'\nimport { PlatformPressable } from '@react-navigation/elements'\nimport * as Haptics from 'expo-haptics'\n\nexport function HapticTab (props: BottomTabBarButtonProps) {\n  return (\n    <PlatformPressable\n      {...props}\n      onPressIn={ev => {\n        if (process.env.EXPO_OS === 'ios')\n          // Add a soft haptic feedback when pressing down on the tabs.\n          Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)\n\n        props.onPressIn?.(ev)\n      }}\n    />\n  )\n}\n"
  },
  {
    "path": "example/components/hello-wave.tsx",
    "content": "import Animated from 'react-native-reanimated'\n\nexport function HelloWave () {\n  return (\n    <Animated.Text\n      style={{\n        fontSize: 28,\n        lineHeight: 32,\n        marginTop: -6,\n        animationName: {\n          '50%': { transform: [{ rotate: '25deg' }] },\n        },\n        animationIterationCount: 4,\n        animationDuration: '300ms',\n      }}\n    >\n      👋\n    </Animated.Text>\n  )\n}\n"
  },
  {
    "path": "example/components/parallax-scroll-view.tsx",
    "content": "import type { PropsWithChildren, ReactElement } from 'react'\nimport { StyleSheet } from 'react-native'\nimport Animated, {\n  interpolate,\n  useAnimatedRef,\n  useAnimatedStyle,\n  useScrollOffset,\n} from 'react-native-reanimated'\n\nimport { ThemedView } from '@/components/themed-view'\nimport { useColorScheme } from '@/hooks/use-color-scheme'\nimport { useThemeColor } from '@/hooks/use-theme-color'\n\nconst HEADER_HEIGHT = 250\n\ntype Props = PropsWithChildren<{\n  headerImage: ReactElement\n  headerBackgroundColor: { dark: string, light: string }\n}>\n\nexport default function ParallaxScrollView ({\n  children,\n  headerImage,\n  headerBackgroundColor,\n}: Props) {\n  const backgroundColor = useThemeColor({}, 'background')\n  const colorScheme = useColorScheme() ?? 'light'\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  const scrollRef = useAnimatedRef<Animated.ScrollView>()\n  const scrollOffset = useScrollOffset(scrollRef)\n  const headerAnimatedStyle = useAnimatedStyle(() => {\n    return {\n      transform: [\n        {\n          translateY: interpolate(\n            scrollOffset.value,\n            [-HEADER_HEIGHT, 0, HEADER_HEIGHT],\n            [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75]\n          ),\n        },\n        {\n          scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]),\n        },\n      ],\n    }\n  })\n\n  return (\n    <Animated.ScrollView\n      ref={scrollRef}\n      style={{ backgroundColor, flex: 1 }}\n      scrollEventThrottle={16}\n    >\n      <Animated.View\n        style={[\n          styles.header,\n          { backgroundColor: headerBackgroundColor[colorScheme] },\n          headerAnimatedStyle,\n        ]}\n      >\n        {headerImage}\n      </Animated.View>\n      <ThemedView style={styles.content}>{children}</ThemedView>\n    </Animated.ScrollView>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  header: {\n    height: HEADER_HEIGHT,\n    overflow: 'hidden',\n  },\n  content: {\n    flex: 1,\n    padding: 32,\n    gap: 16,\n    overflow: 'hidden',\n  },\n})\n"
  },
  {
    "path": "example/components/themed-text.tsx",
    "content": "import { StyleSheet, Text, type TextProps } from 'react-native'\n\nimport { useThemeColor } from '@/hooks/use-theme-color'\n\nexport type ThemedTextProps = TextProps & {\n  lightColor?: string\n  darkColor?: string\n  type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link'\n}\n\nexport function ThemedText ({\n  style,\n  lightColor,\n  darkColor,\n  type = 'default',\n  ...rest\n}: ThemedTextProps) {\n  const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text')\n\n  return (\n    <Text\n      style={[\n        { color },\n        type === 'default' ? styles.default : undefined,\n        type === 'title' ? styles.title : undefined,\n        type === 'defaultSemiBold' ? styles.defaultSemiBold : undefined,\n        type === 'subtitle' ? styles.subtitle : undefined,\n        type === 'link' ? styles.link : undefined,\n        style,\n      ]}\n      {...rest}\n    />\n  )\n}\n\nconst styles = StyleSheet.create({\n  default: {\n    fontSize: 16,\n    lineHeight: 24,\n  },\n  defaultSemiBold: {\n    fontSize: 16,\n    lineHeight: 24,\n    fontWeight: '600',\n  },\n  title: {\n    fontSize: 32,\n    fontWeight: 'bold',\n    lineHeight: 32,\n  },\n  subtitle: {\n    fontSize: 20,\n    fontWeight: 'bold',\n  },\n  link: {\n    lineHeight: 30,\n    fontSize: 16,\n    color: '#0a7ea4',\n  },\n})\n"
  },
  {
    "path": "example/components/themed-view.tsx",
    "content": "import { View, type ViewProps } from 'react-native'\n\nimport { useThemeColor } from '@/hooks/use-theme-color'\n\nexport type ThemedViewProps = ViewProps & {\n  lightColor?: string\n  darkColor?: string\n}\n\nexport function ThemedView ({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {\n  const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background')\n\n  return <View style={[{ backgroundColor }, style]} {...otherProps} />\n}\n"
  },
  {
    "path": "example/components/ui/collapsible.tsx",
    "content": "import { PropsWithChildren, useState } from 'react'\nimport { StyleSheet } from 'react-native'\nimport { RectButton } from 'react-native-gesture-handler'\n\nimport { ThemedText } from '@/components/themed-text'\nimport { ThemedView } from '@/components/themed-view'\nimport { IconSymbol } from '@/components/ui/icon-symbol'\nimport { Colors } from '@/constants/theme'\nimport { useColorScheme } from '@/hooks/use-color-scheme'\n\nexport function Collapsible ({ children, title }: PropsWithChildren & { title: string }) {\n  const [isOpen, setIsOpen] = useState(false)\n  const theme = useColorScheme() ?? 'light'\n\n  return (\n    <ThemedView>\n      <RectButton\n        style={styles.heading}\n        onPress={() => setIsOpen(value => !value)}\n      >\n        <IconSymbol\n          name='chevron.right'\n          size={18}\n          weight='medium'\n          color={theme === 'light' ? Colors.light.icon : Colors.dark.icon}\n          style={{ transform: [{ rotate: isOpen ? '90deg' : '0deg' }] }}\n        />\n\n        <ThemedText type='defaultSemiBold'>{title}</ThemedText>\n      </RectButton>\n      {isOpen && <ThemedView style={styles.content}>{children}</ThemedView>}\n    </ThemedView>\n  )\n}\n\nconst styles = StyleSheet.create({\n  heading: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    gap: 6,\n  },\n  content: {\n    marginTop: 6,\n    marginLeft: 24,\n  },\n})\n"
  },
  {
    "path": "example/components/ui/icon-symbol.ios.tsx",
    "content": "import { StyleProp, ViewStyle } from 'react-native'\nimport { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols'\n\nexport function IconSymbol ({\n  name,\n  size = 24,\n  color,\n  style,\n  weight = 'regular',\n}: {\n  name: SymbolViewProps['name']\n  size?: number\n  color: string\n  style?: StyleProp<ViewStyle>\n  weight?: SymbolWeight\n}) {\n  return (\n    <SymbolView\n      weight={weight}\n      tintColor={color}\n      resizeMode='scaleAspectFit'\n      name={name}\n      style={[\n        {\n          width: size,\n          height: size,\n        },\n        style,\n      ]}\n    />\n  )\n}\n"
  },
  {
    "path": "example/components/ui/icon-symbol.tsx",
    "content": "// Fallback for using MaterialIcons on Android and web.\n\nimport { ComponentProps } from 'react'\nimport { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native'\nimport MaterialIcons from '@expo/vector-icons/MaterialIcons'\nimport { SymbolWeight, SymbolViewProps } from 'expo-symbols'\n\ntype IconMapping = Record<SymbolViewProps['name'], ComponentProps<typeof MaterialIcons>['name']>\ntype IconSymbolName = keyof typeof MAPPING\n\n/**\n * Add your SF Symbols to Material Icons mappings here.\n * - see Material Icons in the [Icons Directory](https://icons.expo.fyi).\n * - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app.\n */\nconst MAPPING = {\n  'house.fill': 'home',\n  'paperplane.fill': 'send',\n  'chevron.left.forwardslash.chevron.right': 'code',\n  'chevron.right': 'chevron-right',\n} as IconMapping\n\n/**\n * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web.\n * This ensures a consistent look across platforms, and optimal resource usage.\n * Icon `name`s are based on SF Symbols and require manual mapping to Material Icons.\n */\nexport function IconSymbol ({\n  name,\n  size = 24,\n  color,\n  style,\n}: {\n  name: IconSymbolName\n  size?: number\n  color: string | OpaqueColorValue\n  style?: StyleProp<TextStyle>\n  weight?: SymbolWeight\n}) {\n  return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />\n}\n"
  },
  {
    "path": "example/constants/theme.ts",
    "content": "/**\n * Below are the colors that are used in the app. The colors are defined in the light and dark mode.\n * 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.\n */\n\nimport { Platform } from 'react-native'\n\nconst tintColorLight = '#0a7ea4'\nconst tintColorDark = '#fff'\n\nexport const Colors = {\n  light: {\n    text: '#11181C',\n    background: '#fff',\n    tint: tintColorLight,\n    icon: '#687076',\n    tabIconDefault: '#687076',\n    tabIconSelected: tintColorLight,\n  },\n  dark: {\n    text: '#ECEDEE',\n    background: '#151718',\n    tint: tintColorDark,\n    icon: '#9BA1A6',\n    tabIconDefault: '#9BA1A6',\n    tabIconSelected: tintColorDark,\n  },\n}\n\nexport const Fonts = Platform.select({\n  ios: {\n    /** iOS `UIFontDescriptorSystemDesignDefault` */\n    sans: 'system-ui',\n    /** iOS `UIFontDescriptorSystemDesignSerif` */\n    serif: 'ui-serif',\n    /** iOS `UIFontDescriptorSystemDesignRounded` */\n    rounded: 'ui-rounded',\n    /** iOS `UIFontDescriptorSystemDesignMonospaced` */\n    mono: 'ui-monospace',\n  },\n  default: {\n    sans: 'normal',\n    serif: 'serif',\n    rounded: 'normal',\n    mono: 'monospace',\n  },\n  web: {\n    sans: 'system-ui, -apple-system, BlinkMacSystemFont, \\'Segoe UI\\', Roboto, Helvetica, Arial, sans-serif',\n    serif: 'Georgia, \\'Times New Roman\\', serif',\n    rounded: '\\'SF Pro Rounded\\', \\'Hiragino Maru Gothic ProN\\', Meiryo, \\'MS PGothic\\', sans-serif',\n    mono: 'SFMono-Regular, Menlo, Monaco, Consolas, \\'Liberation Mono\\', \\'Courier New\\', monospace',\n  },\n})\n"
  },
  {
    "path": "example/eslint.config.js",
    "content": "import stylistic from '@stylistic/eslint-plugin'\nimport typescriptEslint from '@typescript-eslint/eslint-plugin'\nimport typescriptParser from '@typescript-eslint/parser'\nimport importPlugin from 'eslint-plugin-import'\nimport jestPlugin from 'eslint-plugin-jest'\nimport perfectionistPlugin from 'eslint-plugin-perfectionist'\nimport react from 'eslint-plugin-react'\nimport reactHooks from 'eslint-plugin-react-hooks'\n\nexport default [\n  {\n    ignores: ['**/node_modules/**', '**/lib/**', '**/build/**', '**/.expo/**', '**/android/**', '**/ios/**'],\n  },\n  {\n    files: ['**/*.{js,jsx,ts,tsx}'],\n    languageOptions: {\n      ecmaVersion: 'latest',\n      sourceType: 'module',\n      parser: typescriptParser,\n      parserOptions: {\n        ecmaFeatures: {\n          jsx: true,\n        },\n      },\n      globals: {\n        fetch: 'readonly',\n        navigator: 'readonly',\n        __DEV__: 'readonly',\n        XMLHttpRequest: 'readonly',\n        FormData: 'readonly',\n        React$Element: 'readonly',\n        requestAnimationFrame: 'readonly',\n\n        // Node.js globals for build scripts and configuration files\n        require: 'readonly',\n        module: 'readonly',\n        process: 'readonly',\n        global: 'readonly',\n        console: 'readonly',\n        setTimeout: 'readonly',\n        clearTimeout: 'readonly',\n        setInterval: 'readonly',\n        clearInterval: 'readonly',\n\n        // Jest globals\n        describe: 'readonly',\n        test: 'readonly',\n        it: 'readonly',\n        jest: 'readonly',\n        expect: 'readonly',\n        beforeAll: 'readonly',\n        beforeEach: 'readonly',\n        afterAll: 'readonly',\n        afterEach: 'readonly',\n      },\n    },\n    plugins: {\n      '@stylistic': stylistic,\n      '@typescript-eslint': typescriptEslint,\n      'import': importPlugin,\n      'perfectionist': perfectionistPlugin,\n      'react': react,\n      'react-hooks': reactHooks,\n    },\n    settings: {\n      react: {\n        version: 'detect',\n      },\n    },\n    rules: {\n      // React rules\n      'react/react-in-jsx-scope': 'off',\n      'react/no-unknown-property': 'off',\n      'react/display-name': 'off',\n      'react/prop-types': 'off',\n\n      // React Hooks\n      'react-hooks/rules-of-hooks': 'error',\n      'react-hooks/exhaustive-deps': [\n        'warn',\n        {\n          additionalHooks:\n            '(useAnimatedStyle|useSharedValue|useAnimatedGestureHandler|useAnimatedScrollHandler|useAnimatedProps|useDerivedValue|useAnimatedRef|useAnimatedReact|useAnimatedReaction|useCallbackDebounced|useCallbackThrottled)',\n        },\n      ],\n\n      // TypeScript rules\n      '@typescript-eslint/no-explicit-any': 'off',\n      '@typescript-eslint/no-unused-vars': ['error'],\n\n      // Stylistic rules\n      '@stylistic/semi': ['error', 'never'],\n      '@stylistic/member-delimiter-style': [\n        'error',\n        {\n          multiline: {\n            delimiter: 'none',\n            requireLast: true,\n          },\n          singleline: {\n            delimiter: 'comma',\n            requireLast: false,\n          },\n        },\n      ],\n      '@stylistic/indent': [\n        'error',\n        2,\n        {\n          SwitchCase: 1,\n          VariableDeclarator: 'first',\n          ignoredNodes: ['TemplateLiteral'],\n        },\n      ],\n      '@stylistic/quotes': ['error', 'single'],\n      '@stylistic/jsx-quotes': ['error', 'prefer-single'],\n      '@stylistic/comma-dangle': [\n        'error',\n        {\n          arrays: 'always-multiline',\n          objects: 'always-multiline',\n          imports: 'always-multiline',\n          exports: 'never',\n          functions: 'never',\n        },\n      ],\n      '@stylistic/arrow-parens': ['error', 'as-needed'],\n      '@stylistic/template-curly-spacing': 'off',\n      '@stylistic/linebreak-style': ['off', 'unix'],\n      '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: false }],\n      '@stylistic/jsx-closing-bracket-location': ['error', 'line-aligned'],\n\n      // General rules\n      'no-func-assign': 'off',\n      'no-class-assign': 'off',\n      'no-useless-escape': 'off',\n      'no-unused-vars': 'off', // Use @typescript-eslint/no-unused-vars instead\n      'no-unreachable': 'error',\n      'curly': [2, 'multi', 'consistent'],\n      'nonblock-statement-body-position': ['error', 'below'],\n\n      // Perfectionist rules\n      'perfectionist/sort-imports': [\n        'error',\n        {\n          groups: [\n            'react',\n            'external',\n            'internal',\n            ['parent', 'sibling'],\n            'index',\n          ],\n          customGroups: {\n            value: {\n              react: ['^react$', '^react-native$'],\n            },\n          },\n          newlinesBetween: 'ignore',\n        },\n      ],\n      'perfectionist/sort-interfaces': 'off',\n    },\n  },\n  {\n    files: ['tests/**/*', 'src/__tests__/**/*'],\n    plugins: {\n      jest: jestPlugin,\n    },\n    rules: {\n      ...jestPlugin.configs.recommended.rules,\n    },\n  },\n]\n"
  },
  {
    "path": "example/example-expo/AccessoryBar.tsx",
    "content": "import React from 'react'\nimport { StyleSheet, View, useColorScheme } from 'react-native'\nimport { MaterialIcons } from '@expo/vector-icons'\nimport { RectButton } from 'react-native-gesture-handler'\n\nimport { IMessage, User } from '../../src'\nimport {\n  getLocationAsync,\n  pickImageAsync,\n  takePictureAsync,\n} from './mediaUtils'\n\nexport default function AccessoryBar ({ onSend, isTyping, user }: { onSend: (messages: IMessage[]) => void, isTyping: () => void, user: User }) {\n  const colorScheme = useColorScheme()\n  const isDark = colorScheme === 'dark'\n\n  const handlePickImage = async () => {\n    const images = await pickImageAsync()\n    if (!images)\n      return\n\n    const messages: IMessage[] = images.map(image => ({\n      _id: Math.random().toString(36).substring(7),\n      image,\n      text: '',\n      createdAt: new Date(),\n      user,\n    }))\n    onSend(messages)\n  }\n\n  const handleTakePicture = async () => {\n    const images = await takePictureAsync()\n    if (!images)\n      return\n\n    const messages: IMessage[] = images.map(image => ({\n      _id: Math.random().toString(36).substring(7),\n      image,\n      text: '',\n      createdAt: new Date(),\n      user,\n    }))\n    onSend(messages)\n  }\n\n  const handleSendLocation = async () => {\n    const location = await getLocationAsync()\n    if (!location)\n      return\n\n    const message: IMessage = {\n      _id: Math.random().toString(36).substring(7),\n      location,\n      text: '',\n      createdAt: new Date(),\n      user,\n    }\n    onSend([message])\n  }\n\n  const containerColorStyle = colorScheme === 'dark' ? styles.container_dark : {}\n\n  return (\n    <View style={[styles.container, containerColorStyle]}>\n      <Button\n        onPress={handlePickImage}\n        name='photo'\n        color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.5)'}\n      />\n      <Button\n        onPress={handleTakePicture}\n        name='camera'\n        color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.5)'}\n      />\n      <Button\n        onPress={handleSendLocation}\n        name='my-location'\n        color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.5)'}\n      />\n      <Button\n        onPress={() => {\n          isTyping()\n        }}\n        name='chat'\n        color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.5)'}\n      />\n    </View>\n  )\n}\n\nconst Button = ({\n  onPress,\n  size = 30,\n  color = 'rgba(0,0,0,0.5)',\n  name,\n}: {\n  onPress: () => void\n  size?: number\n  color?: string\n  name: React.ComponentProps<typeof MaterialIcons>['name']\n}) => (\n  <RectButton onPress={onPress}>\n    <MaterialIcons size={size} color={color} name={name} />\n  </RectButton>\n)\n\nconst styles = StyleSheet.create({\n  container: {\n    backgroundColor: 'white',\n    flexDirection: 'row',\n    justifyContent: 'space-around',\n    alignItems: 'center',\n    borderTopWidth: StyleSheet.hairlineWidth,\n    borderTopColor: 'rgba(0,0,0,0.3)',\n    paddingVertical: 5,\n  },\n  container_dark: {\n    backgroundColor: '#1a1a1a',\n    borderTopColor: 'rgba(255,255,255,0.2)',\n  },\n})\n"
  },
  {
    "path": "example/example-expo/CustomActions.tsx",
    "content": "import React, { useCallback } from 'react'\nimport {\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n  StyleSheet,\n  Text,\n  View,\n  useColorScheme,\n} from 'react-native'\nimport { useActionSheet } from '@expo/react-native-action-sheet'\nimport { RectButton } from 'react-native-gesture-handler'\nimport { IMessage, User } from '../../src'\nimport {\n  getLocationAsync,\n  pickImageAsync,\n  takePictureAsync,\n} from './mediaUtils'\n\ninterface Props {\n  renderIcon?: () => React.ReactNode\n  wrapperStyle?: StyleProp<ViewStyle>\n  containerStyle?: StyleProp<ViewStyle>\n  iconTextStyle?: StyleProp<TextStyle>\n  onSend: (messages: IMessage[]) => void\n  user: User\n}\n\nconst CustomActions = ({\n  renderIcon,\n  iconTextStyle,\n  containerStyle,\n  wrapperStyle,\n  onSend,\n  user,\n}: Props) => {\n  const { showActionSheetWithOptions } = useActionSheet()\n  const colorScheme = useColorScheme()\n\n  const handlePickImage = useCallback(async () => {\n    const images = await pickImageAsync()\n    if (!images)\n      return\n\n    const messages: IMessage[] = images.map(image => ({\n      _id: Math.random().toString(36).substring(7),\n      image,\n      text: '',\n      createdAt: new Date(),\n      user,\n    }))\n    onSend(messages)\n  }, [onSend, user])\n\n  const handleTakePicture = useCallback(async () => {\n    const images = await takePictureAsync()\n    if (!images)\n      return\n\n    const messages: IMessage[] = images.map(image => ({\n      _id: Math.random().toString(36).substring(7),\n      image,\n      text: '',\n      createdAt: new Date(),\n      user,\n    }))\n    onSend(messages)\n  }, [onSend, user])\n\n  const handleSendLocation = useCallback(async () => {\n    const location = await getLocationAsync()\n    if (!location)\n      return\n\n    const message: IMessage = {\n      _id: Math.random().toString(36).substring(7),\n      location,\n      text: '',\n      createdAt: new Date(),\n      user,\n    }\n    onSend([message])\n  }, [onSend, user])\n\n  const onActionsPress = useCallback(() => {\n    const options: { title: string, action?: () => Promise<void> }[] = [\n      { title: 'Choose From Library', action: handlePickImage },\n      { title: 'Take Picture', action: handleTakePicture },\n      { title: 'Send Location', action: handleSendLocation },\n      { title: 'Cancel' },\n    ]\n    const cancelButtonIndex = options.length - 1\n\n    showActionSheetWithOptions(\n      {\n        options: options.map(o => o.title),\n        cancelButtonIndex,\n      },\n      async buttonIndex => {\n        if (buttonIndex !== undefined) {\n          const selectedOption = options[buttonIndex]\n          selectedOption?.action?.()\n        }\n      }\n    )\n  }, [showActionSheetWithOptions, handlePickImage, handleTakePicture, handleSendLocation])\n\n  const renderIconComponent = useCallback(() => {\n    if (renderIcon)\n      return renderIcon()\n\n    const wrapperColorStyle = colorScheme === 'dark' ? styles.wrapper_dark : {}\n    const iconTextColorStyle = colorScheme === 'dark' ? styles.iconText_dark : {}\n\n    return (\n      <View style={[styles.wrapper, wrapperColorStyle, wrapperStyle]}>\n        <Text style={[styles.iconText, iconTextColorStyle, iconTextStyle]}>+</Text>\n      </View>\n    )\n  }, [renderIcon, wrapperStyle, iconTextStyle, colorScheme])\n\n  return (\n    <RectButton\n      style={[styles.container, containerStyle]}\n      onPress={onActionsPress}\n    >\n      {renderIconComponent()}\n    </RectButton>\n  )\n}\n\nexport default CustomActions\n\nconst styles = StyleSheet.create({\n  container: {\n    paddingLeft: 8,\n    paddingRight: 4,\n    paddingVertical: 7,\n  },\n  wrapper: {\n    width: 26,\n    height: 26,\n    borderRadius: 13,\n    borderColor: '#b2b2b2',\n    borderWidth: 2,\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  wrapper_dark: {\n    borderColor: '#666',\n  },\n  iconText: {\n    color: '#b2b2b2',\n    fontWeight: 'bold',\n    fontSize: 16,\n    lineHeight: 16,\n    backgroundColor: 'transparent',\n    textAlign: 'center',\n  },\n  iconText_dark: {\n    color: '#999',\n  },\n})\n"
  },
  {
    "path": "example/example-expo/CustomView/index.tsx",
    "content": "import React, { useCallback } from 'react'\nimport { Platform, View, Text } from 'react-native'\nimport Constants from 'expo-constants'\nimport * as Linking from 'expo-linking'\nimport { RectButton } from 'react-native-gesture-handler'\nimport MapView from 'react-native-maps'\nimport commonStyles from '../../styles'\nimport styles from './styles'\nimport type { CustomViewProps } from './types'\n\nconst CustomView = ({\n  currentMessage,\n  containerStyle,\n  mapViewStyle,\n}: CustomViewProps) => {\n  const openMapAsync = useCallback(async () => {\n    if (Platform.OS === 'web') {\n      alert('Opening the map is not supported.')\n      return\n    }\n\n    const { location } = currentMessage\n\n    const url = Platform.select({\n      ios: `http://maps.apple.com/?ll=${location.latitude},${location.longitude}`,\n      default: `http://maps.google.com/?q=${location.latitude},${location.longitude}`,\n    })\n\n    try {\n      const supported = await Linking.canOpenURL(url)\n      if (supported)\n        return Linking.openURL(url)\n\n      alert('Opening the map is not supported.')\n    } catch (e) {\n      alert(e.message)\n    }\n  }, [currentMessage])\n\n  if (currentMessage.location) {\n    // Check if Google Maps API key is configured for Android\n    const androidApiKey = Constants.expoConfig?.android?.config?.googleMaps?.apiKey\n    const hasAndroidApiKey = androidApiKey && androidApiKey !== 'YOUR_GOOGLE_MAPS_API_KEY'\n    const shouldShowPlaceholder = Platform.OS === 'android' && !hasAndroidApiKey\n\n    // Use native MapView for iOS or Android with API key\n    return (\n      <RectButton\n        style={containerStyle}\n        onPress={openMapAsync}\n      >\n        {\n          shouldShowPlaceholder\n            ? (\n              <View style={[commonStyles.center, styles.mapView, mapViewStyle]}>\n                <Text style={commonStyles.textCenter}>Google Maps API key is not configured.</Text>\n              </View>\n            ) : (\n              <MapView\n                style={[styles.mapView, mapViewStyle]}\n                region={{\n                  latitude: currentMessage.location.latitude,\n                  longitude: currentMessage.location.longitude,\n                  latitudeDelta: 0.0922,\n                  longitudeDelta: 0.0421,\n                }}\n                scrollEnabled={false}\n                zoomEnabled={false}\n              />\n            )\n        }\n      </RectButton>\n    )\n  }\n\n  return null\n}\n\nexport default CustomView\n"
  },
  {
    "path": "example/example-expo/CustomView/index.web.tsx",
    "content": "import React, { useCallback } from 'react'\nimport {\n  View,\n  Text,\n} from 'react-native'\nimport { RectButton } from 'react-native-gesture-handler'\nimport styles from './styles'\nimport type { CustomViewProps } from './types'\n\nconst CustomView = ({\n  currentMessage,\n  containerStyle,\n}: CustomViewProps) => {\n  const openMapAsync = useCallback(async () => {\n    alert('Opening the map is not supported.')\n  }, [])\n\n  if (currentMessage.location)\n    return (\n      <RectButton onPress={openMapAsync}>\n        <View style={[styles.mapView, containerStyle]}>\n          <Text style={styles.text}>\n            Maps are not supported on web, sorry!\n          </Text>\n        </View>\n      </RectButton>\n    )\n\n  return null\n}\n\nexport default CustomView\n"
  },
  {
    "path": "example/example-expo/CustomView/styles.ts",
    "content": "import { StyleSheet } from 'react-native'\n\nexport default StyleSheet.create({\n  mapView: {\n    width: 150,\n    height: 100,\n    borderRadius: 13,\n    alignItems: 'center',\n    justifyContent: 'center',\n    margin: 3,\n  },\n\n  text: {\n    color: 'tomato',\n    fontWeight: 'bold',\n  },\n})\n"
  },
  {
    "path": "example/example-expo/CustomView/types.ts",
    "content": "import { StyleProp, ViewStyle } from 'react-native'\nimport { IMessage } from '../../../src'\n\nexport interface CustomViewProps {\n  currentMessage: IMessage\n  containerStyle?: StyleProp<ViewStyle>\n  mapViewStyle?: StyleProp<ViewStyle>\n}\n"
  },
  {
    "path": "example/example-expo/data/earlierMessages.ts",
    "content": "import dayjs from 'dayjs'\nimport { IMessage } from 'react-native-gifted-chat'\n\nconst date = dayjs().subtract(1, 'year')\n\nexport default (): IMessage[] => [\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text:\n      'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text:\n      'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text:\n      'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text:\n      'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text: 'React Native lets you build mobile apps using only JavaScript',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text: 'React Native lets you build mobile apps using only JavaScript',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text: 'React Native lets you build mobile apps using only JavaScript',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text: 'React Native lets you build mobile apps using only JavaScript',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text: 'React Native lets you build mobile apps using only JavaScript',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text: 'React Native lets you build mobile apps using only JavaScript',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text: 'React Native lets you build mobile apps using only JavaScript',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text: 'React Native lets you build mobile apps using only JavaScript',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text: 'React Native lets you build mobile apps using only JavaScript',\n    createdAt: date.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    _id: Math.round(Math.random() * 1000000),\n    text: 'This is a system message.',\n    createdAt: date.toDate(),\n    system: true,\n    user: {\n      _id: 0,\n    },\n  },\n]\n"
  },
  {
    "path": "example/example-expo/data/messages.ts",
    "content": "import dayjs from 'dayjs'\nimport { IMessage } from 'react-native-gifted-chat'\n\nconst date1 = dayjs()\nconst date2 = date1.clone().subtract(1, 'day')\nconst date3 = date2.clone().subtract(1, 'week')\n\nconst messages: IMessage[] = [\n  {\n    text: '',\n    createdAt: date3.toDate(),\n    audio:\n      'https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3',\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n  },\n  {\n    text: '',\n    createdAt: date3.toDate(),\n    video: 'https://media.giphy.com/media/3o6ZthZjk09Xx4ktZ6/giphy.mp4',\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n  },\n  {\n    text: 'This is a quick reply. Do you love Gifted Chat? (checkbox)',\n    createdAt: date3.toDate(),\n    quickReplies: {\n      type: 'checkbox', // or 'checkbox',\n      values: [\n        {\n          title: 'Yes',\n          value: 'yes',\n        },\n        {\n          title: 'Yes, let me show you with a picture!',\n          value: 'yes_picture',\n        },\n        {\n          title: 'Nope. What?',\n          value: 'no',\n        },\n      ],\n    },\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n  },\n  {\n    text: 'This is a quick reply. Do you love Gifted Chat? (radio) KEEP IT',\n    createdAt: date3.toDate(),\n    quickReplies: {\n      type: 'radio', // or 'checkbox',\n      keepIt: true,\n      values: [\n        {\n          title: '😋 Yes',\n          value: 'yes',\n        },\n        {\n          title:\n            '📷 Yes, let me show you with a picture!',\n          value: 'yes_picture',\n        },\n        {\n          title: '😞 Nope. What?',\n          value: 'no',\n        },\n      ],\n    },\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n  },\n  {\n    text: 'Are you building a chat app?',\n    createdAt: date3.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    text: 'Yes, and I use #GiftedChat!',\n    createdAt: date3.toDate(),\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n    sent: true,\n    received: true,\n  },\n  {\n    text: 'Where are you?',\n    createdAt: date3.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    text: '',\n    createdAt: date2.toDate(),\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n    sent: true,\n    received: true,\n    location: {\n      latitude: 48.864601,\n      longitude: 2.398704,\n    },\n  },\n  {\n    text: 'Send me a picture!',\n    createdAt: date2.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    text: 'Paris',\n    createdAt: date2.toDate(),\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n    image:\n      'https://static.vecteezy.com/system/resources/thumbnails/003/407/768/small/eiffel-tower-at-paris-france-free-photo.jpg',\n    sent: true,\n    received: true,\n  },\n  {\n    text: '#awesome',\n    createdAt: date1.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    text: '#awesome 2',\n    createdAt: date1.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n    sent: true,\n    received: true,\n  },\n  {\n    text: '#awesome 3',\n    createdAt: date1.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n    sent: true,\n    received: true,\n  },\n  {\n    text: 'Hi',\n    createdAt: date1.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n    sent: true,\n    received: true,\n  },\n  {\n    text: '👋',\n    createdAt: date1.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n    sent: true,\n    received: true,\n  },\n].map((message, index) => ({\n  ...message,\n  _id: index + 1,\n})).reverse()\n\nexport default messages\n"
  },
  {
    "path": "example/example-expo/mediaUtils.ts",
    "content": "import * as ImagePicker from 'expo-image-picker'\n\nimport { getCurrentPositionAsync, LocationObjectCoords, requestForegroundPermissionsAsync } from 'expo-location'\n\nexport async function getLocationAsync (): Promise<LocationObjectCoords | undefined> {\n  const response = await requestForegroundPermissionsAsync()\n  if (!response.granted)\n    return\n\n  const location = await getCurrentPositionAsync()\n  if (!location)\n    return\n\n  return location.coords\n}\n\nexport async function pickImageAsync (): Promise<string[] | undefined> {\n  const response = await ImagePicker.requestMediaLibraryPermissionsAsync()\n  if (!response.granted)\n    return\n\n  const result = await ImagePicker.launchImageLibraryAsync({\n    allowsEditing: true,\n    aspect: [4, 3],\n  })\n\n  if (result.canceled)\n    return\n\n  return result.assets.map(({ uri }) => uri)\n}\n\nexport async function takePictureAsync (): Promise<string[] | undefined> {\n  const response = await ImagePicker.requestCameraPermissionsAsync()\n  if (!response.granted)\n    return\n\n  const result = await ImagePicker.launchCameraAsync({\n    allowsEditing: true,\n    aspect: [4, 3],\n  })\n\n  if (result.canceled)\n    return\n\n  return result.assets.map(({ uri }) => uri)\n}\n"
  },
  {
    "path": "example/example-gifted-chat/README.md",
    "content": "# example-gifted-chat\n\nLots of people using `react-native-gifted-chat` might want to know that...\n\n1. There are so many render props could use, but what should I pass?\n2. How could I customize each component, but leaving its functionality.\n\n> 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.\n\n---\n\nI made this for anyone who wants to know how to use the render Props properly.\n\n##### Such as:\n\n- renderInputToolbar\n- renderActions\n- renderComposer\n- renderSend\n- renderAvatar\n- renderBubble\n- renderSystemMessage\n- renderMessage\n- renderMessageText\n- renderCustomView\n\n<img src=\"https://i.imgur.com/dbkc7I4.png\" alt=\"Example for customize components\" width=\"300\">"
  },
  {
    "path": "example/example-gifted-chat/src/Chats.tsx",
    "content": "import React, { useState, useEffect } from 'react'\nimport { useColorScheme } from 'react-native'\nimport { GiftedChat, IMessage } from 'react-native-gifted-chat'\nimport { useKeyboardVerticalOffset } from '../../hooks/useKeyboardVerticalOffset'\nimport {\n  renderAvatar,\n  renderBubble,\n  renderSystemMessage,\n  renderMessage,\n  renderMessageText,\n  renderCustomView,\n} from './customComponents'\nimport { RenderInputToolbar, RenderActions, RenderComposer, RenderSend } from './InputToolbar'\nimport initialMessages from './messages'\n\nconst Chats = () => {\n  const [text, setText] = useState('')\n  const [messages, setMessages] = useState<IMessage[]>([])\n  const colorScheme = useColorScheme()\n  const isDark = colorScheme === 'dark'\n  const keyboardVerticalOffset = useKeyboardVerticalOffset()\n\n  useEffect(() => {\n    setMessages(initialMessages.reverse())\n  }, [])\n\n  const onSend = (newMessages: IMessage[] = []) => {\n    setMessages(prevMessages => GiftedChat.append(prevMessages, newMessages))\n  }\n\n  return (\n    <GiftedChat\n      messages={messages}\n      text={text}\n      onSend={onSend}\n      user={{\n        _id: 1,\n        name: 'Aaron',\n        avatar: 'https://placeimg.com/150/150/any',\n      }}\n      isAlignedTop\n      isSendButtonAlwaysVisible\n      isScrollToBottomEnabled\n      // isUserAvatarVisible\n      isAvatarOnTop\n      isUsernameVisible\n      onPressAvatar={console.log}\n      renderInputToolbar={RenderInputToolbar}\n      renderActions={RenderActions}\n      renderComposer={RenderComposer}\n      renderSend={RenderSend}\n      renderAvatar={renderAvatar}\n      renderBubble={renderBubble}\n      renderSystemMessage={renderSystemMessage}\n      renderMessage={renderMessage}\n      renderMessageText={renderMessageText}\n      // renderMessageImage\n      renderCustomView={renderCustomView}\n      isCustomViewBottom\n      messagesContainerStyle={{ backgroundColor: isDark ? '#1a1a1a' : 'indigo' }}\n      textInputProps={{\n        style: isDark && { backgroundColor: '#2a2a2a', color: '#fff' },\n        onChangeText: setText,\n      }}\n      keyboardAvoidingViewProps={{ keyboardVerticalOffset }}\n    />\n  )\n}\n\nexport default Chats\n"
  },
  {
    "path": "example/example-gifted-chat/src/InputToolbar.tsx",
    "content": "import React from 'react'\nimport { Image, useColorScheme } from 'react-native'\nimport {\n  InputToolbar,\n  Actions,\n  Composer,\n  Send,\n  IMessage,\n  InputToolbarProps,\n  ActionsProps,\n  ComposerProps,\n  SendProps,\n} from 'react-native-gifted-chat'\n\n// These are React components (not render functions) so they can use hooks\nexport const RenderInputToolbar = React.memo((props: InputToolbarProps<IMessage>) => {\n  const colorScheme = useColorScheme()\n  const isDark = colorScheme === 'dark'\n\n  return (\n    <InputToolbar\n      {...props}\n      containerStyle={{\n        backgroundColor: isDark ? '#1a1a1a' : '#222B45',\n        paddingTop: 6,\n      }}\n      primaryStyle={{ alignItems: 'center' }}\n    />\n  )\n})\n\nexport const RenderActions = React.memo((props: ActionsProps) => {\n  const colorScheme = useColorScheme()\n  const isDark = colorScheme === 'dark'\n\n  return (\n    <Actions\n      {...props}\n      containerStyle={{\n        width: 44,\n        height: 44,\n        alignItems: 'center',\n        justifyContent: 'center',\n        marginLeft: 4,\n        marginRight: 4,\n        marginBottom: 0,\n      }}\n      icon={() => (\n        <Image\n          style={{ width: 32, height: 32 }}\n          source={{\n            uri: 'https://placeimg.com/32/32/any',\n          }}\n        />\n      )}\n      options={{\n        'Choose From Library': () => {\n          console.log('Choose From Library')\n        },\n        Cancel: () => {\n          console.log('Cancel')\n        },\n      }}\n      optionTintColor={isDark ? '#ffffff' : '#222B45'}\n    />\n  )\n})\n\nexport const RenderComposer = React.memo((props: ComposerProps) => {\n  const colorScheme = useColorScheme()\n  const isDark = colorScheme === 'dark'\n\n  return (\n    <Composer\n      {...props}\n      textInputProps={{\n        style: {\n          color: isDark ? '#ffffff' : '#222B45',\n          backgroundColor: isDark ? '#2a2a2a' : '#EDF1F7',\n          borderWidth: 1,\n          borderRadius: 5,\n          borderColor: isDark ? '#3a3a3a' : '#E4E9F2',\n          paddingTop: 8.5,\n          paddingHorizontal: 12,\n          marginLeft: 0,\n        },\n        placeholderTextColor: isDark ? '#888' : undefined,\n      }}\n    />\n  )\n})\n\nexport const RenderSend = React.memo((props: SendProps<IMessage>) => (\n  <Send\n    {...props}\n    isDisabled={!props.text}\n    containerStyle={{\n      width: 44,\n      height: 44,\n      alignItems: 'center',\n      justifyContent: 'center',\n      marginHorizontal: 4,\n    }}\n  >\n    <Image\n      style={{ width: 32, height: 32 }}\n      source={{\n        uri: 'https://placeimg.com/32/32/any',\n      }}\n    />\n  </Send>\n))\n"
  },
  {
    "path": "example/example-gifted-chat/src/customComponents.tsx",
    "content": "import React from 'react'\nimport { View, Text } from 'react-native'\nimport {\n  Avatar,\n  Bubble,\n  SystemMessage,\n  Message,\n  MessageText,\n  IMessage,\n  User,\n  AvatarProps,\n  BubbleProps,\n  SystemMessageProps,\n  MessageProps,\n  MessageTextProps,\n} from 'react-native-gifted-chat'\n\nexport const renderAvatar = (props: AvatarProps<IMessage>) => (\n  <Avatar\n    {...props}\n    containerStyle={{ left: { borderWidth: 3, borderColor: 'red' } }}\n    imageStyle={{ left: { borderWidth: 3, borderColor: 'blue' } }}\n  />\n)\n\nexport const renderBubble = (props: BubbleProps<IMessage>) => (\n  <Bubble\n    {...props}\n    // renderTime={() => <Text>Time</Text>}\n    // renderTicks={() => <Text>Ticks</Text>}\n    containerStyle={{\n      left: { borderColor: 'teal', borderWidth: 8 },\n    }}\n    wrapperStyle={{\n      left: { borderColor: 'tomato', borderWidth: 4 },\n    }}\n    bottomContainerStyle={{\n      left: { borderColor: 'purple', borderWidth: 4 },\n    }}\n    usernameStyle={{ color: 'tomato', fontWeight: '100' }}\n    containerToNextStyle={{\n      left: { borderColor: 'navy', borderWidth: 4 },\n    }}\n    containerToPreviousStyle={{\n      left: { borderColor: 'mediumorchid', borderWidth: 4 },\n    }}\n  />\n)\n\nexport const renderSystemMessage = (props: SystemMessageProps<IMessage>) => (\n  <SystemMessage\n    {...props}\n    containerStyle={{ backgroundColor: 'pink' }}\n    wrapperStyle={{ borderWidth: 10, borderColor: 'white' }}\n    textStyle={{ color: 'crimson', fontWeight: '900' }}\n  />\n)\n\nexport const renderMessage = (props: MessageProps<IMessage>) => (\n  <Message\n    {...props}\n    // renderDay={() => <Text>Date</Text>}\n    containerStyle={{\n      left: { backgroundColor: 'lime' },\n      right: { backgroundColor: 'gold' },\n    }}\n  />\n)\n\nexport const renderMessageText = (props: MessageTextProps<IMessage>) => (\n  <MessageText\n    {...props}\n    containerStyle={{\n      left: { backgroundColor: 'yellow' },\n      right: { backgroundColor: 'purple' },\n    }}\n    textStyle={{\n      left: { color: 'red' },\n      right: { color: 'green' },\n    }}\n    linkStyle={{\n      left: { color: 'orange' },\n      right: { color: 'orange' },\n    }}\n    customTextStyle={{ fontSize: 24, lineHeight: 24 }}\n  />\n)\n\ninterface CustomViewProps {\n  user: User\n}\n\nexport const renderCustomView = ({ user }: CustomViewProps) => (\n  <View style={{ minHeight: 20, alignItems: 'center' }}>\n    <Text>\n      Current user:\n      {user.name}\n    </Text>\n    <Text>From CustomView</Text>\n  </View>\n)\n"
  },
  {
    "path": "example/example-gifted-chat/src/messages.ts",
    "content": "import { IMessage } from 'react-native-gifted-chat'\n\nconst messages: IMessage[] = [\n  {\n    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.\n        But you can also do more with this package, for example Bob will change style and David too. foo@gmail.com\n        And the magic number is 42!\n        #react #react-native`,\n    createdAt: new Date(Date.UTC(2016, 5, 13, 17, 20, 0)),\n    user: {\n      _id: 1,\n      name: 'John Doe',\n      avatar: 'https://placeimg.com/140/140/any',\n    },\n  },\n  {\n    text: 'Come on!',\n    createdAt: new Date(Date.UTC(2016, 5, 15, 18, 20, 0)),\n    user: {\n      _id: 2,\n      name: 'John Doe',\n      avatar: 'https://placeimg.com/140/140/any',\n    },\n  },\n  {\n    text: 'This is a quick reply. Do you love Gifted Chat? (checkbox)',\n    createdAt: new Date(Date.UTC(2016, 5, 15, 17, 20, 0)),\n    user: {\n      _id: 2,\n      name: 'John Doe',\n      avatar: 'https://placeimg.com/140/140/any',\n    },\n    quickReplies: {\n      type: 'checkbox', // or 'radio',\n      values: [\n        {\n          title: 'Yes',\n          value: 'yes',\n        },\n        {\n          title: 'Yes, let me show you with a picture!',\n          value: 'yes_picture',\n        },\n        {\n          title: 'Nope. What?',\n          value: 'no',\n        },\n      ],\n    },\n  },\n  {\n    text: 'This is a quick reply. Do you love Gifted Chat? (radio) KEEP IT',\n    createdAt: new Date(Date.UTC(2016, 5, 14, 17, 20, 0)),\n    user: {\n      _id: 2,\n      name: 'John Doe',\n      avatar: 'https://placeimg.com/140/140/any',\n    },\n    quickReplies: {\n      type: 'radio', // or 'checkbox',\n      keepIt: true,\n      values: [\n        {\n          title: '😋 Yes',\n          value: 'yes',\n        },\n        {\n          title: '📷 Yes, let me show you with a picture!',\n          value: 'yes_picture',\n        },\n        {\n          title: '😞 Nope. What?',\n          value: 'no',\n        },\n      ],\n    },\n  },\n  {\n    text: 'Hi! I work from home today!',\n    createdAt: new Date(Date.UTC(2016, 5, 13, 17, 20, 0)),\n    user: {\n      _id: 1,\n      name: 'John Doe',\n      avatar: 'https://placeimg.com/140/140/any',\n    },\n    image: 'https://placeimg.com/960/540/any',\n  },\n  {\n    text: 'Hello developer',\n    createdAt: new Date(Date.UTC(2016, 5, 12, 17, 20, 0)),\n    user: {\n      _id: 2,\n      name: 'John Doe',\n      avatar: 'https://placeimg.com/140/140/any',\n    },\n  },\n  {\n    text: 'This is a system message',\n    createdAt: new Date(Date.UTC(2016, 5, 11, 17, 20, 0)),\n    system: true,\n    user: {\n      _id: 0,\n    },\n  },\n].map((message, index) => ({\n  ...message,\n  _id: index + 1,\n})).reverse()\n\nexport default messages\n"
  },
  {
    "path": "example/example-slack-message/README.md",
    "content": "# \"Slack\" style UI example\n\nCredit and inspiration comes from [Slack](https://slack.com/).\n\nScreenshots to compare:\n\n| Default style | \"Slack\" style |\n|:-------------:|:-------------:|\n| <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\"> |\n"
  },
  {
    "path": "example/example-slack-message/src/SlackBubble.tsx",
    "content": "import React, { useCallback, useMemo } from 'react'\nimport {\n  Text,\n  StyleSheet,\n  View,\n  Platform,\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n} from 'react-native'\nimport * as Clipboard from 'expo-clipboard'\nimport { RectButton } from 'react-native-gesture-handler'\nimport {\n  MessageText,\n  MessageImage,\n  Time,\n  utils,\n  useChatContext,\n  IMessage,\n  User,\n} from 'react-native-gifted-chat'\n\nconst { isSameUser, isSameDay } = utils\n\ninterface Props {\n  touchableProps: object\n  onLongPress?: (context: unknown, currentMessage: object) => void\n  renderMessageImage?: (props: Props) => React.ReactNode\n  renderMessageText?: (props: Props) => React.ReactNode\n  renderCustomView?: (props: Props) => React.ReactNode\n  renderUsername?: (props: Props) => React.ReactNode\n  renderTime?: (props: Props) => React.ReactNode\n  renderTicks?: (currentMessage: IMessage) => React.ReactNode\n  currentMessage: IMessage\n  nextMessage: IMessage\n  previousMessage: IMessage\n  user: User\n  containerStyle?: {\n    left?: StyleProp<ViewStyle>\n    right?: StyleProp<ViewStyle>\n  }\n  wrapperStyle?: {\n    left?: StyleProp<ViewStyle>\n    right?: StyleProp<ViewStyle>\n  }\n  messageTextStyle: StyleProp<TextStyle>\n  usernameStyle: StyleProp<TextStyle>\n  tickStyle: StyleProp<TextStyle>\n  containerToNextStyle: {\n    left: StyleProp<ViewStyle>\n    right: StyleProp<ViewStyle>\n  }\n  containerToPreviousStyle: {\n    left: StyleProp<ViewStyle>\n    right: StyleProp<ViewStyle>\n  }\n  imageStyle?: StyleProp<ViewStyle>\n  textStyle?: StyleProp<TextStyle>\n  position: 'left' | 'right'\n}\n\nconst Bubble = (props: Props) => {\n  const {\n    touchableProps,\n    onLongPress,\n    renderCustomView,\n    currentMessage,\n    previousMessage,\n    user,\n    containerStyle,\n    wrapperStyle,\n    usernameStyle,\n    tickStyle,\n    position,\n  } = props\n\n  const context = useChatContext()\n\n  const handleLongPress = useCallback(() => {\n    if (onLongPress) {\n      onLongPress(context, currentMessage)\n      return\n    }\n\n    if (!currentMessage.text)\n      return\n\n    const options = ['Copy Text', 'Cancel']\n    const cancelButtonIndex = options.length - 1\n    context.actionSheet().showActionSheetWithOptions(\n      {\n        options,\n        cancelButtonIndex,\n      },\n      (buttonIndex?: number) => {\n        switch (buttonIndex) {\n          case 0:\n            Clipboard.setStringAsync(currentMessage.text)\n            break\n        }\n      }\n    )\n  }, [context, currentMessage, onLongPress])\n\n  const renderMessageText = useCallback(() => {\n    if (currentMessage.text) {\n      if (props.renderMessageText)\n        return props.renderMessageText(props)\n\n      return (\n        <MessageText\n          {...props}\n          textStyle={{\n            left: [\n              styles.standardFont,\n              styles.slackMessageText,\n              props.textStyle,\n              props.messageTextStyle,\n            ],\n          }}\n        />\n      )\n    }\n\n    return null\n  }, [currentMessage.text, props])\n\n  const renderMessageImage = useCallback(() => {\n    if (currentMessage.image) {\n      if (props.renderMessageImage)\n        return props.renderMessageImage(props)\n\n      return (\n        <MessageImage\n          {...props}\n          imageStyle={[styles.slackImage, props.imageStyle]}\n        />\n      )\n    }\n\n    return null\n  }, [currentMessage.image, props])\n\n  const renderTicks = useCallback(() => {\n    const { currentMessage } = props\n\n    if (props.renderTicks)\n      return props.renderTicks(currentMessage)\n\n    if (currentMessage.user._id !== user._id)\n      return null\n\n    if (currentMessage.sent || currentMessage.received)\n      return (\n        <View style={[styles.headerItem, styles.tickView]}>\n          {currentMessage.sent && (\n            <Text\n              style={[styles.standardFont, styles.tick, tickStyle]}\n            >\n              ✓\n            </Text>\n          )}\n          {currentMessage.received && (\n            <Text\n              style={[styles.standardFont, styles.tick, tickStyle]}\n            >\n              ✓\n            </Text>\n          )}\n        </View>\n      )\n\n    return null\n  }, [props, tickStyle, user._id])\n\n  const renderUsername = useCallback(() => {\n    const username = currentMessage.user.name\n    if (username) {\n      if (props.renderUsername)\n        return props.renderUsername(props)\n\n      return (\n        <Text\n          style={[\n            styles.standardFont,\n            styles.headerItem,\n            styles.username,\n            usernameStyle,\n          ]}\n        >\n          {username}\n        </Text>\n      )\n    }\n    return null\n  }, [currentMessage.user.name, props, usernameStyle])\n\n  const renderTime = useCallback(() => {\n    if (currentMessage.createdAt) {\n      if (props.renderTime)\n        return props.renderTime(props)\n\n      return (\n        <Time\n          {...props}\n          containerStyle={{ left: [styles.timeContainer] }}\n          textStyle={{\n            left: [\n              styles.standardFont,\n              styles.headerItem,\n              styles.time,\n              props.textStyle,\n            ],\n          }}\n        />\n      )\n    }\n\n    return null\n  }, [currentMessage.createdAt, props])\n\n  const isSameThread = useMemo(() =>\n    isSameUser(currentMessage, previousMessage) &&\n    isSameDay(currentMessage, previousMessage)\n  , [currentMessage, previousMessage])\n\n  const messageHeader = useMemo(() => {\n    if (isSameThread)\n      return null\n\n    return (\n      <View style={styles.headerView}>\n        {renderUsername()}\n        {renderTime()}\n        {renderTicks()}\n      </View>\n    )\n  }, [isSameThread, renderUsername, renderTime, renderTicks])\n\n  return (\n    <View style={[styles.container, containerStyle?.[position]]}>\n      <RectButton\n        onLongPress={handleLongPress}\n        accessibilityRole='text'\n        {...touchableProps}\n      >\n        <View style={[styles.wrapper, wrapperStyle?.[position]]}>\n          <View>\n            {renderCustomView?.(props)}\n            {messageHeader}\n            {renderMessageImage()}\n            {renderMessageText()}\n          </View>\n        </View>\n      </RectButton>\n    </View>\n  )\n}\n\n// Note: Everything is forced to be \"left\" positioned with this component.\n// The \"right\" position is only used in the default Bubble.\nconst styles = StyleSheet.create({\n  standardFont: {\n    fontSize: 15,\n  },\n  slackMessageText: {\n    marginLeft: 0,\n    marginRight: 0,\n  },\n  container: {\n    flex: 1,\n    alignItems: 'flex-start',\n  },\n  wrapper: {\n    marginRight: 60,\n    minHeight: 20,\n    justifyContent: 'flex-end',\n  },\n  username: {\n    fontWeight: 'bold',\n  },\n  time: {\n    textAlign: 'left',\n    fontSize: 12,\n  },\n  timeContainer: {\n    marginLeft: 0,\n    marginRight: 0,\n    marginBottom: 0,\n  },\n  headerItem: {\n    marginRight: 10,\n  },\n  headerView: {\n    // Try to align it better with the avatar on Android.\n    marginTop: Platform.OS === 'android' ? -2 : 0,\n    flexDirection: 'row',\n    alignItems: 'baseline',\n  },\n  tick: {\n    backgroundColor: 'transparent',\n    color: 'white',\n  },\n  tickView: {\n    flexDirection: 'row',\n  },\n  slackImage: {\n    borderRadius: 3,\n    marginLeft: 0,\n    marginRight: 0,\n  },\n})\n\nexport default Bubble\n"
  },
  {
    "path": "example/example-slack-message/src/SlackMessage.tsx",
    "content": "import React, { useCallback, useMemo } from 'react'\nimport {\n  View,\n  StyleSheet,\n  StyleProp,\n  ViewStyle,\n} from 'react-native'\n\nimport { Avatar, Day, utils } from 'react-native-gifted-chat'\nimport type { DayProps, BubbleProps, AvatarProps, IMessage } from 'react-native-gifted-chat'\nimport Bubble from './SlackBubble'\n\nconst { isSameUser, isSameDay } = utils\n\ninterface Props {\n  renderAvatar?: (props: AvatarProps<IMessage>) => void\n  renderBubble?: (props: BubbleProps<IMessage>) => void\n  renderDay?: (props: DayProps) => void\n  currentMessage: any\n  nextMessage?: any\n  previousMessage?: any\n  containerStyle?: {\n    left: StyleProp<ViewStyle>\n    right: StyleProp<ViewStyle>\n  }\n}\n\nconst Message = (props: Props) => {\n  const {\n    currentMessage,\n    nextMessage,\n    previousMessage,\n    containerStyle,\n  } = props\n\n  const getInnerComponentProps = useCallback(() => {\n    return {\n      ...props,\n      position: 'left',\n      isSameUser,\n      isSameDay,\n      containerStyle: props.containerStyle?.left,\n    }\n  }, [props])\n\n  const renderDay = useCallback(() => {\n    if (currentMessage.createdAt) {\n      const dayProps = getInnerComponentProps()\n\n      if (props.renderDay)\n        return props.renderDay(dayProps)\n\n      return <Day {...dayProps} />\n    }\n\n    return null\n  }, [currentMessage.createdAt, getInnerComponentProps, props])\n\n  const renderBubble = useCallback(() => {\n    const bubbleProps = getInnerComponentProps()\n\n    if (props.renderBubble)\n      return props.renderBubble(bubbleProps)\n\n    return <Bubble {...bubbleProps} />\n  }, [getInnerComponentProps, props])\n\n  const renderAvatar = useCallback(() => {\n    let extraStyle\n    if (\n      isSameUser(currentMessage, previousMessage) &&\n      isSameDay(currentMessage, previousMessage)\n    )\n      // Set the invisible avatar height to 0, but keep the width, padding, etc.\n      extraStyle = { height: 0 }\n\n    const avatarProps = getInnerComponentProps()\n\n    if (props.renderAvatar)\n      return props.renderAvatar(avatarProps)\n\n    return (\n      <Avatar\n        {...avatarProps}\n        imageStyle={{\n          left: [styles.slackAvatar, avatarProps.imageStyle, extraStyle],\n        }}\n      />\n    )\n  }, [currentMessage, previousMessage, getInnerComponentProps, props])\n\n  const marginBottom = useMemo(() =>\n    isSameUser(\n      currentMessage,\n      nextMessage\n    )\n      ? 2\n      : 10\n  , [currentMessage, nextMessage])\n\n  return (\n    <View>\n      {renderDay()}\n      <View\n        style={[\n          styles.container,\n          { marginBottom },\n          containerStyle,\n        ]}\n      >\n        {renderAvatar()}\n        {renderBubble()}\n      </View>\n    </View>\n  )\n}\n\nexport default Message\n\nconst styles = StyleSheet.create({\n  container: {\n    flexDirection: 'row',\n    alignItems: 'flex-end',\n    justifyContent: 'flex-start',\n    marginLeft: 8,\n    marginRight: 0,\n  },\n  slackAvatar: {\n    // The bottom should roughly line up with the first line of message text.\n    height: 40,\n    width: 40,\n    borderRadius: 3,\n  },\n})\n"
  },
  {
    "path": "example/hooks/use-color-scheme.ts",
    "content": "export { useColorScheme } from 'react-native'\n"
  },
  {
    "path": "example/hooks/use-color-scheme.web.ts",
    "content": "import { useEffect, useState } from 'react'\nimport { useColorScheme as useRNColorScheme } from 'react-native'\n\n/**\n * To support static rendering, this value needs to be re-calculated on the client side for web\n */\nexport function useColorScheme () {\n  const [hasHydrated, setHasHydrated] = useState(false)\n\n  useEffect(() => {\n    setHasHydrated(true)\n  }, [])\n\n  const colorScheme = useRNColorScheme()\n\n  if (hasHydrated)\n    return colorScheme\n\n  return 'light'\n}\n"
  },
  {
    "path": "example/hooks/use-theme-color.ts",
    "content": "/**\n * Learn more about light and dark modes:\n * https://docs.expo.dev/guides/color-schemes/\n */\n\nimport { Colors } from '@/constants/theme'\nimport { useColorScheme } from '@/hooks/use-color-scheme'\n\nexport function useThemeColor (\n  props: { light?: string, dark?: string },\n  colorName: keyof typeof Colors.light & keyof typeof Colors.dark\n) {\n  const theme = useColorScheme() ?? 'light'\n  const colorFromProps = props[theme]\n\n  if (colorFromProps)\n    return colorFromProps\n  else\n    return Colors[theme][colorName]\n}\n"
  },
  {
    "path": "example/hooks/useKeyboardVerticalOffset.ts",
    "content": "import { useHeaderHeight } from '@react-navigation/elements'\n\n/**\n * Hook to get the correct keyboardVerticalOffset for GiftedChat.\n *\n * The offset equals the distance from the screen top to the GiftedChat container top.\n * Uses useHeaderHeight() which includes status bar + navigation header height on iOS.\n *\n * Note: This hook requires the component to be rendered inside a proper navigation screen\n * (not conditional rendering). If useHeaderHeight returns 0, ensure your chat screen\n * is a real navigation screen with a visible header.\n *\n * @returns {number} keyboardVerticalOffset to pass to keyboardAvoidingViewProps\n */\nexport function useKeyboardVerticalOffset () {\n  // useHeaderHeight() returns status bar + navigation header height on iOS\n  return useHeaderHeight()\n}\n"
  },
  {
    "path": "example/ios/.gitignore",
    "content": "# 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*.perspectivev3\n!default.perspectivev3\nxcuserdata\n*.xccheckout\n*.moved-aside\nDerivedData\n*.hmap\n*.ipa\n*.xcuserstate\nproject.xcworkspace\n.xcode.env.local\n\n# Bundle artifacts\n*.jsbundle\n\n# CocoaPods\n/Pods/\n"
  },
  {
    "path": "example/ios/.xcode.env",
    "content": "# This `.xcode.env` file is versioned and is used to source the environment\n# used when running script phases inside Xcode.\n# To customize your local environment, you can create an `.xcode.env.local`\n# file that is not versioned.\n\n# NODE_BINARY variable contains the PATH to the node executable.\n#\n# Customize the NODE_BINARY variable here.\n# For example, to use nvm with brew, add the following line\n# . \"$(brew --prefix nvm)/nvm.sh\" --no-use\nexport NODE_BINARY=$(command -v node)\n"
  },
  {
    "path": "example/ios/Podfile",
    "content": "require File.join(File.dirname(`node --print \"require.resolve('expo/package.json')\"`), \"scripts/autolinking\")\nrequire File.join(File.dirname(`node --print \"require.resolve('react-native/package.json')\"`), \"scripts/react_native_pods\")\n\nrequire 'json'\npodfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}\n\ndef ccache_enabled?(podfile_properties)\n  # Environment variable takes precedence\n  return ENV['USE_CCACHE'] == '1' if ENV['USE_CCACHE']\n  \n  # Fall back to Podfile properties\n  podfile_properties['apple.ccacheEnabled'] == 'true'\nend\n\nENV['RCT_NEW_ARCH_ENABLED'] ||= '0' if podfile_properties['newArchEnabled'] == 'false'\nENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] ||= podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']\nENV['RCT_USE_RN_DEP'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'\nENV['RCT_USE_PREBUILT_RNCORE'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false'\nplatform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'\n\nprepare_react_native_project!\n\ntarget 'example' do\n  use_expo_modules!\n\n  if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'\n    config_command = ['node', '-e', \"process.argv=['', '', 'config'];require('@react-native-community/cli').run()\"];\n  else\n    config_command = [\n      'node',\n      '--no-warnings',\n      '--eval',\n      'require(\\'expo/bin/autolinking\\')',\n      'expo-modules-autolinking',\n      'react-native-config',\n      '--json',\n      '--platform',\n      'ios'\n    ]\n  end\n\n# @generated begin react-native-maps - expo prebuild (DO NOT MODIFY) sync-e9cc66c360abe50bc66d89fffb3c55b034d7d369\n  pod 'react-native-google-maps', path: File.dirname(`node --print \"require.resolve('react-native-maps/package.json')\"`)\n# @generated end react-native-maps\n  config = use_native_modules!(config_command)\n\n  use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']\n  use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']\n\n  use_react_native!(\n    :path => config[:reactNativePath],\n    :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',\n    # An absolute path to your application root.\n    :app_path => \"#{Pod::Config.instance.installation_root}/..\",\n    :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',\n  )\n\n  post_install do |installer|\n    react_native_post_install(\n      installer,\n      config[:reactNativePath],\n      :mac_catalyst_enabled => false,\n      :ccache_enabled => ccache_enabled?(podfile_properties),\n    )\n  end\nend\n"
  },
  {
    "path": "example/ios/Podfile.properties.json",
    "content": "{\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",
    "content": "{\n  \"images\": [\n    {\n      \"filename\": \"App-Icon-1024x1024@1x.png\",\n      \"idiom\": \"universal\",\n      \"platform\": \"ios\",\n      \"size\": \"1024x1024\"\n    }\n  ],\n  \"info\": {\n    \"version\": 1,\n    \"author\": \"expo\"\n  }\n}"
  },
  {
    "path": "example/ios/example/Images.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"expo\"\n  }\n}\n"
  },
  {
    "path": "example/ios/example/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>CADisableMinimumFrameDurationOnPhone</key>\n    <true/>\n    <key>CFBundleDevelopmentRegion</key>\n    <string>$(DEVELOPMENT_LANGUAGE)</string>\n    <key>CFBundleDisplayName</key>\n    <string>example</string>\n    <key>CFBundleExecutable</key>\n    <string>$(EXECUTABLE_NAME)</string>\n    <key>CFBundleIdentifier</key>\n    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n    <key>CFBundleInfoDictionaryVersion</key>\n    <string>6.0</string>\n    <key>CFBundleName</key>\n    <string>$(PRODUCT_NAME)</string>\n    <key>CFBundlePackageType</key>\n    <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n    <key>CFBundleShortVersionString</key>\n    <string>1.0.0</string>\n    <key>CFBundleSignature</key>\n    <string>????</string>\n    <key>CFBundleURLTypes</key>\n    <array>\n      <dict>\n        <key>CFBundleURLSchemes</key>\n        <array>\n          <string>example</string>\n          <string>com.anonymous.example</string>\n        </array>\n      </dict>\n    </array>\n    <key>CFBundleVersion</key>\n    <string>1</string>\n    <key>GMSApiKey</key>\n    <string>YOUR_GOOGLE_MAPS_API_KEY</string>\n    <key>LSMinimumSystemVersion</key>\n    <string>12.0</string>\n    <key>LSRequiresIPhoneOS</key>\n    <true/>\n    <key>NSAppTransportSecurity</key>\n    <dict>\n      <key>NSAllowsArbitraryLoads</key>\n      <false/>\n      <key>NSAllowsLocalNetworking</key>\n      <true/>\n    </dict>\n    <key>NSCameraUsageDescription</key>\n    <string>Allow $(PRODUCT_NAME) to access your camera</string>\n    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>\n    <string>Allow $(PRODUCT_NAME) to access your location</string>\n    <key>NSLocationAlwaysUsageDescription</key>\n    <string>Allow $(PRODUCT_NAME) to access your location</string>\n    <key>NSLocationWhenInUseUsageDescription</key>\n    <string>Allow $(PRODUCT_NAME) to access your location</string>\n    <key>NSMicrophoneUsageDescription</key>\n    <string>Allow $(PRODUCT_NAME) to access your microphone</string>\n    <key>NSPhotoLibraryUsageDescription</key>\n    <string>Allow $(PRODUCT_NAME) to access your photos</string>\n    <key>NSUserActivityTypes</key>\n    <array>\n      <string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>\n    </array>\n    <key>RCTNewArchEnabled</key>\n    <true/>\n    <key>UILaunchStoryboardName</key>\n    <string>SplashScreen</string>\n    <key>UIRequiredDeviceCapabilities</key>\n    <array>\n      <string>arm64</string>\n    </array>\n    <key>UIRequiresFullScreen</key>\n    <false/>\n    <key>UIStatusBarStyle</key>\n    <string>UIStatusBarStyleDefault</string>\n    <key>UISupportedInterfaceOrientations</key>\n    <array>\n      <string>UIInterfaceOrientationPortrait</string>\n      <string>UIInterfaceOrientationPortraitUpsideDown</string>\n    </array>\n    <key>UISupportedInterfaceOrientations~ipad</key>\n    <array>\n      <string>UIInterfaceOrientationPortrait</string>\n      <string>UIInterfaceOrientationPortraitUpsideDown</string>\n      <string>UIInterfaceOrientationLandscapeLeft</string>\n      <string>UIInterfaceOrientationLandscapeRight</string>\n    </array>\n    <key>UIUserInterfaceStyle</key>\n    <string>Automatic</string>\n    <key>UIViewControllerBasedStatusBarAppearance</key>\n    <false/>\n  </dict>\n</plist>"
  },
  {
    "path": "example/ios/example/PrivacyInfo.xcprivacy",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSPrivacyAccessedAPITypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategoryUserDefaults</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>CA92.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>0A2A.1</string>\n\t\t\t\t<string>3B52.1</string>\n\t\t\t\t<string>C617.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategoryDiskSpace</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>E174.1</string>\n\t\t\t\t<string>85F4.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategorySystemBootTime</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>35F9.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>NSPrivacyCollectedDataTypes</key>\n\t<array/>\n\t<key>NSPrivacyTracking</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "example/ios/example/SplashScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<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\">\n    <device id=\"retina6_12\" orientation=\"portrait\" appearance=\"light\"/>\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"24053.1\"/>\n        <capability name=\"Named colors\" minToolsVersion=\"9.0\"/>\n        <capability name=\"Safe area layout guides\" minToolsVersion=\"9.0\"/>\n        <capability name=\"System colors in document resources\" minToolsVersion=\"11.0\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <scenes>\n        <scene sceneID=\"EXPO-SCENE-1\">\n            <objects>\n                <viewController storyboardIdentifier=\"SplashScreenViewController\" id=\"EXPO-VIEWCONTROLLER-1\" sceneMemberID=\"viewController\">\n                    <view key=\"view\" userInteractionEnabled=\"NO\" contentMode=\"scaleToFill\" insetsLayoutMarginsFromSafeArea=\"NO\" id=\"EXPO-ContainerView\" userLabel=\"ContainerView\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"393\" height=\"852\"/>\n                        <autoresizingMask key=\"autoresizingMask\" flexibleMaxX=\"YES\" flexibleMaxY=\"YES\"/>\n                        <subviews>\n                            <imageView id=\"EXPO-SplashScreen\" userLabel=\"SplashScreenLogo\" image=\"SplashScreenLogo\" contentMode=\"scaleAspectFit\" clipsSubviews=\"true\" userInteractionEnabled=\"false\" translatesAutoresizingMaskIntoConstraints=\"false\">\n                                <rect key=\"frame\" x=\"96.5\" y=\"326\" width=\"200\" height=\"200\"/>\n                            </imageView>\n                        </subviews>\n                        <viewLayoutGuide key=\"safeArea\" id=\"Rmq-lb-GrQ\"/>\n                        <constraints>\n                            <constraint firstItem=\"EXPO-SplashScreen\" firstAttribute=\"centerX\" secondItem=\"EXPO-ContainerView\" secondAttribute=\"centerX\" id=\"cad2ab56f97c5429bf29decf850647a4216861d4\"/>\n                            <constraint firstItem=\"EXPO-SplashScreen\" firstAttribute=\"centerY\" secondItem=\"EXPO-ContainerView\" secondAttribute=\"centerY\" id=\"1a145271b085b6ce89b1405a310f5b1bb7656595\"/>\n                        </constraints>\n                        <color key=\"backgroundColor\" name=\"SplashScreenBackground\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"EXPO-PLACEHOLDER-1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"0.0\" y=\"0.0\"/>\n        </scene>\n    </scenes>\n    <resources>\n        <image name=\"SplashScreenLogo\" width=\"200\" height=\"200\"/>\n        <systemColor name=\"systemBackgroundColor\">\n            <color white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"genericGamma22GrayColorSpace\"/>\n        </systemColor>\n        <namedColor name=\"SplashScreenBackground\">\n            <color alpha=\"1.000\" blue=\"1.00000000000000\" green=\"1.00000000000000\" red=\"1.00000000000000\" customColorSpace=\"sRGB\" colorSpace=\"custom\"/>\n        </namedColor>\n    </resources>\n</document>"
  },
  {
    "path": "example/ios/example/Supporting/Expo.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>EXUpdatesCheckOnLaunch</key>\n    <string>ALWAYS</string>\n    <key>EXUpdatesEnabled</key>\n    <false/>\n    <key>EXUpdatesLaunchWaitMs</key>\n    <integer>0</integer>\n  </dict>\n</plist>"
  },
  {
    "path": "example/ios/example.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t0EB4AC0857AAFB0D77472CBD /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B945C814238A5FEBE89E96F5 /* libPods-example.a */; };\n\t\t13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };\n\t\t3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };\n\t\t95774C49D12665FBD013B7B6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1F72D30C6AB981B0917D041F /* PrivacyInfo.xcprivacy */; };\n\t\tBB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };\n\t\tD2455EE1B33CDCC769A93446 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A038F507BB039A5B209DF9 /* ExpoModulesProvider.swift */; };\n\t\tF11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t13B07F961A680F5B00A75B9A /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = example/Images.xcassets; sourceTree = \"<group>\"; };\n\t\t13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = example/Info.plist; sourceTree = \"<group>\"; };\n\t\t1F72D30C6AB981B0917D041F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = example/PrivacyInfo.xcprivacy; sourceTree = \"<group>\"; };\n\t\t2C6A65C7C222B5FD1DFFEB3D /* 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>\"; };\n\t\t5ACC43DD9EC8D36FFB24F025 /* 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>\"; };\n\t\tA9A038F507BB039A5B209DF9 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = \"Pods/Target Support Files/Pods-example/ExpoModulesProvider.swift\"; sourceTree = \"<group>\"; };\n\t\tAA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = example/SplashScreen.storyboard; sourceTree = \"<group>\"; };\n\t\tB945C814238A5FEBE89E96F5 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = \"libPods-example.a\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tBB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = \"<group>\"; };\n\t\tED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };\n\t\tF11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = example/AppDelegate.swift; sourceTree = \"<group>\"; };\n\t\tF11748442D0722820044C1D9 /* example-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = \"example-Bridging-Header.h\"; path = \"example/example-Bridging-Header.h\"; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t13B07F8C1A680F5B00A75B9A /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t0EB4AC0857AAFB0D77472CBD /* libPods-example.a in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t13B07FAE1A68108700A75B9A /* example */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tF11748412D0307B40044C1D9 /* AppDelegate.swift */,\n\t\t\t\tF11748442D0722820044C1D9 /* example-Bridging-Header.h */,\n\t\t\t\tBB2F792B24A3F905000567C9 /* Supporting */,\n\t\t\t\t13B07FB51A68108700A75B9A /* Images.xcassets */,\n\t\t\t\t13B07FB61A68108700A75B9A /* Info.plist */,\n\t\t\t\tAA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,\n\t\t\t\t1F72D30C6AB981B0917D041F /* PrivacyInfo.xcprivacy */,\n\t\t\t);\n\t\t\tname = example;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t2D16E6871FA4F8E400B85C8A /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tED297162215061F000B7C4FE /* JavaScriptCore.framework */,\n\t\t\t\tB945C814238A5FEBE89E96F5 /* libPods-example.a */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t73C3B28C69CD6BB9738FA204 /* example */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tA9A038F507BB039A5B209DF9 /* ExpoModulesProvider.swift */,\n\t\t\t);\n\t\t\tname = example;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t832341AE1AAA6A7D00B99B32 /* Libraries */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = Libraries;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t83CBB9F61A601CBA00E9B192 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t13B07FAE1A68108700A75B9A /* example */,\n\t\t\t\t832341AE1AAA6A7D00B99B32 /* Libraries */,\n\t\t\t\t83CBBA001A601CBA00E9B192 /* Products */,\n\t\t\t\t2D16E6871FA4F8E400B85C8A /* Frameworks */,\n\t\t\t\tD52C4A25CB8634558A3AF68D /* Pods */,\n\t\t\t\tC493ADA3EAB4EF422CF3A61E /* ExpoModulesProviders */,\n\t\t\t);\n\t\t\tindentWidth = 2;\n\t\t\tsourceTree = \"<group>\";\n\t\t\ttabWidth = 2;\n\t\t\tusesTabs = 0;\n\t\t};\n\t\t83CBBA001A601CBA00E9B192 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t13B07F961A680F5B00A75B9A /* example.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBB2F792B24A3F905000567C9 /* Supporting */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tBB2F792C24A3F905000567C9 /* Expo.plist */,\n\t\t\t);\n\t\t\tname = Supporting;\n\t\t\tpath = example/Supporting;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tC493ADA3EAB4EF422CF3A61E /* ExpoModulesProviders */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t73C3B28C69CD6BB9738FA204 /* example */,\n\t\t\t);\n\t\t\tname = ExpoModulesProviders;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tD52C4A25CB8634558A3AF68D /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t2C6A65C7C222B5FD1DFFEB3D /* Pods-example.debug.xcconfig */,\n\t\t\t\t5ACC43DD9EC8D36FFB24F025 /* Pods-example.release.xcconfig */,\n\t\t\t);\n\t\t\tname = Pods;\n\t\t\tpath = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t13B07F861A680F5B00A75B9A /* example */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget \"example\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,\n\t\t\t\tE3A154A212F22893CC38D97B /* [Expo] Configure project */,\n\t\t\t\t13B07F871A680F5B00A75B9A /* Sources */,\n\t\t\t\t13B07F8C1A680F5B00A75B9A /* Frameworks */,\n\t\t\t\t13B07F8E1A680F5B00A75B9A /* Resources */,\n\t\t\t\t00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,\n\t\t\t\t800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,\n\t\t\t\t55CE56BF3547841532F83FD6 /* [CP] Embed Pods Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = example;\n\t\t\tproductName = example;\n\t\t\tproductReference = 13B07F961A680F5B00A75B9A /* example.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t83CBB9F71A601CBA00E9B192 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1130;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t13B07F861A680F5B00A75B9A = {\n\t\t\t\t\t\tLastSwiftMigration = 1250;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject \"example\" */;\n\t\t\tcompatibilityVersion = \"Xcode 3.2\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 83CBB9F61A601CBA00E9B192;\n\t\t\tproductRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t13B07F861A680F5B00A75B9A /* example */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t13B07F8E1A680F5B00A75B9A /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tBB2F792D24A3F905000567C9 /* Expo.plist in Resources */,\n\t\t\t\t13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,\n\t\t\t\t3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,\n\t\t\t\t95774C49D12665FBD013B7B6 /* PrivacyInfo.xcprivacy in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"$(SRCROOT)/.xcode.env\",\n\t\t\t\t\"$(SRCROOT)/.xcode.env.local\",\n\t\t\t);\n\t\t\tname = \"Bundle React Native code and images\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"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\";\n\t\t};\n\t\t08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-example-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"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\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t55CE56BF3547841532F83FD6 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\",\n\t\t\t\t\"${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React\",\n\t\t\t\t\"${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies\",\n\t\t\t\t\"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputPaths = (\n\t\t\t\t\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/GoogleMaps/GoogleMapsResources.bundle\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-google-maps/GoogleMapsPrivacy.bundle\",\n\t\t\t\t\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-maps/ReactNativeMapsPrivacy.bundle\",\n\t\t\t);\n\t\t\tname = \"[CP] Copy Pods Resources\";\n\t\t\toutputPaths = (\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMapsResources.bundle\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMapsPrivacy.bundle\",\n\t\t\t\t\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ReactNativeMapsPrivacy.bundle\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\tE3A154A212F22893CC38D97B /* [Expo] Configure project */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"$(SRCROOT)/.xcode.env\",\n\t\t\t\t\"$(SRCROOT)/.xcode.env.local\",\n\t\t\t\t\"$(SRCROOT)/example/example.entitlements\",\n\t\t\t\t\"$(SRCROOT)/Pods/Target Support Files/Pods-example/expo-configure-project.sh\",\n\t\t\t);\n\t\t\tname = \"[Expo] Configure project\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(SRCROOT)/Pods/Target Support Files/Pods-example/ExpoModulesProvider.swift\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"# 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\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t13B07F871A680F5B00A75B9A /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tF11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */,\n\t\t\t\tD2455EE1B33CDCC769A93446 /* ExpoModulesProvider.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t13B07F941A680F5B00A75B9A /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 2C6A65C7C222B5FD1DFFEB3D /* Pods-example.debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = example/example.entitlements;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"FB_SONARKIT_ENABLED=1\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = example/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tOTHER_SWIFT_FLAGS = \"$(inherited) -D EXPO_CONFIGURATION_DEBUG\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.anonymous.example;\n\t\t\t\tPRODUCT_NAME = example;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"example/example-Bridging-Header.h\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t13B07F951A680F5B00A75B9A /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 5ACC43DD9EC8D36FFB24F025 /* Pods-example.release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = example/example.entitlements;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tINFOPLIST_FILE = example/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"-lc++\",\n\t\t\t\t);\n\t\t\t\tOTHER_SWIFT_FLAGS = \"$(inherited) -D EXPO_CONFIGURATION_RELEASE\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.anonymous.example;\n\t\t\t\tPRODUCT_NAME = example;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"example/example-Bridging-Header.h\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t83CBBA201A601CBA00E9B192 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++20\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_SYMBOLS_PRIVATE_EXTERN = NO;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t/usr/lib/swift,\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = \"$(SDKROOT)/usr/lib/swift\\\"$(inherited)\\\"\";\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tREACT_NATIVE_PATH = \"${PODS_ROOT}/../../node_modules/react-native\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"$(inherited) DEBUG\";\n\t\t\t\tSWIFT_ENABLE_EXPLICIT_MODULES = NO;\n\t\t\t\tUSE_HERMES = true;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t83CBBA211A601CBA00E9B192 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"c++20\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = YES;\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t/usr/lib/swift,\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tLIBRARY_SEARCH_PATHS = \"$(SDKROOT)/usr/lib/swift\\\"$(inherited)\\\"\";\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tREACT_NATIVE_PATH = \"${PODS_ROOT}/../../node_modules/react-native\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ENABLE_EXPLICIT_MODULES = NO;\n\t\t\t\tUSE_HERMES = true;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget \"example\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t13B07F941A680F5B00A75B9A /* Debug */,\n\t\t\t\t13B07F951A680F5B00A75B9A /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject \"example\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t83CBBA201A601CBA00E9B192 /* Debug */,\n\t\t\t\t83CBBA211A601CBA00E9B192 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;\n}\n"
  },
  {
    "path": "example/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1130\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n               BuildableName = \"example.app\"\n               BlueprintName = \"example\"\n               ReferencedContainer = \"container:example.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"00E356ED1AD99517003FC87E\"\n               BuildableName = \"exampleTests.xctest\"\n               BlueprintName = \"exampleTests\"\n               ReferencedContainer = \"container:example.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n            BuildableName = \"example.app\"\n            BlueprintName = \"example\"\n            ReferencedContainer = \"container:example.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"13B07F861A680F5B00A75B9A\"\n            BuildableName = \"example.app\"\n            BlueprintName = \"example\"\n            ReferencedContainer = \"container:example.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "example/ios/example.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:example.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:Pods/Pods.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "example/metro.config.js",
    "content": "const escape = require('escape-string-regexp')\nconst { getDefaultConfig } = require('expo/metro-config')\nconst fs = require('fs')\nconst path = require('path')\n\nconst config = getDefaultConfig(__dirname)\nconst { transformer, resolver } = config\nconst defaultWatchFolders = config.watchFolders ?? []\n\nconst root = path.resolve(__dirname, '..')\nconst rootPak = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))\n\nconst modules = [\n  '@babel/runtime',\n  'metro-runtime',\n  'react-native-web',\n  ...Object.keys({\n    ...rootPak.dependencies,\n    ...rootPak.peerDependencies,\n  }),\n]\n\nmodule.exports = {\n  ...config,\n\n  projectRoot: __dirname,\n  watchFolders: [...defaultWatchFolders, root],\n\n  // We need to make sure that only one version is loaded for peerDependencies\n  // So we blocklist them at the root, and alias them to the versions in example's node_modules\n  resolver: {\n    ...resolver,\n    blockList: [new RegExp(`^${escape(path.join(root, 'node_modules'))}\\\\/.*$`)],\n    extraNodeModules: modules.reduce((acc, name) => {\n      acc[name] = path.join(__dirname, 'node_modules', name)\n      return acc\n    }, {}),\n    unstable_enablePackageExports: false,\n  },\n\n  transformer: {\n    ...transformer,\n    getTransformOptions: async () => ({\n      transform: {\n        experimentalImportSupport: false,\n        inlineRequires: true,\n      },\n    }),\n  },\n}\n"
  },
  {
    "path": "example/package.json",
    "content": "{\n  \"name\": \"example\",\n  \"main\": \"expo-router/entry\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"reset-project\": \"node ./scripts/reset-project.js\",\n    \"start\": \"npx expo start --dev-client --clear\",\n    \"start:android\": \"npx expo start --android --dev-client --clear\",\n    \"start:ios\": \"npx expo start --ios --dev-client --clear\",\n    \"start:web\": \"npx expo start --web --clear\",\n    \"lint\": \"expo lint\",\n    \"fresh\": \"yarn start --reset-cache\",\n    \"installDevBuild:ios\": \"expo run:ios\",\n    \"installDevBuild:android\": \"expo run:android\",\n    \"rebuildNativeDeps\": \"yarn expo prebuild\",\n    \"rebuildNativeDeps:clean\": \"yarn expo prebuild --clean\",\n    \"android\": \"expo run:android\",\n    \"ios\": \"expo run:ios\"\n  },\n  \"dependencies\": {\n    \"@expo/react-native-action-sheet\": \"^4.1.1\",\n    \"@expo/vector-icons\": \"^15.0.3\",\n    \"@react-navigation/bottom-tabs\": \"^7.4.0\",\n    \"@react-navigation/elements\": \"^2.9.5\",\n    \"@react-navigation/native\": \"^7.1.8\",\n    \"expo\": \"~54.0.31\",\n    \"expo-build-properties\": \"~1.0.10\",\n    \"expo-clipboard\": \"~8.0.8\",\n    \"expo-constants\": \"~18.0.13\",\n    \"expo-font\": \"~14.0.10\",\n    \"expo-haptics\": \"~15.0.8\",\n    \"expo-image\": \"~3.0.11\",\n    \"expo-image-picker\": \"~17.0.10\",\n    \"expo-linking\": \"~8.0.11\",\n    \"expo-location\": \"~19.0.8\",\n    \"expo-router\": \"~6.0.21\",\n    \"expo-splash-screen\": \"~31.0.13\",\n    \"expo-status-bar\": \"~3.0.9\",\n    \"expo-symbols\": \"~1.0.8\",\n    \"expo-system-ui\": \"~6.0.9\",\n    \"expo-web-browser\": \"~15.0.10\",\n    \"libphonenumber-js\": \"^1.12.34\",\n    \"react\": \"19.1.0\",\n    \"react-dom\": \"19.1.0\",\n    \"react-native\": \"0.81.5\",\n    \"react-native-gesture-handler\": \"~2.30.0\",\n    \"react-native-gifted-chat\": \"link:..\",\n    \"react-native-keyboard-controller\": \"1.20.6\",\n    \"react-native-maps\": \"1.20.1\",\n    \"react-native-paper\": \"^5.14.5\",\n    \"react-native-reanimated\": \"~4.1.1\",\n    \"react-native-safe-area-context\": \"~5.6.2\",\n    \"react-native-screens\": \"~4.16.0\",\n    \"react-native-web\": \"~0.21.2\",\n    \"react-native-worklets\": \"0.5.1\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.28.6\",\n    \"@react-native/metro-config\": \"0.81.5\",\n    \"@react-native/typescript-config\": \"0.81.5\",\n    \"@types/react\": \"~19.1.10\",\n    \"@types/react-test-renderer\": \"^19.1.0\",\n    \"babel-plugin-module-resolver\": \"^5.0.2\",\n    \"eslint\": \"^9.39.2\",\n    \"eslint-config-expo\": \"~10.0.0\",\n    \"jest\": \"^29.7.0\",\n    \"react-test-renderer\": \"19.2.3\",\n    \"typescript\": \"~5.9.3\"\n  },\n  \"private\": true\n}\n"
  },
  {
    "path": "example/scripts/reset-project.js",
    "content": "#!/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, /components, /hooks, /scripts, and /constants directories to /app-example based on user input and creates a new /app directory with an index.tsx and _layout.tsx file.\n * You can remove the `reset-project` script from package.json and safely delete this file after running it.\n */\n\nconst fs = require('fs')\nconst path = require('path')\nconst readline = require('readline')\n\nconst root = process.cwd()\nconst oldDirs = ['app', 'components', 'hooks', 'constants', 'scripts']\nconst exampleDir = 'app-example'\nconst newAppDir = 'app'\nconst exampleDirPath = path.join(root, exampleDir)\n\nconst indexContent = `import { Text, View } from \"react-native\";\n\nexport default function Index() {\n  return (\n    <View\n      style={{\n        flex: 1,\n        justifyContent: \"center\",\n        alignItems: \"center\",\n      }}\n    >\n      <Text>Edit app/index.tsx to edit this screen.</Text>\n    </View>\n  );\n}\n`\n\nconst layoutContent = `import { Stack } from \"expo-router\";\n\nexport default function RootLayout() {\n  return <Stack />;\n}\n`\n\nconst rl = readline.createInterface({\n  input: process.stdin,\n  output: process.stdout,\n})\n\nconst moveDirectories = async userInput => {\n  try {\n    if (userInput === 'y') {\n      // Create the app-example directory\n      await fs.promises.mkdir(exampleDirPath, { recursive: true })\n      console.log(`📁 /${exampleDir} directory created.`)\n    }\n\n    // Move old directories to new app-example directory or delete them\n    for (const dir of oldDirs) {\n      const oldDirPath = path.join(root, dir)\n      if (fs.existsSync(oldDirPath))\n        if (userInput === 'y') {\n          const newDirPath = path.join(root, exampleDir, dir)\n          await fs.promises.rename(oldDirPath, newDirPath)\n          console.log(`➡️ /${dir} moved to /${exampleDir}/${dir}.`)\n        } else {\n          await fs.promises.rm(oldDirPath, { recursive: true, force: true })\n          console.log(`❌ /${dir} deleted.`)\n        }\n      else\n        console.log(`➡️ /${dir} does not exist, skipping.`)\n    }\n\n    // Create new /app directory\n    const newAppDirPath = path.join(root, newAppDir)\n    await fs.promises.mkdir(newAppDirPath, { recursive: true })\n    console.log('\\n📁 New /app directory created.')\n\n    // Create index.tsx\n    const indexPath = path.join(newAppDirPath, 'index.tsx')\n    await fs.promises.writeFile(indexPath, indexContent)\n    console.log('📄 app/index.tsx created.')\n\n    // Create _layout.tsx\n    const layoutPath = path.join(newAppDirPath, '_layout.tsx')\n    await fs.promises.writeFile(layoutPath, layoutContent)\n    console.log('📄 app/_layout.tsx created.')\n\n    console.log('\\n✅ Project reset complete. Next steps:')\n    console.log(\n      `1. Run \\`npx expo start\\` to start a development server.\\n2. Edit app/index.tsx to edit the main screen.${\n        userInput === 'y'\n          ? `\\n3. Delete the /${exampleDir} directory when you're done referencing it.`\n          : ''\n      }`\n    )\n  } catch (error) {\n    console.error(`❌ Error during script execution: ${error.message}`)\n  }\n}\n\nrl.question(\n  'Do you want to move existing files to /app-example instead of deleting them? (Y/n): ',\n  answer => {\n    const userInput = answer.trim().toLowerCase() || 'y'\n    if (userInput === 'y' || userInput === 'n') {\n      moveDirectories(userInput).finally(() => rl.close())\n    } else {\n      console.log('❌ Invalid input. Please enter \\'Y\\' or \\'N\\'.')\n      rl.close()\n    }\n  }\n)\n"
  },
  {
    "path": "example/styles/index.ts",
    "content": "import { StyleSheet } from 'react-native'\n\nexport default StyleSheet.create({\n  center: {\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  textCenter: {\n    textAlign: 'center',\n  },\n})\n"
  },
  {
    "path": "example/tsconfig.json",
    "content": "{\n  \"extends\": \"expo/tsconfig.base\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"paths\": {\n      \"@/*\": [\n        \"./*\"\n      ]\n    }\n  },\n  \"include\": [\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".expo/types/**/*.ts\",\n    \"expo-env.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "example/utils/styleUtils.ts",
    "content": "export function getColorSchemeStyle<T>(styles: T, baseName: string, colorScheme: string | null | undefined) {\n  const key = `${baseName}_${colorScheme}` as keyof T\n  return styles[key]\n}\n"
  },
  {
    "path": "expoSnack/ExpoSnack.tsx",
    "content": "import React, { useCallback, useMemo, useState } from 'react'\nimport {\n  StyleSheet,\n  Text,\n  View,\n  useColorScheme,\n} from 'react-native'\nimport { ActionSheetProvider, useActionSheet } from '@expo/react-native-action-sheet'\nimport { MaterialIcons } from '@expo/vector-icons'\nimport dayjs from 'dayjs'\nimport * as ImagePicker from 'expo-image-picker'\nimport { getCurrentPositionAsync, requestForegroundPermissionsAsync } from 'expo-location'\nimport { RectButton } from 'react-native-gesture-handler'\nimport { GiftedChat } from 'react-native-gifted-chat'\nimport type { IMessage, User } from 'react-native-gifted-chat'\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction getColorSchemeStyle<T>(styles: T, baseName: string, colorScheme: string | null | undefined) {\n  const key = `${baseName}_${colorScheme}` as keyof T\n  return styles[key]\n}\n\n// ============================================================================\n// Data\n// ============================================================================\n\nconst date1 = dayjs()\nconst date2 = date1.clone().subtract(1, 'day')\nconst date3 = date2.clone().subtract(1, 'week')\n\nconst initialMessages: IMessage[] = [\n  {\n    text: '',\n    createdAt: date3.toDate(),\n    audio:\n      'https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3',\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n  },\n  {\n    text: '',\n    createdAt: date3.toDate(),\n    video: 'https://media.giphy.com/media/3o6ZthZjk09Xx4ktZ6/giphy.mp4',\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n  },\n  {\n    text: 'This is a quick reply. Do you love Gifted Chat? (checkbox)',\n    createdAt: date3.toDate(),\n    quickReplies: {\n      type: 'checkbox',\n      values: [\n        {\n          title: 'Yes',\n          value: 'yes',\n        },\n        {\n          title: 'Yes, let me show you with a picture!',\n          value: 'yes_picture',\n        },\n        {\n          title: 'Nope. What?',\n          value: 'no',\n        },\n      ],\n    },\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n  },\n  {\n    text: 'This is a quick reply. Do you love Gifted Chat? (radio) KEEP IT',\n    createdAt: date3.toDate(),\n    quickReplies: {\n      type: 'radio',\n      keepIt: true,\n      values: [\n        {\n          title: '😋 Yes',\n          value: 'yes',\n        },\n        {\n          title: '📷 Yes, let me show you with a picture!',\n          value: 'yes_picture',\n        },\n        {\n          title: '😞 Nope. What?',\n          value: 'no',\n        },\n      ],\n    },\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n  },\n  {\n    text: 'Are you building a chat app?',\n    createdAt: date3.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    text: 'Yes, and I use #GiftedChat!',\n    createdAt: date3.toDate(),\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n    sent: true,\n    received: true,\n  },\n  {\n    text: 'Where are you?',\n    createdAt: date3.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    text: '',\n    createdAt: date2.toDate(),\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n    sent: true,\n    received: true,\n    location: {\n      latitude: 48.864601,\n      longitude: 2.398704,\n    },\n  },\n  {\n    text: 'Send me a picture!',\n    createdAt: date2.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    text: 'Paris',\n    createdAt: date2.toDate(),\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n    image:\n      'https://static.vecteezy.com/system/resources/thumbnails/003/407/768/small/eiffel-tower-at-paris-france-free-photo.jpg',\n    sent: true,\n    received: true,\n  },\n  {\n    text: '#awesome',\n    createdAt: date1.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    text: '#awesome 2',\n    createdAt: date1.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n  {\n    text: '#awesome 3',\n    createdAt: date1.toDate(),\n    user: {\n      _id: 1,\n      name: 'Developer',\n    },\n  },\n].map((message, index) => ({\n  ...message,\n  _id: index + 1,\n})).reverse()\n\n// Earlier messages for \"Load Earlier\" functionality\nconst getEarlierMessages = (): IMessage[] => {\n  const date = dayjs().subtract(1, 'year')\n  return [\n    {\n      _id: Math.round(Math.random() * 1000000),\n      text: 'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/',\n      createdAt: date.toDate(),\n      user: {\n        _id: 1,\n        name: 'Developer',\n      },\n    },\n    {\n      _id: Math.round(Math.random() * 1000000),\n      text: 'React Native lets you build mobile apps using only JavaScript',\n      createdAt: date.toDate(),\n      user: {\n        _id: 1,\n        name: 'Developer',\n      },\n    },\n    {\n      _id: Math.round(Math.random() * 1000000),\n      text: 'This is a system message.',\n      createdAt: date.toDate(),\n      system: true,\n      user: {\n        _id: 0,\n      },\n    },\n  ]\n}\n\n// ============================================================================\n// Media Utilities\n// ============================================================================\n\nasync function getLocationAsync() {\n  const response = await requestForegroundPermissionsAsync()\n  if (!response.granted)\n    return\n\n  const location = await getCurrentPositionAsync()\n  if (!location)\n    return\n\n  return location.coords\n}\n\nasync function pickImageAsync() {\n  const response = await ImagePicker.requestMediaLibraryPermissionsAsync()\n  if (!response.granted)\n    return\n\n  const result = await ImagePicker.launchImageLibraryAsync({\n    allowsEditing: true,\n    aspect: [4, 3],\n  })\n\n  if (result.canceled)\n    return\n\n  return result.assets.map(({ uri }) => uri)\n}\n\nasync function takePictureAsync() {\n  const response = await ImagePicker.requestCameraPermissionsAsync()\n  if (!response.granted)\n    return\n\n  const result = await ImagePicker.launchCameraAsync({\n    allowsEditing: true,\n    aspect: [4, 3],\n  })\n\n  if (result.canceled)\n    return\n\n  return result.assets.map(({ uri }) => uri)\n}\n\n// ============================================================================\n// Custom Actions Component\n// ============================================================================\n\ninterface CustomActionsProps {\n  renderIcon?: () => React.ReactNode\n  wrapperStyle?: any\n  containerStyle?: any\n  iconTextStyle?: any\n  onSend: (messages: IMessage[]) => void\n  user: User\n}\n\nconst CustomActions = ({\n  renderIcon,\n  iconTextStyle,\n  containerStyle,\n  wrapperStyle,\n  onSend,\n  user,\n}: CustomActionsProps) => {\n  const { showActionSheetWithOptions } = useActionSheet()\n  const colorScheme = useColorScheme()\n\n  const handlePickImage = useCallback(async () => {\n    const images = await pickImageAsync()\n    if (!images)\n      return\n\n    const messages: IMessage[] = images.map(image => ({\n      _id: Math.random().toString(36).substring(7),\n      image,\n      text: '',\n      createdAt: new Date(),\n      user,\n    }))\n    onSend(messages)\n  }, [onSend, user])\n\n  const handleTakePicture = useCallback(async () => {\n    const images = await takePictureAsync()\n    if (!images)\n      return\n\n    const messages: IMessage[] = images.map(image => ({\n      _id: Math.random().toString(36).substring(7),\n      image,\n      text: '',\n      createdAt: new Date(),\n      user,\n    }))\n    onSend(messages)\n  }, [onSend, user])\n\n  const handleSendLocation = useCallback(async () => {\n    const location = await getLocationAsync()\n    if (!location)\n      return\n\n    const message: IMessage = {\n      _id: Math.random().toString(36).substring(7),\n      location,\n      text: '',\n      createdAt: new Date(),\n      user,\n    }\n    onSend([message])\n  }, [onSend, user])\n\n  const onActionsPress = useCallback(() => {\n    const options: { title: string, action?: () => Promise<void> }[] = [\n      { title: 'Choose From Library', action: handlePickImage },\n      { title: 'Take Picture', action: handleTakePicture },\n      { title: 'Send Location', action: handleSendLocation },\n      { title: 'Cancel' },\n    ]\n    const cancelButtonIndex = options.length - 1\n\n    showActionSheetWithOptions(\n      {\n        options: options.map(o => o.title),\n        cancelButtonIndex,\n      },\n      async buttonIndex => {\n        if (buttonIndex !== undefined) {\n          const selectedOption = options[buttonIndex]\n          selectedOption?.action?.()\n        }\n      }\n    )\n  }, [showActionSheetWithOptions, handlePickImage, handleTakePicture, handleSendLocation])\n\n  const renderIconComponent = useCallback(() => {\n    if (renderIcon)\n      return renderIcon()\n\n    const wrapperColorStyle = colorScheme === 'dark' ? customActionsStyles.wrapper_dark : {}\n    const iconTextColorStyle = colorScheme === 'dark' ? customActionsStyles.iconText_dark : {}\n\n    return (\n      <View style={[customActionsStyles.wrapper, wrapperColorStyle, wrapperStyle]}>\n        <Text style={[customActionsStyles.iconText, iconTextColorStyle, iconTextStyle]}>+</Text>\n      </View>\n    )\n  }, [renderIcon, wrapperStyle, iconTextStyle, colorScheme])\n\n  return (\n    <RectButton style={[customActionsStyles.container, containerStyle]} onPress={onActionsPress}>\n      {renderIconComponent()}\n    </RectButton>\n  )\n}\n\nconst customActionsStyles = StyleSheet.create({\n  container: {\n    paddingHorizontal: 8,\n    paddingVertical: 7,\n  },\n  wrapper: {\n    width: 26,\n    height: 26,\n    borderRadius: 13,\n    borderColor: '#b2b2b2',\n    borderWidth: 2,\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  wrapper_dark: {\n    borderColor: '#666',\n  },\n  iconText: {\n    color: '#b2b2b2',\n    fontWeight: 'bold',\n    fontSize: 16,\n    lineHeight: 16,\n    backgroundColor: 'transparent',\n    textAlign: 'center',\n  },\n  iconText_dark: {\n    color: '#999',\n  },\n})\n\n// ============================================================================\n// Accessory Bar Component\n// ============================================================================\n\ninterface AccessoryBarProps {\n  onSend: (messages: IMessage[]) => void\n  isTyping: () => void\n  user: User\n}\n\nconst AccessoryBar = ({ onSend, isTyping, user }: AccessoryBarProps) => {\n  const colorScheme = useColorScheme()\n  const isDark = colorScheme === 'dark'\n\n  const handlePickImage = async () => {\n    const images = await pickImageAsync()\n    if (!images)\n      return\n\n    const messages: IMessage[] = images.map(image => ({\n      _id: Math.random().toString(36).substring(7),\n      image,\n      text: '',\n      createdAt: new Date(),\n      user,\n    }))\n    onSend(messages)\n  }\n\n  const handleTakePicture = async () => {\n    const images = await takePictureAsync()\n    if (!images)\n      return\n\n    const messages: IMessage[] = images.map(image => ({\n      _id: Math.random().toString(36).substring(7),\n      image,\n      text: '',\n      createdAt: new Date(),\n      user,\n    }))\n    onSend(messages)\n  }\n\n  const handleSendLocation = async () => {\n    const location = await getLocationAsync()\n    if (!location)\n      return\n\n    const message: IMessage = {\n      _id: Math.random().toString(36).substring(7),\n      location,\n      text: '',\n      createdAt: new Date(),\n      user,\n    }\n    onSend([message])\n  }\n\n  const containerColorStyle = colorScheme === 'dark' ? accessoryBarStyles.container_dark : {}\n\n  return (\n    <View style={[accessoryBarStyles.container, containerColorStyle]}>\n      <Button\n        onPress={handlePickImage}\n        name='photo'\n        color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.5)'}\n      />\n      <Button\n        onPress={handleTakePicture}\n        name='camera'\n        color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.5)'}\n      />\n      <Button\n        onPress={handleSendLocation}\n        name='my-location'\n        color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.5)'}\n      />\n      <Button\n        onPress={() => {\n          isTyping()\n        }}\n        name='chat'\n        color={isDark ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.5)'}\n      />\n    </View>\n  )\n}\n\nconst Button = ({\n  onPress,\n  size = 30,\n  color = 'rgba(0,0,0,0.5)',\n  name,\n}: {\n  onPress: () => void\n  size?: number\n  color?: string\n  name: React.ComponentProps<typeof MaterialIcons>['name']\n}) => (\n  <RectButton onPress={onPress}>\n    <MaterialIcons size={size} color={color} name={name} />\n  </RectButton>\n)\n\nconst accessoryBarStyles = StyleSheet.create({\n  container: {\n    height: 44,\n    backgroundColor: 'white',\n    flexDirection: 'row',\n    justifyContent: 'space-around',\n    alignItems: 'center',\n    borderTopWidth: StyleSheet.hairlineWidth,\n    borderTopColor: 'rgba(0,0,0,0.3)',\n    paddingVertical: 5,\n  },\n  container_dark: {\n    backgroundColor: '#1a1a1a',\n    borderTopColor: 'rgba(255,255,255,0.2)',\n  },\n})\n\n// ============================================================================\n// Custom View Component (for location messages)\n// ============================================================================\n\ninterface CustomViewProps {\n  currentMessage: IMessage\n  containerStyle?: any\n}\n\nconst CustomView = ({ currentMessage, containerStyle }: CustomViewProps) => {\n  const handlePress = useCallback(() => {\n    alert('Opening the map is not supported in this demo.')\n  }, [])\n\n  if (currentMessage.location)\n    return (\n      <RectButton onPress={handlePress}>\n        <View style={[customViewStyles.container, containerStyle]}>\n          <MaterialIcons name='location-on' size={24} color='tomato' />\n          <Text style={customViewStyles.text}>\n            Location: {currentMessage.location.latitude.toFixed(4)}, {currentMessage.location.longitude.toFixed(4)}\n          </Text>\n        </View>\n      </RectButton>\n    )\n\n  return null\n}\n\nconst customViewStyles = StyleSheet.create({\n  container: {\n    width: 150,\n    height: 100,\n    borderRadius: 13,\n    margin: 3,\n    backgroundColor: '#f0f0f0',\n    padding: 10,\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  text: {\n    color: 'tomato',\n    fontWeight: 'bold',\n    fontSize: 12,\n    marginTop: 5,\n    textAlign: 'center',\n  },\n})\n\n// ============================================================================\n// Main Chat Component\n// ============================================================================\n\nfunction ChatExample() {\n  const [messages, setMessages] = useState<IMessage[]>(initialMessages)\n  const [isLoadingEarlier, setIsLoadingEarlier] = useState(false)\n  const [isTyping, setIsTyping] = useState(false)\n  const colorScheme = useColorScheme()\n\n  const user = useMemo(\n    () => ({\n      _id: 1,\n      name: 'Developer',\n    }),\n    []\n  )\n\n  const onSend = useCallback(\n    (newMessages: IMessage[] = []) => {\n      const messagesWithIds = newMessages.map(msg => ({\n        ...msg,\n        _id: msg._id || Math.random().toString(36).substring(7),\n        user: msg.user || user,\n      }))\n      setMessages(previousMessages => GiftedChat.append(previousMessages, messagesWithIds))\n    },\n    [user]\n  )\n\n  const onPressLoadEarlierMessages = useCallback(() => {\n    setIsLoadingEarlier(true)\n    setTimeout(() => {\n      setMessages(previousMessages => GiftedChat.prepend(previousMessages, getEarlierMessages()))\n      setIsLoadingEarlier(false)\n    }, 1500)\n  }, [])\n\n  const renderAccessory = useCallback(\n    () => <AccessoryBar onSend={onSend} isTyping={() => setIsTyping(isTyping => !isTyping)} user={user} />,\n    [onSend, user]\n  )\n\n  const renderActions = useCallback(\n    (props: any) => <CustomActions {...props} onSend={onSend} user={user} />,\n    [onSend, user]\n  )\n\n  const renderCustomView = useCallback(\n    (props: any) => <CustomView {...props} />,\n    []\n  )\n\n  return (\n    <View style={[styles.container, getColorSchemeStyle(styles, 'container', colorScheme)]}>\n      <GiftedChat\n        messages={messages}\n        onSend={onSend}\n        loadEarlierMessagesProps={{\n          isAvailable: true,\n          isLoading: isLoadingEarlier,\n          onPress: onPressLoadEarlierMessages,\n        }}\n        user={user}\n        renderActions={renderActions}\n        renderAccessory={renderAccessory}\n        renderCustomView={renderCustomView}\n        isTyping={isTyping}\n        messagesContainerStyle={getColorSchemeStyle(styles, 'messagesContainer', colorScheme)}\n        textInputProps={{\n          style: getColorSchemeStyle(styles, 'composer', colorScheme),\n        }}\n      />\n    </View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: '#fff',\n  },\n  container_dark: {\n    backgroundColor: '#000',\n  },\n  messagesContainer_dark: {\n    backgroundColor: '#000',\n  },\n  composer_dark: {\n    backgroundColor: '#1a1a1a',\n    color: '#fff',\n  },\n})\n\n// ============================================================================\n// App Export (wrapped with ActionSheetProvider)\n// ============================================================================\n\nexport default function App() {\n  return (\n    <ActionSheetProvider>\n      <ChatExample />\n    </ActionSheetProvider>\n  )\n}\n"
  },
  {
    "path": "expoSnack/README.md",
    "content": "# React Native Gifted Chat - Expo Snack\n\nThis directory contains a self-contained Expo Snack example that demonstrates the full capabilities of React Native Gifted Chat.\n\n## 🚀 Quick Start\n\n### Option 1: Use Expo Snack (Recommended)\n\n1. Go to [https://snack.expo.dev](https://snack.expo.dev)\n2. Click \"Create a new Snack\"\n3. Copy the entire contents of `ExpoSnack.tsx` from this repository\n4. Replace the default `App.tsx` content with the copied code\n5. Install dependencies (see below)\n6. Run on your device or simulator\n\n### Option 2: Use in Your Project\n\nSimply copy `ExpoSnack.tsx` into your Expo project and ensure all dependencies are installed.\n\n## 📦 Required Dependencies\n\nAdd these dependencies to your Expo Snack or project:\n\n```json\n{\n  \"dependencies\": {\n    \"react-native-gifted-chat\": \"*\",\n    \"react-native-gesture-handler\": \"*\",\n    \"react-native-safe-area-context\": \"*\",\n    \"react-native-reanimated\": \"*\",\n    \"@expo/react-native-action-sheet\": \"*\",\n    \"@expo/vector-icons\": \"*\",\n    \"expo-image-picker\": \"*\",\n    \"expo-location\": \"*\",\n    \"expo-linking\": \"*\",\n    \"react-native-maps\": \"*\",\n    \"dayjs\": \"*\"\n  }\n}\n```\n\n### In Expo Snack:\n\n1. Click on the \"Dependencies\" tab in the left sidebar\n2. Search for and add each dependency listed above\n3. Wait for the dependencies to install\n\n## ✨ Features Demonstrated\n\nThis example showcases all major features of React Native Gifted Chat:\n\n### Core Features\n- ✅ Text messaging with bubbles\n- ✅ User avatars and names\n- ✅ Message timestamps\n- ✅ Typing indicator\n- ✅ Load earlier messages\n- ✅ Dark mode support\n\n### Rich Media\n- 📷 Image messages\n- 📹 Video messages\n- 🎵 Audio messages\n- 📍 Location/map messages\n\n### Interactive Elements\n- ⚡ Quick replies (radio and checkbox)\n- ➕ Custom actions (via action sheet)\n- 🎯 Custom accessory bar with quick actions\n\n### Custom Components\n- **CustomActions**: Action button with action sheet menu\n- **AccessoryBar**: Bottom toolbar with photo, camera, location, and typing buttons\n- **CustomView**: Map view for location messages\n\n## 🎮 How to Use\n\n### Sending Messages\n1. Type in the text input at the bottom\n2. Press the send button to send a message\n\n### Adding Images\n1. Tap the **+** button or the **photo icon** in the accessory bar\n2. Choose \"Choose From Library\" or \"Take Picture\"\n3. Select or capture an image\n\n### Sending Location\n1. Tap the **location icon** in the accessory bar\n2. Grant location permissions when prompted\n3. Location will be sent and displayed as a map\n\n### Loading Earlier Messages\n1. Scroll to the top of the chat\n2. Tap \"Load earlier messages\"\n3. Wait for earlier messages to load (simulated with 1.5s delay)\n\n### Quick Replies\n1. Scroll to find the quick reply messages\n2. Tap on any quick reply option\n3. Watch it get added to your chat\n\n### Toggle Typing Indicator\n1. Tap the **chat icon** in the accessory bar\n2. See the typing indicator appear/disappear\n\n## 🎨 Customization\n\nThe example includes several customizable components that you can modify:\n\n### Colors & Theming\nThe app automatically adapts to your device's color scheme (light/dark mode). You can customize the colors in the `styles` object:\n\n```typescript\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: '#fff', // Light mode background\n  },\n  containerDark: {\n    backgroundColor: '#000', // Dark mode background\n  },\n  // ... more styles\n})\n```\n\n### Message Data\nInitial messages are defined in the `initialMessages` array. You can modify or add more messages:\n\n```typescript\nconst initialMessages: IMessage[] = [\n  {\n    _id: 1,\n    text: 'Your message here',\n    createdAt: new Date(),\n    user: {\n      _id: 2,\n      name: 'Bot Name',\n    },\n  },\n  // ... more messages\n]\n```\n\n### User Configuration\nThe current user is defined in the `user` object:\n\n```typescript\nconst user = useMemo(() => ({\n  _id: 1,\n  name: 'Developer',\n}), [])\n```\n\n## 📱 Platform Support\n\nThis example works on:\n- ✅ iOS (simulator and device)\n- ✅ Android (emulator and device)\n- ⚠️ Web (limited - maps and some media features may not work)\n\n## 🔧 Troubleshooting\n\n### Permissions Issues\nIf image picker or location features don't work:\n1. Make sure you've granted the necessary permissions in your device settings\n2. On iOS simulator, you may need to set a custom location\n3. On Android emulator, enable location services\n\n### Dependencies Not Installing\nIf dependencies fail to install in Expo Snack:\n1. Try refreshing the page\n2. Clear your browser cache\n3. Try using a different browser\n4. Check Expo Snack status at [status.expo.dev](https://status.expo.dev)\n\n### Maps Not Displaying\nMaps may not work on web or some simulators:\n1. Use a physical device for best results\n2. On Android emulator, enable Google Play Services\n3. On iOS simulator, maps should work by default\n\n## 📖 Code Structure\n\nThe `ExpoSnack.tsx` file is organized into these sections:\n\n1. **Imports**: All required dependencies\n2. **Data**: Initial messages and earlier messages\n3. **Media Utilities**: Functions for image picker, camera, and location\n4. **CustomView**: Component for rendering map locations\n5. **CustomActions**: Action button component with action sheet\n6. **AccessoryBar**: Bottom toolbar component\n7. **ChatExample**: Main chat component\n8. **App**: Root component with ActionSheetProvider\n\n## 🚀 Next Steps\n\nOnce you have this working, you can:\n\n1. Customize the styling to match your app\n2. Connect to a real backend (Firebase, Socket.io, etc.)\n3. Add more custom message types\n4. Implement message persistence\n5. Add user authentication\n6. Implement push notifications\n\n## 📚 Additional Resources\n\n- [React Native Gifted Chat Documentation](https://github.com/FaridSafi/react-native-gifted-chat)\n- [Expo Documentation](https://docs.expo.dev)\n- [Expo Snack](https://snack.expo.dev)\n\n## ⚠️ Known Limitations\n\nWhen using in Expo Snack:\n- File sizes may be limited\n- Some native modules may not work\n- Performance may be slower than a local app\n- Network requests may be restricted\n\nFor production apps, it's recommended to use this as a starting point and develop locally with Expo CLI or React Native CLI.\n\n## 💡 Tips\n\n1. **Test on a real device**: Many features work better on physical devices\n2. **Use the Expo Go app**: Scan the QR code with Expo Go for best results\n3. **Check console for errors**: Open the console in Expo Snack to see any errors\n4. **Experiment**: Modify the code and see changes in real-time!\n\n## 📝 License\n\nThis example is based on the React Native Gifted Chat library which is MIT licensed.\n\n---\n\nHappy chatting! 🎉\n"
  },
  {
    "path": "expoSnack/package.json",
    "content": "{\n  \"dependencies\": {\n    \"dayjs\": \"*\",\n    \"expo-font\": \"~14.0.9\",\n    \"expo-image\": \"~3.0.10\",\n    \"expo-router\": \"~6.0.15\",\n    \"expo-haptics\": \"~15.0.7\",\n    \"expo-linking\": \"~8.0.9\",\n    \"expo-symbols\": \"~1.0.7\",\n    \"expo-location\": \"~19.0.7\",\n    \"expo-clipboard\": \"~8.0.7\",\n    \"expo-constants\": \"~18.0.10\",\n    \"expo-system-ui\": \"~6.0.8\",\n    \"expo-status-bar\": \"~3.0.8\",\n    \"expo-app-loading\": \"^2.1.1\",\n    \"expo-web-browser\": \"~15.0.9\",\n    \"expo-image-picker\": \"~17.0.8\",\n    \"@expo/vector-icons\": \"^15.0.3\",\n    \"expo-splash-screen\": \"~31.0.11\",\n    \"react-native-paper\": \"^5.14.5\",\n    \"react-native-screens\": \"~4.16.0\",\n    \"react-native-worklets\": \"0.5.1\",\n    \"react-native-reanimated\": \"~4.1.1\",\n    \"@react-navigation/native\": \"^7.1.20\",\n    \"react-native-gifted-chat\": \"^3.0.1\",\n    \"@react-navigation/elements\": \"^2.8.2\",\n    \"react-native-gesture-handler\": \"~2.28.0\",\n    \"@react-navigation/bottom-tabs\": \"^7.8.5\",\n    \"react-native-safe-area-context\": \"~5.6.0\",\n    \"@expo/react-native-action-sheet\": \"^4.1.1\",\n    \"react-native-keyboard-controller\": \"1.18.5\",\n    \"@react-native-masked-view/masked-view\": \"0.3.2\"\n  }\n}\n"
  },
  {
    "path": "jest.config.cjs",
    "content": "module.exports = {\n  preset: 'react-native',\n  resetMocks: true,\n  setupFilesAfterEnv: [\n    './node_modules/react-native/jest-preset',\n    './node_modules/react-native-gesture-handler/jestSetup.js',\n    './tests/setup.ts',\n  ],\n  moduleFileExtensions: ['js', 'jsx', 'json', 'ts', 'tsx'],\n  transform: {\n    '\\\\.js$': ['babel-jest', { configFile: './babel.config.cjs' }],\n  },\n  transformIgnorePatterns: [],\n  testMatch: ['**/*.test.ts?(x)'],\n  modulePathIgnorePatterns: ['./example'],\n  coveragePathIgnorePatterns: ['./src/__tests__/'],\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"react-native-gifted-chat\",\n  \"version\": \"3.3.2\",\n  \"description\": \"The most complete chat UI for React Native\",\n  \"keywords\": [\n    \"android\",\n    \"ios\",\n    \"react-native\",\n    \"react\",\n    \"react-component\",\n    \"messenger\",\n    \"message\",\n    \"chat\"\n  ],\n  \"homepage\": \"https://github.com/FaridSafi/react-native-gifted-chat#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/FaridSafi/react-native-gifted-chat/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/FaridSafi/react-native-gifted-chat.git\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"Farid Safi\",\n  \"type\": \"module\",\n  \"main\": \"src/index.ts\",\n  \"types\": \"src/index.ts\",\n  \"files\": [\n    \"src\"\n  ],\n  \"scripts\": {\n    \"lint\": \"yarn eslint src example\",\n    \"lint:fix\": \"yarn eslint --cache --fix src example\",\n    \"prepublishOnly\": \"yarn lint && yarn test\",\n    \"start\": \"cd example && expo start\",\n    \"start:web\": \"cd example && expo start -w --dev\",\n    \"test\": \"TZ=Europe/Paris jest --no-watchman\",\n    \"test:coverage\": \"TZ=Europe/Paris jest --coverage\",\n    \"test:watch\": \"TZ=Europe/Paris jest --watch\",\n    \"tsc:write\": \"yarn tsc --project tsconfig.json\",\n    \"tsc:watch\": \"yarn tsc --watch --noEmit\",\n    \"fresh\": \"yarn start --reset-cache\",\n    \"prepare\": \"yarn husky\"\n  },\n  \"lint-staged\": {\n    \"src/*.{json,js,jsx,ts,tsx}\": [\n      \"yarn lint:fix\"\n    ]\n  },\n  \"dependencies\": {\n    \"@expo/react-native-action-sheet\": \"^4.1.1\",\n    \"@types/lodash.isequal\": \"^4.5.8\",\n    \"dayjs\": \"^1.11.19\",\n    \"lodash.isequal\": \"^4.5.0\",\n    \"react-native-zoom-reanimated\": \"^1.5.2\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.28.5\",\n    \"@babel/plugin-transform-react-jsx\": \"^7.27.1\",\n    \"@babel/plugin-transform-unicode-property-regex\": \"^7.27.1\",\n    \"@babel/preset-env\": \"^7.28.5\",\n    \"@react-native-community/cli\": \"20.0.0\",\n    \"@react-native-community/cli-platform-android\": \"20.0.0\",\n    \"@react-native-community/cli-platform-ios\": \"20.0.0\",\n    \"@react-native/babel-preset\": \"0.81.5\",\n    \"@react-native/eslint-config\": \"0.81.5\",\n    \"@react-native/metro-config\": \"0.81.5\",\n    \"@react-native/typescript-config\": \"0.81.5\",\n    \"@stylistic/eslint-plugin\": \"^3.1.0\",\n    \"@testing-library/dom\": \"^10.4.1\",\n    \"@testing-library/react\": \"^16.3.0\",\n    \"@testing-library/react-native\": \"^13.3.3\",\n    \"@types/jest\": \"^29.5.13\",\n    \"@types/react\": \"^19.2.5\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"@types/react-native\": \"^0.72.8\",\n    \"@types/react-test-renderer\": \"^19.1.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.46.4\",\n    \"@typescript-eslint/parser\": \"^8.46.4\",\n    \"babel-jest\": \"^29.7.0\",\n    \"eslint\": \"^9.18.0\",\n    \"eslint-import-resolver-typescript\": \"^4.4.4\",\n    \"eslint-plugin-import\": \"^2.32.0\",\n    \"eslint-plugin-jest\": \"^28.11.0\",\n    \"eslint-plugin-perfectionist\": \"^4.15.1\",\n    \"eslint-plugin-react\": \"^7.37.5\",\n    \"eslint-plugin-react-hooks\": \"^5.1.0\",\n    \"husky\": \"^9.1.7\",\n    \"jest\": \"^29.7.0\",\n    \"json\": \"^11.0.0\",\n    \"lint-staged\": \"^15.5.0\",\n    \"react\": \"19.1.0\",\n    \"react-dom\": \"19.1.0\",\n    \"react-native\": \"0.81.5\",\n    \"react-native-gesture-handler\": \"~2.30.0\",\n    \"react-native-keyboard-controller\": \"1.20.6\",\n    \"react-native-reanimated\": \"~4.2.1\",\n    \"react-native-safe-area-context\": \"~5.6.2\",\n    \"react-native-worklets\": \"0.7.2\",\n    \"react-test-renderer\": \"19.1.0\",\n    \"typescript\": \"^5.9.3\"\n  },\n  \"peerDependencies\": {\n    \"react\": \">=18.0.0\",\n    \"react-native\": \"*\",\n    \"react-native-gesture-handler\": \">=2.0.0\",\n    \"react-native-keyboard-controller\": \">=1.0.0\",\n    \"react-native-reanimated\": \">=3.0.0 || ^4.0.0\",\n    \"react-native-safe-area-context\": \">=5.0.0\"\n  },\n  \"packageManager\": \"yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e\",\n  \"engines\": {\n    \"node\": \">=20\"\n  }\n}\n"
  },
  {
    "path": "src/Actions.tsx",
    "content": "import React, { ReactNode, useCallback } from 'react'\nimport {\n  StyleSheet,\n  View,\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n} from 'react-native'\nimport { Text } from 'react-native-gesture-handler'\nimport { Color } from './Color'\nimport { TouchableOpacity } from './components/TouchableOpacity'\n\nimport { useChatContext } from './GiftedChatContext'\nimport stylesCommon from './styles'\n\nexport interface ActionsProps {\n  actions?: Array<{ title: string, action: () => void }>\n  actionSheetOptionTintColor?: string\n  icon?: () => ReactNode\n  wrapperStyle?: StyleProp<ViewStyle>\n  iconTextStyle?: StyleProp<TextStyle>\n  buttonStyle?: StyleProp<ViewStyle>\n  onPressActionButton?(): void\n}\n\nexport function Actions ({\n  actions,\n  actionSheetOptionTintColor = Color.optionTintColor,\n  icon,\n  wrapperStyle,\n  iconTextStyle,\n  onPressActionButton,\n  buttonStyle,\n}: ActionsProps) {\n  const { actionSheet } = useChatContext()\n\n  const handlePress = useCallback(() => {\n    if (onPressActionButton) {\n      onPressActionButton()\n      return\n    }\n\n    if (!actions?.length)\n      return\n\n    const titles = actions.map(item => item.title)\n\n    actionSheet().showActionSheetWithOptions(\n      {\n        options: titles,\n        cancelButtonIndex: titles.length - 1,\n        tintColor: actionSheetOptionTintColor,\n      },\n      (buttonIndex?: number) => {\n        if (buttonIndex === undefined)\n          return\n\n        const item = actions[buttonIndex]\n        item.action?.()\n      }\n    )\n  }, [actionSheet, actions, actionSheetOptionTintColor, onPressActionButton])\n\n  const renderIcon = useCallback(() => {\n    if (icon)\n      return icon()\n\n    return (\n      <View style={[stylesCommon.centerItems, styles.wrapper, wrapperStyle]}>\n        <Text style={[styles.iconText, iconTextStyle]}>{'+'}</Text>\n      </View>\n    )\n  }, [icon, iconTextStyle, wrapperStyle])\n\n  return (\n    <View style={styles.container}>\n      <TouchableOpacity\n        onPress={handlePress}\n        style={[styles.button, buttonStyle]}\n      >\n        {renderIcon()}\n      </TouchableOpacity>\n    </View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    alignItems: 'flex-end',\n  },\n  button: {\n    paddingLeft: 10,\n    paddingRight: 4,\n    paddingVertical: 7,\n  },\n\n  wrapper: {\n    borderColor: Color.defaultColor,\n    borderWidth: 2,\n    width: 26,\n    height: 26,\n    borderRadius: 13,\n  },\n  iconText: {\n    color: Color.defaultColor,\n    fontWeight: 'bold',\n    fontSize: 16,\n    lineHeight: 16,\n  },\n})\n"
  },
  {
    "path": "src/Avatar.tsx",
    "content": "import React, { ReactNode, useCallback } from 'react'\nimport {\n  ImageStyle,\n  StyleSheet,\n  TextStyle,\n  View,\n  ViewStyle,\n} from 'react-native'\nimport { GiftedAvatar } from './GiftedAvatar'\nimport { IMessage, LeftRightStyle, User } from './Models'\nimport { isSameUser, isSameDay } from './utils'\n\ninterface Styles {\n  left: {\n    container: ViewStyle\n    onTop: ViewStyle\n    image: ImageStyle\n  }\n  right: {\n    container: ViewStyle\n    onTop: ViewStyle\n    image: ImageStyle\n  }\n}\n\nconst styles: Styles = {\n  left: StyleSheet.create({\n    container: {\n      marginRight: 8,\n    },\n    onTop: {\n      alignSelf: 'flex-start',\n    },\n    image: {\n      height: 36,\n      width: 36,\n      borderRadius: 18,\n    },\n  }),\n  right: StyleSheet.create({\n    container: {\n      marginLeft: 8,\n    },\n    onTop: {\n      alignSelf: 'flex-start',\n    },\n    image: {\n      height: 36,\n      width: 36,\n      borderRadius: 18,\n    },\n  }),\n}\n\nexport interface AvatarProps<TMessage extends IMessage> {\n  currentMessage: TMessage\n  previousMessage?: TMessage\n  nextMessage?: TMessage\n  position: 'left' | 'right'\n  isAvatarOnTop?: boolean\n  isAvatarVisibleForEveryMessage?: boolean\n  imageStyle?: LeftRightStyle<ImageStyle>\n  containerStyle?: LeftRightStyle<ViewStyle>\n  textStyle?: TextStyle\n  renderAvatar?(props: Omit<AvatarProps<TMessage>, 'renderAvatar'>): ReactNode\n  onPressAvatar?: (user: User) => void\n  onLongPressAvatar?: (user: User) => void\n}\n\nexport function Avatar<TMessage extends IMessage = IMessage> (\n  props: AvatarProps<TMessage>\n) {\n  const {\n    isAvatarOnTop,\n    isAvatarVisibleForEveryMessage,\n    containerStyle,\n    position,\n    currentMessage,\n    renderAvatar,\n    previousMessage,\n    nextMessage,\n    imageStyle,\n    onPressAvatar,\n    onLongPressAvatar,\n  } = props\n\n  const messageToCompare = isAvatarOnTop ? previousMessage : nextMessage\n\n  const renderAvatarComponent = useCallback(() => {\n    if (renderAvatar)\n      return renderAvatar({\n        isAvatarOnTop,\n        isAvatarVisibleForEveryMessage,\n        containerStyle,\n        position,\n        currentMessage,\n        previousMessage,\n        nextMessage,\n        imageStyle,\n        onPressAvatar,\n        onLongPressAvatar,\n      })\n\n    if (currentMessage)\n      return (\n        <GiftedAvatar\n          avatarStyle={[\n            styles[position].image,\n            imageStyle?.[position],\n          ]}\n          user={currentMessage.user}\n          onPress={() => onPressAvatar?.(currentMessage.user)}\n          onLongPress={() => onLongPressAvatar?.(currentMessage.user)}\n        />\n      )\n\n    return null\n  }, [\n    renderAvatar,\n    isAvatarOnTop,\n    isAvatarVisibleForEveryMessage,\n    containerStyle,\n    position,\n    currentMessage,\n    previousMessage,\n    nextMessage,\n    imageStyle,\n    onPressAvatar,\n    onLongPressAvatar,\n  ])\n\n  if (renderAvatar === null)\n    return null\n\n  if (\n    !isAvatarVisibleForEveryMessage &&\n    currentMessage &&\n    messageToCompare &&\n    isSameUser(currentMessage, messageToCompare) &&\n    isSameDay(currentMessage, messageToCompare)\n  )\n    return (\n      <View\n        style={[\n          styles[position].container,\n          containerStyle?.[position],\n        ]}\n      >\n        <GiftedAvatar\n          avatarStyle={[\n            styles[position].image,\n            imageStyle?.[position],\n          ]}\n        />\n      </View>\n    )\n\n  return (\n    <View\n      style={[\n        styles[position].container,\n        isAvatarOnTop && styles[position].onTop,\n        containerStyle?.[position],\n      ]}\n    >\n      {renderAvatarComponent()}\n    </View>\n  )\n}\n"
  },
  {
    "path": "src/Bubble/index.tsx",
    "content": "import React, { useCallback, useMemo } from 'react'\nimport {\n  View,\n  Pressable,\n} from 'react-native'\n\nimport { Text } from 'react-native-gesture-handler'\n\nimport { MessageReply } from '../components/MessageReply'\nimport { useChatContext } from '../GiftedChatContext'\nimport { MessageAudio } from '../MessageAudio'\nimport { MessageImage } from '../MessageImage'\nimport { MessageText } from '../MessageText'\nimport { MessageVideo } from '../MessageVideo'\nimport { IMessage } from '../Models'\nimport { QuickReplies } from '../QuickReplies'\nimport { getStyleWithPosition } from '../styles'\nimport { Time } from '../Time'\nimport { isSameUser, isSameDay, renderComponentOrElement } from '../utils'\nimport styles from './styles'\nimport { BubbleProps, RenderMessageTextProps } from './types'\n\nexport * from './types'\n\nexport const Bubble = <TMessage extends IMessage = IMessage>(props: BubbleProps<TMessage>): React.ReactElement => {\n  const {\n    currentMessage,\n    nextMessage,\n    position,\n    containerToNextStyle,\n    previousMessage,\n    containerToPreviousStyle,\n    onQuickReply,\n    renderQuickReplySend,\n    quickReplyStyle,\n    quickReplyTextStyle,\n    quickReplyContainerStyle,\n    containerStyle,\n    wrapperStyle,\n    bottomContainerStyle,\n    onPressMessage: onPressMessageProp,\n    onLongPressMessage: onLongPressMessageProp,\n  } = props\n\n  const context = useChatContext()\n\n  const onPress = useCallback(() => {\n    onPressMessageProp?.(context, currentMessage)\n  }, [onPressMessageProp, context, currentMessage])\n\n  const onLongPress = useCallback(() => {\n    onLongPressMessageProp?.(context, currentMessage)\n  }, [\n    currentMessage,\n    context,\n    onLongPressMessageProp,\n  ])\n\n  const styledBubbleToNext = useMemo(() => {\n    if (\n      currentMessage &&\n      nextMessage &&\n      position &&\n      isSameUser(currentMessage, nextMessage) &&\n      isSameDay(currentMessage, nextMessage)\n    )\n      return [\n        getStyleWithPosition(styles, 'containerToNext', position),\n        containerToNextStyle?.[position],\n      ]\n\n    return null\n  }, [\n    currentMessage,\n    nextMessage,\n    position,\n    containerToNextStyle,\n  ])\n\n  const styledBubbleToPrevious = useMemo(() => {\n    if (\n      currentMessage &&\n      previousMessage &&\n      position &&\n      isSameUser(currentMessage, previousMessage) &&\n      isSameDay(currentMessage, previousMessage)\n    )\n      return [\n        getStyleWithPosition(styles, 'containerToPrevious', position),\n        containerToPreviousStyle?.[position],\n      ]\n\n    return null\n  }, [\n    currentMessage,\n    previousMessage,\n    position,\n    containerToPreviousStyle,\n  ])\n\n  const renderQuickReplies = useCallback(() => {\n    if (currentMessage?.quickReplies) {\n      const {\n        /* eslint-disable @typescript-eslint/no-unused-vars */\n        containerStyle,\n        wrapperStyle,\n        /* eslint-enable @typescript-eslint/no-unused-vars */\n        ...quickReplyProps\n      } = props\n\n      if (props.renderQuickReplies)\n        return renderComponentOrElement(props.renderQuickReplies, quickReplyProps)\n\n      return (\n        <QuickReplies\n          currentMessage={currentMessage}\n          onQuickReply={onQuickReply}\n          renderQuickReplySend={renderQuickReplySend}\n          quickReplyStyle={quickReplyStyle}\n          quickReplyTextStyle={quickReplyTextStyle}\n          quickReplyContainerStyle={quickReplyContainerStyle}\n          nextMessage={nextMessage}\n        />\n      )\n    }\n\n    return null\n  }, [\n    currentMessage,\n    onQuickReply,\n    renderQuickReplySend,\n    quickReplyStyle,\n    quickReplyTextStyle,\n    quickReplyContainerStyle,\n    nextMessage,\n    props,\n  ])\n\n  const renderMessageText = useCallback(() => {\n    if (currentMessage?.text) {\n      const {\n        /* eslint-disable @typescript-eslint/no-unused-vars */\n        containerStyle,\n        wrapperStyle,\n        messageTextProps,\n        /* eslint-enable @typescript-eslint/no-unused-vars */\n        ...messageTextPropsRest\n      } = props\n\n      const combinedProps = {\n        ...messageTextPropsRest,\n        ...messageTextProps,\n      } as RenderMessageTextProps<TMessage>\n\n      if (props.renderMessageText)\n        return renderComponentOrElement(props.renderMessageText, combinedProps)\n\n      return <MessageText {...combinedProps as any} />\n    }\n\n    return null\n  }, [props, currentMessage])\n\n  const renderMessageImage = useCallback(() => {\n    if (currentMessage?.image) {\n      const {\n        /* eslint-disable @typescript-eslint/no-unused-vars */\n        containerStyle,\n        wrapperStyle,\n        /* eslint-enable @typescript-eslint/no-unused-vars */\n        ...messageImageProps\n      } = props\n\n      if (props.renderMessageImage)\n        return renderComponentOrElement(props.renderMessageImage, messageImageProps)\n\n      return <MessageImage {...messageImageProps} />\n    }\n\n    return null\n  }, [props, currentMessage])\n\n  const renderMessageVideo = useCallback(() => {\n    if (!currentMessage?.video)\n      return null\n\n    const {\n      /* eslint-disable @typescript-eslint/no-unused-vars */\n      containerStyle,\n      wrapperStyle,\n      /* eslint-enable @typescript-eslint/no-unused-vars */\n      ...messageVideoProps\n    } = props\n\n    if (props.renderMessageVideo)\n      return renderComponentOrElement(props.renderMessageVideo, messageVideoProps)\n\n    return <MessageVideo />\n  }, [props, currentMessage])\n\n  const renderMessageAudio = useCallback(() => {\n    if (!currentMessage?.audio)\n      return null\n\n    const {\n      /* eslint-disable @typescript-eslint/no-unused-vars */\n      containerStyle,\n      wrapperStyle,\n      /* eslint-enable @typescript-eslint/no-unused-vars */\n      ...messageAudioProps\n    } = props\n\n    if (props.renderMessageAudio)\n      return renderComponentOrElement(props.renderMessageAudio, messageAudioProps)\n\n    return <MessageAudio />\n  }, [props, currentMessage])\n\n  const renderTicks = useCallback(() => {\n    const {\n      renderTicks,\n      user,\n    } = props\n\n    if (renderTicks && currentMessage)\n      return renderComponentOrElement(renderTicks, currentMessage)\n\n    if (\n      user &&\n      currentMessage?.user &&\n      currentMessage.user._id !== user._id\n    )\n      return null\n\n    if (\n      currentMessage &&\n      (currentMessage.sent || currentMessage.received || currentMessage.pending)\n    )\n      return (\n        <View style={styles.messageStatusContainer}>\n          {!!currentMessage.sent && (\n            <Text style={[styles.messageStatus, props.tickStyle]}>\n              {'✓'}\n            </Text>\n          )}\n          {!!currentMessage.received && (\n            <Text style={[styles.messageStatus, props.tickStyle]}>\n              {'✓'}\n            </Text>\n          )}\n          {!!currentMessage.pending && (\n            <Text style={[styles.messageStatus, props.tickStyle]}>\n              {'🕓'}\n            </Text>\n          )}\n        </View>\n      )\n\n    return null\n  }, [\n    props,\n    currentMessage,\n  ])\n\n  const renderTime = useCallback(() => {\n    if (currentMessage?.createdAt) {\n      const {\n        /* eslint-disable @typescript-eslint/no-unused-vars */\n        containerStyle,\n        wrapperStyle,\n        textStyle,\n        /* eslint-enable @typescript-eslint/no-unused-vars */\n        ...timeProps\n      } = props\n\n      if (props.renderTime)\n        return renderComponentOrElement(props.renderTime, timeProps)\n\n      return <Time {...timeProps} />\n    }\n\n    return null\n  }, [props, currentMessage])\n\n  const renderUsername = useCallback(() => {\n    const {\n      user,\n      renderUsername,\n    } = props\n\n    if (props.isUsernameVisible && currentMessage) {\n      if (user && currentMessage.user._id === user._id)\n        return null\n\n      if (renderUsername)\n        return renderComponentOrElement(renderUsername, currentMessage.user)\n\n      return (\n        <View style={styles.usernameContainer}>\n          <Text\n            style={[styles.username, props.usernameStyle]}\n          >\n            {currentMessage.user.name}\n          </Text>\n        </View>\n      )\n    }\n\n    return null\n  }, [\n    currentMessage,\n    props,\n  ])\n\n  const renderCustomView = useCallback(() => {\n    if (props.renderCustomView)\n      return renderComponentOrElement(props.renderCustomView, props)\n\n    return null\n  }, [props])\n\n  const renderMessageReply = useCallback(() => {\n    if (!currentMessage?.replyMessage)\n      return null\n\n    const { messageReply } = props\n\n    const messageReplyProps = {\n      replyMessage: currentMessage.replyMessage,\n      currentMessage,\n      position,\n      onPress: messageReply?.onPress,\n      containerStyle: position === 'left'\n        ? messageReply?.containerStyleLeft ?? messageReply?.containerStyle\n        : messageReply?.containerStyleRight ?? messageReply?.containerStyle,\n      textStyle: position === 'left'\n        ? messageReply?.textStyleLeft ?? messageReply?.textStyle\n        : messageReply?.textStyleRight ?? messageReply?.textStyle,\n      imageStyle: messageReply?.imageStyle,\n    }\n\n    if (messageReply?.renderMessageReply)\n      return renderComponentOrElement(messageReply.renderMessageReply, messageReplyProps)\n\n    return <MessageReply {...messageReplyProps} />\n  }, [\n    props,\n    currentMessage,\n    position,\n  ])\n\n  const renderBubbleContent = useCallback(() => {\n    return (\n      <>\n        {!props.isCustomViewBottom && renderCustomView()}\n        {renderMessageReply()}\n        {renderMessageImage()}\n        {renderMessageVideo()}\n        {renderMessageAudio()}\n        {renderMessageText()}\n        {props.isCustomViewBottom && renderCustomView()}\n      </>\n    )\n  }, [\n    renderMessageReply,\n    renderCustomView,\n    renderMessageImage,\n    renderMessageVideo,\n    renderMessageAudio,\n    renderMessageText,\n    props.isCustomViewBottom,\n  ])\n\n  return (\n    <View style={containerStyle?.[position]}>\n      <View\n        style={[\n          getStyleWithPosition(styles, 'wrapper', position),\n          styledBubbleToNext,\n          styledBubbleToPrevious,\n          wrapperStyle?.[position],\n        ]}\n      >\n        <Pressable\n          onPress={onPress}\n          onLongPress={onLongPress}\n          {...props.touchableProps}\n        >\n          {renderBubbleContent()}\n          <View\n            style={[\n              styles.bottom,\n              bottomContainerStyle?.[position],\n            ]}\n          >\n            {renderUsername()}\n            <View style={styles.messageTimeAndStatusContainer}>\n              {renderTime()}\n              {renderTicks()}\n            </View>\n          </View>\n        </Pressable>\n      </View>\n      {renderQuickReplies()}\n    </View>\n  )\n}\n"
  },
  {
    "path": "src/Bubble/styles.ts",
    "content": "import { StyleSheet } from 'react-native'\nimport { Color } from '../Color'\n\nconst styles = StyleSheet.create({\n  wrapper: {\n    borderRadius: 15,\n    minHeight: 20,\n  },\n  wrapper_left: {\n    backgroundColor: Color.leftBubbleBackground,\n    justifyContent: 'flex-end',\n  },\n  wrapper_right: {\n    backgroundColor: Color.defaultBlue,\n    justifyContent: 'flex-end',\n  },\n\n  bottom: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n    alignItems: 'flex-end',\n    paddingHorizontal: 10,\n    paddingBottom: 5,\n  },\n\n  containerToNext_left: {\n    borderBottomLeftRadius: 3,\n  },\n  containerToNext_right: {\n    borderBottomRightRadius: 3,\n  },\n\n  containerToPrevious_left: {\n    borderTopLeftRadius: 3,\n  },\n  containerToPrevious_right: {\n    borderTopRightRadius: 3,\n  },\n\n  messageTimeAndStatusContainer: {\n    flexGrow: 1,\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'flex-end',\n  },\n\n  messageStatusContainer: {\n    flexDirection: 'row',\n    marginLeft: 5,\n  },\n  messageStatus: {\n    fontSize: 10,\n    color: Color.white,\n  },\n\n  usernameContainer: {\n    flexDirection: 'row',\n    marginRight: 5,\n  },\n  username: {\n    fontSize: 12,\n    color: '#aaa',\n  },\n})\n\nexport default styles\n"
  },
  {
    "path": "src/Bubble/types.ts",
    "content": "import React, { ComponentProps } from 'react'\nimport {\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n  Pressable,\n} from 'react-native'\n\nimport { MessageReplyProps } from '../components/MessageReply'\nimport { MessageImageProps } from '../MessageImage'\nimport { MessageTextProps } from '../MessageText'\nimport {\n  User,\n  IMessage,\n  LeftRightStyle,\n  Reply,\n  ReplyMessage,\n  Omit,\n  MessageVideoProps,\n  MessageAudioProps,\n} from '../Models'\nimport { QuickRepliesProps } from '../QuickReplies'\nimport { MessageReplyStyleProps } from '../Reply'\nimport { TimeProps } from '../Time'\n\n\nexport type RenderMessageImageProps<TMessage extends IMessage> = Omit<\n  BubbleProps<TMessage>,\n  'containerStyle' | 'wrapperStyle'\n> &\n  MessageImageProps<TMessage>\n\nexport type RenderMessageVideoProps<TMessage extends IMessage> = Omit<\n  BubbleProps<TMessage>,\n  'containerStyle' | 'wrapperStyle'\n> &\n  MessageVideoProps<TMessage>\n\nexport type RenderMessageAudioProps<TMessage extends IMessage> = Omit<\n  BubbleProps<TMessage>,\n  'containerStyle' | 'wrapperStyle'\n> &\n  MessageAudioProps<TMessage>\n\nexport type RenderMessageTextProps<TMessage extends IMessage> = Omit<\n  BubbleProps<TMessage>,\n  'containerStyle' | 'wrapperStyle'\n> &\n  MessageTextProps<TMessage>\n\n/** Props for message reply functionality in bubble */\nexport interface BubbleReplyProps<TMessage extends IMessage> extends MessageReplyStyleProps {\n  /** Custom render for message reply; rendered on top of message content */\n  renderMessageReply?: (props: MessageReplyProps<TMessage>) => React.ReactNode\n  /** Callback when message reply is pressed */\n  onPress?: (replyMessage: ReplyMessage) => void\n}\n\nexport interface BubbleProps<TMessage extends IMessage> {\n  user?: User\n  touchableProps?: ComponentProps<typeof Pressable>\n  isUsernameVisible?: boolean\n  isCustomViewBottom?: boolean\n  isInverted?: boolean\n  position: 'left' | 'right'\n  currentMessage: TMessage\n  nextMessage?: TMessage\n  previousMessage?: TMessage\n  containerStyle?: LeftRightStyle<ViewStyle>\n  wrapperStyle?: LeftRightStyle<ViewStyle>\n  textStyle?: LeftRightStyle<TextStyle>\n  bottomContainerStyle?: LeftRightStyle<ViewStyle>\n  tickStyle?: StyleProp<TextStyle>\n  containerToNextStyle?: LeftRightStyle<ViewStyle>\n  containerToPreviousStyle?: LeftRightStyle<ViewStyle>\n  usernameStyle?: TextStyle\n  quickReplyStyle?: StyleProp<ViewStyle>\n  quickReplyTextStyle?: StyleProp<TextStyle>\n  quickReplyContainerStyle?: StyleProp<ViewStyle>\n  messageTextProps?: Partial<MessageTextProps<TMessage>>\n  onPressMessage?: (context?: unknown, message?: unknown) => void\n  onLongPressMessage?: (context?: unknown, message?: unknown) => void\n  onQuickReply?: (replies: Reply[]) => void\n  renderMessageImage?: (\n    props: RenderMessageImageProps<TMessage>\n  ) => React.ReactNode\n  renderMessageVideo?: (\n    props: RenderMessageVideoProps<TMessage>\n  ) => React.ReactNode\n  renderMessageAudio?: (\n    props: RenderMessageAudioProps<TMessage>\n  ) => React.ReactNode\n  renderMessageText?: (props: RenderMessageTextProps<TMessage>) => React.ReactNode\n  renderCustomView?: (bubbleProps: BubbleProps<TMessage>) => React.ReactNode\n  renderTime?: (timeProps: TimeProps<TMessage>) => React.ReactNode\n  renderTicks?: (currentMessage: TMessage) => React.ReactNode\n  renderUsername?: (user?: TMessage['user']) => React.ReactNode\n  renderQuickReplySend?: () => React.ReactNode\n  renderQuickReplies?: (\n    quickReplies: QuickRepliesProps<TMessage>\n  ) => React.ReactNode\n  /** Message reply configuration */\n  messageReply?: BubbleReplyProps<TMessage>\n}\n"
  },
  {
    "path": "src/Color.ts",
    "content": "export const Color = {\n  defaultColor: '#b2b2b2',\n  backgroundTransparent: 'transparent',\n  defaultBlue: '#0084ff',\n  leftBubbleBackground: '#f0f0f0',\n  black: '#000',\n  white: '#fff',\n  carrot: '#e67e22',\n  emerald: '#2ecc71',\n  peterRiver: '#3498db',\n  wisteria: '#8e44ad',\n  alizarin: '#e74c3c',\n  turquoise: '#1abc9c',\n  midnightBlue: '#2c3e50',\n  optionTintColor: '#007AFF',\n  timeTextColor: '#aaa',\n}\n"
  },
  {
    "path": "src/Composer.tsx",
    "content": "import React, { useCallback, useMemo, useState } from 'react'\nimport {\n  Platform,\n  StyleSheet,\n  TextInputChangeEvent,\n  TextInputContentSizeChangeEvent,\n  TextInputProps,\n  View,\n} from 'react-native'\nimport { TextInput } from 'react-native-gesture-handler'\nimport { Color } from './Color'\nimport { useColorScheme } from './hooks/useColorScheme'\nimport stylesCommon, { getColorSchemeStyle } from './styles'\n\nexport interface ComposerProps {\n  composerHeight?: number\n  text?: string\n  textInputProps?: Partial<TextInputProps>\n}\n\nexport function Composer ({\n  text = '',\n  textInputProps,\n}: ComposerProps): React.ReactElement {\n  const colorScheme = useColorScheme()\n  const isDark = colorScheme === 'dark'\n\n  const placeholder = textInputProps?.placeholder ?? 'Type a message...'\n\n  const minHeight = useMemo(() =>\n    Platform.select({\n      web: styles.textInput.lineHeight + styles.textInput.paddingTop + styles.textInput.paddingBottom,\n      default: undefined,\n    })\n  , [])\n\n  const [height, setHeight] = useState<number | undefined>(minHeight)\n\n  const handleContentSizeChange = useMemo(() => {\n    if (Platform.OS === 'web')\n      return (e: TextInputContentSizeChangeEvent) => {\n        const contentHeight = e.nativeEvent.contentSize.height\n        setHeight(Math.max(minHeight ?? 0, contentHeight))\n      }\n\n    return undefined\n  }, [minHeight])\n\n  const handleChange = useCallback((event: TextInputChangeEvent) => {\n    if (Platform.OS === 'web')\n      // Reset height to 0 to get the correct scrollHeight\n      requestAnimationFrame(() => {\n        // @ts-expect-error - web-specific code\n        event.nativeEvent.target.style.height = '0px'\n        // @ts-expect-error - web-specific code\n        event.nativeEvent.target.style.height = `${event.nativeEvent.target.scrollHeight}px`\n      })\n  }, [])\n\n  return (\n    <View style={stylesCommon.fill}>\n      <TextInput\n        testID={placeholder}\n        accessible\n        accessibilityLabel={placeholder}\n        placeholderTextColor={textInputProps?.placeholderTextColor ?? (isDark ? '#888' : Color.defaultColor)}\n        value={text}\n        enablesReturnKeyAutomatically\n        underlineColorAndroid='transparent'\n        keyboardAppearance={isDark ? 'dark' : 'default'}\n        multiline\n        placeholder={placeholder}\n        onContentSizeChange={handleContentSizeChange}\n        onChange={handleChange}\n        {...textInputProps}\n        style={[getColorSchemeStyle(styles, 'textInput', colorScheme), stylesWeb.textInput, { height }, textInputProps?.style]}\n      />\n    </View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  textInput: {\n    fontSize: 16,\n    lineHeight: 22,\n    paddingTop: 8,\n    paddingBottom: 10,\n    paddingHorizontal: 8,\n  },\n  textInput_dark: {\n    color: '#fff',\n  },\n})\n\nconst stylesWeb = StyleSheet.create({\n  textInput: {\n    /* @ts-expect-error - web-specific styles */\n    outlineStyle: 'none',\n  },\n})\n"
  },
  {
    "path": "src/Constant.ts",
    "content": "export const DATE_FORMAT = 'D MMMM'\nexport const TIME_FORMAT = 'LT'\n\nexport const TEST_ID = {\n  WRAPPER: 'GC_WRAPPER',\n  LOADING_WRAPPER: 'GC_LOADING_CONTAINER',\n  SEND_TOUCHABLE: 'GC_SEND_TOUCHABLE',\n}\n"
  },
  {
    "path": "src/Day/index.tsx",
    "content": "import React, { useMemo } from 'react'\nimport {\n  View,\n} from 'react-native'\nimport dayjs from 'dayjs'\nimport calendar from 'dayjs/plugin/calendar'\nimport relativeTime from 'dayjs/plugin/relativeTime'\n\nimport { Text } from 'react-native-gesture-handler'\nimport { DATE_FORMAT } from '../Constant'\n\nimport { useChatContext } from '../GiftedChatContext'\nimport stylesCommon from '../styles'\nimport styles from './styles'\nimport { DayProps } from './types'\n\nexport * from './types'\n\ndayjs.extend(relativeTime)\ndayjs.extend(calendar)\n\nexport function Day ({\n  dateFormat = DATE_FORMAT,\n  dateFormatCalendar,\n  createdAt,\n  containerStyle,\n  wrapperStyle,\n  textProps,\n}: DayProps) {\n  const { getLocale } = useChatContext()\n\n  const dateStr = useMemo(() => {\n    if (createdAt == null)\n      return null\n\n    const now = dayjs().startOf('day')\n    const date = dayjs(createdAt).locale(getLocale()).startOf('day')\n\n    if (!now.isSame(date, 'year'))\n      return date.format('D MMMM YYYY')\n\n    if (now.diff(date, 'days') < 1)\n      return date.calendar(now, {\n        sameDay: '[Today]',\n        ...dateFormatCalendar,\n      })\n\n    return date.format(dateFormat)\n  }, [createdAt, dateFormat, getLocale, dateFormatCalendar])\n\n  if (!dateStr)\n    return null\n\n  return (\n    <View style={[stylesCommon.centerItems, styles.container, containerStyle]}>\n      <View style={[styles.wrapper, wrapperStyle]}>\n        <Text {...textProps} style={[styles.text, textProps?.style]}>\n          {dateStr}\n        </Text>\n      </View>\n    </View>\n  )\n}\n"
  },
  {
    "path": "src/Day/styles.ts",
    "content": "import { StyleSheet } from 'react-native'\nimport { Color } from '../Color'\n\nexport default StyleSheet.create({\n  container: {\n    marginTop: 5,\n    marginBottom: 10,\n  },\n  wrapper: {\n    backgroundColor: 'rgba(0, 0, 0, 0.75)',\n    paddingTop: 6,\n    paddingBottom: 6,\n    paddingLeft: 10,\n    paddingRight: 10,\n    borderRadius: 15,\n  },\n  text: {\n    color: Color.white,\n    fontSize: 12,\n    fontWeight: '600',\n  },\n})\n"
  },
  {
    "path": "src/Day/types.ts",
    "content": "import {\n  StyleProp,\n  ViewStyle,\n  TextProps,\n} from 'react-native'\n\nexport interface DayProps {\n  createdAt: Date | number\n  dateFormat?: string\n  dateFormatCalendar?: object\n  containerStyle?: StyleProp<ViewStyle>\n  wrapperStyle?: StyleProp<ViewStyle>\n  /** Props to pass to the Text component (e.g., style, allowFontScaling, numberOfLines) */\n  textProps?: Partial<TextProps>\n}\n"
  },
  {
    "path": "src/GiftedAvatar.tsx",
    "content": "import React, { useCallback, useMemo } from 'react'\nimport {\n  Image,\n  View,\n  StyleSheet,\n  StyleProp,\n  ImageStyle,\n  TextStyle,\n} from 'react-native'\nimport { Text } from 'react-native-gesture-handler'\nimport { Color } from './Color'\nimport { TouchableOpacity } from './components/TouchableOpacity'\nimport { User } from './Models'\nimport stylesCommon from './styles'\n\nconst {\n  carrot,\n  emerald,\n  peterRiver,\n  wisteria,\n  alizarin,\n  turquoise,\n  midnightBlue,\n} = Color\n\nconst styles = StyleSheet.create({\n  avatarStyle: {\n    width: 40,\n    height: 40,\n    borderRadius: 20,\n  },\n  avatarTransparent: {\n    backgroundColor: Color.backgroundTransparent,\n  },\n  textStyle: {\n    color: Color.white,\n    fontSize: 16,\n    backgroundColor: Color.backgroundTransparent,\n    fontWeight: '100',\n  },\n})\n\nexport interface GiftedAvatarProps {\n  user?: User\n  avatarStyle?: StyleProp<ImageStyle>\n  textStyle?: StyleProp<TextStyle>\n  onPress?: (props: GiftedAvatarProps) => void\n  onLongPress?: (props: GiftedAvatarProps) => void\n}\n\nexport function GiftedAvatar (\n  props: GiftedAvatarProps\n) {\n  const {\n    user,\n    avatarStyle,\n    textStyle,\n    onPress,\n  } = props\n\n  const avatarName = useMemo(() => {\n    const userName = user?.name || ''\n    const name = userName.toUpperCase().split(' ')\n\n    if (name.length === 1)\n      return `${name[0].charAt(0)}`\n    else if (name.length > 1)\n      return `${name[0].charAt(0)}${name[1].charAt(0)}`\n    else\n      return ''\n  }, [user?.name])\n\n  const backgroundColor = useMemo(() => {\n    let sumChars = 0\n    if (user?.name)\n      for (let i = 0; i < user.name.length; i += 1)\n        sumChars += user.name.charCodeAt(i)\n\n    // inspired by https://github.com/wbinnssmith/react-user-avatar\n    // colors from https://flatuicolors.com/\n    const colors = [\n      carrot,\n      emerald,\n      peterRiver,\n      wisteria,\n      alizarin,\n      turquoise,\n      midnightBlue,\n    ]\n\n    return colors[sumChars % colors.length]\n  }, [user?.name])\n\n  const renderAvatar = useCallback(() => {\n    switch (typeof user?.avatar) {\n      case 'function':\n        return user.avatar([stylesCommon.centerItems, styles.avatarStyle, avatarStyle])\n      case 'string':\n        return (\n          <Image\n            source={{ uri: user.avatar }}\n            style={[stylesCommon.centerItems, styles.avatarStyle, avatarStyle]}\n          />\n        )\n      case 'number':\n        return (\n          <Image\n            source={user.avatar}\n            style={[stylesCommon.centerItems, styles.avatarStyle, avatarStyle]}\n          />\n        )\n      default:\n        return null\n    }\n  }, [user, avatarStyle])\n\n  const renderInitials = useCallback(() => {\n    return (\n      <Text style={[styles.textStyle, textStyle]}>\n        {avatarName}\n      </Text>\n    )\n  }, [textStyle, avatarName])\n\n  const handleOnPress = useCallback(() => {\n    const {\n      onPress,\n      ...rest\n    } = props\n\n    onPress?.(rest)\n  }, [props])\n\n  const handleOnLongPress = useCallback(() => {\n    const {\n      onLongPress,\n      ...rest\n    } = props\n\n    if (onLongPress)\n      onLongPress(rest)\n  }, [props])\n\n  const placeholderView = useMemo(() => (\n    <View\n      style={[\n        stylesCommon.centerItems,\n        styles.avatarStyle,\n        styles.avatarTransparent,\n        avatarStyle,\n      ]}\n      accessibilityRole='image'\n    />\n  ), [avatarStyle])\n\n  if (!user || (!user.name && !user.avatar))\n    return placeholderView\n\n  if (user.avatar)\n    return (\n      <TouchableOpacity\n        enabled={!!onPress}\n        onPress={handleOnPress}\n        onLongPress={handleOnLongPress}\n        accessibilityRole='image'\n      >\n        {renderAvatar()}\n      </TouchableOpacity>\n    )\n\n  return (\n    <TouchableOpacity\n      enabled={!!onPress}\n      onPress={handleOnPress}\n      onLongPress={handleOnLongPress}\n      style={[\n        stylesCommon.centerItems,\n        styles.avatarStyle,\n        { backgroundColor },\n        avatarStyle,\n      ]}\n      accessibilityRole='image'\n    >\n      {renderInitials()}\n    </TouchableOpacity>\n  )\n}\n"
  },
  {
    "path": "src/GiftedChat/index.tsx",
    "content": "import React, {\n  createRef,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  useCallback,\n  RefObject,\n} from 'react'\nimport {\n  View,\n  LayoutChangeEvent,\n  useColorScheme,\n} from 'react-native'\nimport {\n  ActionSheetProvider,\n  ActionSheetProviderRef,\n} from '@expo/react-native-action-sheet'\nimport dayjs from 'dayjs'\nimport localizedFormat from 'dayjs/plugin/localizedFormat'\nimport { GestureHandlerRootView, TextInput } from 'react-native-gesture-handler'\nimport { KeyboardAvoidingView, KeyboardProvider } from 'react-native-keyboard-controller'\nimport { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'\nimport { TEST_ID } from '../Constant'\nimport { GiftedChatContext } from '../GiftedChatContext'\nimport { InputToolbar } from '../InputToolbar'\nimport { MessagesContainer, AnimatedList } from '../MessagesContainer'\nimport { IMessage, ReplyMessage } from '../Models'\nimport stylesCommon from '../styles'\nimport { renderComponentOrElement } from '../utils'\nimport styles from './styles'\nimport { GiftedChatProps } from './types'\n\ndayjs.extend(localizedFormat)\n\nfunction GiftedChat<TMessage extends IMessage = IMessage> (\n  props: GiftedChatProps<TMessage>\n) {\n  const {\n    messages = [],\n    initialText = '',\n    isTyping,\n\n    // \"random\" function from here: https://stackoverflow.com/a/8084248/3452513\n    // we do not use uuid since it would add extra native dependency (https://www.npmjs.com/package/react-native-get-random-values)\n    // lib's user can decide which algorithm to use and pass it as a prop\n    messageIdGenerator = () => (Math.random() + 1).toString(36).substring(7),\n\n    user = {},\n    onSend,\n    locale = 'en',\n    colorScheme: colorSchemeProp,\n    renderLoading,\n    actionSheet,\n    textInputProps,\n    renderChatFooter,\n    renderInputToolbar,\n    isInverted = true,\n\n    // Reply props\n    reply,\n  } = props\n\n  // Extract reply props for internal use\n  const replyMessageProp = reply?.message\n  const onClearReply = reply?.onClear\n  const onSwipeToReply = reply?.swipe?.onSwipe\n  const renderReplyPreview = reply?.renderPreview\n  const replyPreviewContainerStyle = reply?.previewStyle?.containerStyle\n  const replyPreviewTextStyle = reply?.previewStyle?.textStyle\n\n  const systemColorScheme = useColorScheme()\n  const colorScheme = colorSchemeProp !== undefined ? colorSchemeProp : systemColorScheme\n\n  const actionSheetRef = useRef<ActionSheetProviderRef>(null)\n\n  const insets = useSafeAreaInsets()\n\n  const messagesContainerRef = useMemo(\n    () => props.messagesContainerRef || createRef<AnimatedList<TMessage>>(),\n    [props.messagesContainerRef]\n  ) as RefObject<AnimatedList<TMessage>>\n\n  const textInputRef = useMemo(\n    () => props.textInputRef || createRef<TextInput>(),\n    [props.textInputRef]\n  )\n\n  const [isInitialized, setIsInitialized] = useState<boolean>(false)\n  const [text, setText] = useState<string | undefined>(() => props.text || '')\n  const [internalReplyMessage, setInternalReplyMessage] = useState<ReplyMessage | null>(null)\n\n  // Use controlled or uncontrolled reply state\n  const replyMessage = replyMessageProp !== undefined ? replyMessageProp : internalReplyMessage\n\n  const getTextFromProp = useCallback(\n    (fallback: string) => {\n      if (props.text === undefined)\n        return fallback\n\n      return props.text\n    },\n    [props.text]\n  )\n\n  const scrollToBottom = useCallback(\n    (isAnimated = true) => {\n      if (!messagesContainerRef?.current)\n        return\n\n      if (isInverted) {\n        messagesContainerRef.current.scrollToOffset({\n          offset: 0,\n          animated: isAnimated,\n        })\n        return\n      }\n\n      messagesContainerRef.current.scrollToEnd({ animated: isAnimated })\n    },\n    [isInverted, messagesContainerRef]\n  )\n\n  const handleSwipeToReply = useCallback(\n    (message: TMessage) => {\n      if (replyMessageProp === undefined)\n        // Uncontrolled mode: manage state internally\n        setInternalReplyMessage({\n          _id: message._id,\n          text: message.text,\n          user: message.user,\n          image: message.image,\n          audio: message.audio,\n        })\n\n      onSwipeToReply?.(message)\n    },\n    [replyMessageProp, onSwipeToReply]\n  )\n\n  const clearReply = useCallback(() => {\n    if (replyMessageProp === undefined)\n      // Uncontrolled mode: manage state internally\n      setInternalReplyMessage(null)\n\n    onClearReply?.()\n  }, [replyMessageProp, onClearReply])\n\n  const renderMessages = useMemo(() => {\n    if (!isInitialized)\n      return null\n\n    const { messagesContainerStyle, ...messagesContainerProps } = props\n\n    return (\n      <View style={[stylesCommon.fill, messagesContainerStyle]}>\n        <MessagesContainer<TMessage>\n          {...messagesContainerProps}\n          isInverted={isInverted}\n          messages={messages}\n          forwardRef={messagesContainerRef}\n          isTyping={isTyping}\n          reply={{\n            ...reply,\n            swipe: reply?.swipe ? {\n              ...reply.swipe,\n              onSwipe: handleSwipeToReply,\n            } : undefined,\n          }}\n        />\n        {renderComponentOrElement(renderChatFooter, {})}\n      </View>\n    )\n  }, [\n    isInitialized,\n    isTyping,\n    messages,\n    props,\n    isInverted,\n    messagesContainerRef,\n    renderChatFooter,\n    reply,\n    handleSwipeToReply,\n  ])\n\n  const notifyInputTextReset = useCallback(() => {\n    props.textInputProps?.onChangeText?.('')\n  }, [props.textInputProps])\n\n  const resetInputToolbar = useCallback(() => {\n    textInputRef.current?.clear()\n\n    notifyInputTextReset()\n\n    setText(getTextFromProp(''))\n  }, [\n    getTextFromProp,\n    textInputRef,\n    notifyInputTextReset,\n  ])\n\n  const _onSend = useCallback(\n    (messages: TMessage[] = [], shouldResetInputToolbar = false) => {\n      if (!Array.isArray(messages))\n        messages = [messages]\n\n      const newMessages: TMessage[] = messages.map(message => {\n        return {\n          ...message,\n          user: user!,\n          createdAt: new Date(),\n          _id: messageIdGenerator?.(),\n          // Attach reply message if exists\n          ...(replyMessage ? { replyMessage } : {}),\n        }\n      })\n\n      if (shouldResetInputToolbar === true)\n        resetInputToolbar()\n\n      // Clear reply after sending\n      clearReply()\n\n      onSend?.(newMessages)\n\n      setTimeout(() => scrollToBottom(), 10)\n    },\n    [messageIdGenerator, onSend, user, resetInputToolbar, scrollToBottom, replyMessage, clearReply]\n  )\n\n  const _onChangeText = useCallback(\n    (text: string) => {\n      props.textInputProps?.onChangeText?.(text)\n\n      // Only set state if it's not being overridden by a prop.\n      if (props.text === undefined)\n        setText(text)\n    },\n    [props.text, props.textInputProps]\n  )\n\n  const onInitialLayoutViewLayout = useCallback(\n    (e: LayoutChangeEvent) => {\n      if (isInitialized)\n        return\n\n      const { layout } = e.nativeEvent\n\n      if (layout.height <= 0)\n        return\n\n      notifyInputTextReset()\n\n      setIsInitialized(true)\n      setText(getTextFromProp(initialText))\n    },\n    [isInitialized, initialText, notifyInputTextReset, getTextFromProp]\n  )\n\n  const inputToolbarFragment = useMemo(() => {\n    if (!isInitialized)\n      return null\n\n    const inputToolbarProps = {\n      ...props,\n      text: getTextFromProp(text!),\n      onSend: _onSend,\n      textInputProps: {\n        ...textInputProps,\n        onChangeText: _onChangeText,\n        ref: textInputRef,\n      },\n      // Reply preview props\n      replyMessage,\n      onClearReply: clearReply,\n      renderReplyPreview,\n      replyPreviewContainerStyle,\n      replyPreviewTextStyle,\n    }\n\n    if (renderInputToolbar)\n      return renderComponentOrElement(renderInputToolbar, inputToolbarProps)\n\n    return <InputToolbar {...inputToolbarProps} />\n  }, [\n    isInitialized,\n    _onSend,\n    getTextFromProp,\n    props,\n    text,\n    renderInputToolbar,\n    textInputRef,\n    textInputProps,\n    _onChangeText,\n    replyMessage,\n    clearReply,\n    renderReplyPreview,\n    replyPreviewContainerStyle,\n    replyPreviewTextStyle,\n  ])\n\n  const contextValues = useMemo(\n    () => ({\n      actionSheet:\n        actionSheet ||\n        (() => ({\n          showActionSheetWithOptions:\n            actionSheetRef.current!.showActionSheetWithOptions,\n        })),\n      getLocale: () => locale,\n      getColorScheme: () => colorScheme,\n    }),\n    [actionSheet, locale, colorScheme]\n  )\n\n  useEffect(() => {\n    if (props.text != null)\n      setText(props.text)\n  }, [props.text])\n\n  return (\n    <GiftedChatContext.Provider value={contextValues}>\n      <ActionSheetProvider ref={actionSheetRef}>\n        <View\n          testID={TEST_ID.WRAPPER}\n          style={[stylesCommon.fill, styles.contentContainer]}\n          onLayout={onInitialLayoutViewLayout}\n        >\n          {/* @ts-expect-error */}\n          <KeyboardAvoidingView\n            behavior='translate-with-padding'\n            keyboardVerticalOffset={insets.top}\n            style={stylesCommon.fill}\n            {...props.keyboardAvoidingViewProps}\n          >\n            <View style={[stylesCommon.fill, !isInitialized && styles.hidden]}>\n              {renderMessages}\n              {inputToolbarFragment}\n            </View>\n            {!isInitialized && renderComponentOrElement(renderLoading, {})}\n          </KeyboardAvoidingView>\n        </View>\n      </ActionSheetProvider>\n    </GiftedChatContext.Provider>\n  )\n}\n\nfunction GiftedChatWrapper<TMessage extends IMessage = IMessage> (props: GiftedChatProps<TMessage>) {\n  const {\n    keyboardProviderProps,\n    ...rest\n  } = props\n\n  return (\n    <GestureHandlerRootView style={styles.fill}>\n      <SafeAreaProvider>\n        <KeyboardProvider\n          statusBarTranslucent\n          navigationBarTranslucent\n          {...keyboardProviderProps}\n        >\n          <GiftedChat<TMessage> {...rest} />\n        </KeyboardProvider>\n      </SafeAreaProvider>\n    </GestureHandlerRootView>\n  )\n}\n\nGiftedChatWrapper.append = <TMessage extends IMessage>(\n  currentMessages: TMessage[] = [],\n  messages: TMessage[],\n  isInverted = true\n) => {\n  if (!Array.isArray(messages))\n    messages = [messages]\n\n  return isInverted\n    ? messages.concat(currentMessages)\n    : currentMessages.concat(messages)\n}\n\nGiftedChatWrapper.prepend = <TMessage extends IMessage>(\n  currentMessages: TMessage[] = [],\n  messages: TMessage[],\n  isInverted = true\n) => {\n  if (!Array.isArray(messages))\n    messages = [messages]\n\n  return isInverted\n    ? currentMessages.concat(messages)\n    : messages.concat(currentMessages)\n}\n\nexport {\n  GiftedChatWrapper as GiftedChat\n}\n"
  },
  {
    "path": "src/GiftedChat/styles.ts",
    "content": "import { StyleSheet } from 'react-native'\n\nexport default StyleSheet.create({\n  fill: {\n    flex: 1,\n  },\n  contentContainer: {\n    overflow: 'hidden',\n  },\n  hidden: {\n    opacity: 0,\n  },\n})\n"
  },
  {
    "path": "src/GiftedChat/types.ts",
    "content": "import React, { RefObject } from 'react'\nimport {\n  TextInput,\n  StyleProp,\n  TextStyle,\n  ViewStyle,\n} from 'react-native'\nimport {\n  ActionSheetOptions,\n} from '@expo/react-native-action-sheet'\nimport { KeyboardProvider, KeyboardAvoidingViewProps } from 'react-native-keyboard-controller'\n\nimport { ActionsProps } from '../Actions'\nimport { AvatarProps } from '../Avatar'\nimport { BubbleProps } from '../Bubble'\nimport { ComposerProps } from '../Composer'\nimport { InputToolbarProps } from '../InputToolbar'\nimport { MessageImageProps } from '../MessageImage'\nimport { AnimatedList, MessagesContainerProps } from '../MessagesContainer'\nimport { MessageTextProps } from '../MessageText'\nimport {\n  IMessage,\n  LeftRightStyle,\n  MessageAudioProps,\n  MessageVideoProps,\n  User,\n} from '../Models'\nimport { QuickRepliesProps } from '../QuickReplies'\nimport { ReplyProps } from '../Reply'\nimport { SendProps } from '../Send'\nimport { SystemMessageProps } from '../SystemMessage'\nimport { TimeProps } from '../Time'\n\nexport interface GiftedChatProps<TMessage extends IMessage> extends Partial<Omit<MessagesContainerProps<TMessage>, 'messageReplyContainerStyle'>> {\n  /* Messages container ref */\n  messagesContainerRef?: RefObject<AnimatedList<TMessage>>\n  /* text input ref */\n  textInputRef?: RefObject<TextInput>\n  /* Controls whether or not to show user.name property in the message bubble */\n  isUsernameVisible?: boolean\n  /* Messages container style */\n  messagesContainerStyle?: StyleProp<ViewStyle>\n  /* Input text; default is undefined, but if specified, it will override GiftedChat's internal state */\n  text?: string\n  initialText?: string\n  /* User sending the messages: { _id, name, avatar } */\n  user?: User\n  /*  Locale to localize the dates */\n  locale?: string\n  /* Force color scheme (light/dark); default is undefined (uses system color scheme) */\n  colorScheme?: 'light' | 'dark'\n  /* Format to use for rendering times; default is 'LT' */\n  timeFormat?: string\n  /* Format to use for rendering dates; default is 'll' */\n  dateFormat?: string\n  /* Format to use for rendering relative times; Today - for now. See more: https://day.js.org/docs/en/plugin/calendar */\n  dateFormatCalendar?: object\n  /* Whether to render an avatar for the current user; default is false, only show avatars for other users */\n  isUserAvatarVisible?: boolean\n  /* When false, avatars will only be displayed when a consecutive message is from the same user on the same day; default is false */\n  isAvatarVisibleForEveryMessage?: boolean\n  /* Render the message avatar at the top of consecutive messages, rather than the bottom; default is false */\n  isAvatarOnTop?: boolean\n  /* Extra props to be passed to the <Image> component created by the default renderMessageImage */\n  imageProps?: MessageImageProps<TMessage>\n  /* Minimum height of the input toolbar; default is 44 */\n  minInputToolbarHeight?: number\n  /*  Extra props to be passed to the <TextInput>. See https://reactnative.dev/docs/textinput */\n  textInputProps?: Partial<React.ComponentProps<typeof TextInput>>\n  /* Force send button */\n  isSendButtonAlwaysVisible?: boolean\n  /* Image style */\n  imageStyle?: StyleProp<ViewStyle>\n  /* composer min Height */\n  minComposerHeight?: number\n  /* composer min Height */\n  maxComposerHeight?: number\n  actions?: Array<{ title: string, action: () => void }>\n  actionSheetOptionTintColor?: string\n  quickReplyStyle?: StyleProp<ViewStyle>\n  quickReplyTextStyle?: StyleProp<TextStyle>\n  quickReplyContainerStyle?: StyleProp<ViewStyle>\n  /* optional prop used to place customView below text, image and video views; default is false */\n  isCustomViewBottom?: boolean\n  timeTextStyle?: LeftRightStyle<TextStyle>\n  /* Custom action sheet */\n  actionSheet?: () => {\n    showActionSheetWithOptions: (\n      options: ActionSheetOptions,\n      callback: (buttonIndex: number) => void | Promise<void>\n    ) => void\n  }\n  /* Callback when a message avatar is tapped */\n  onPressAvatar?: (user: User) => void\n  /* Callback when a message avatar is tapped */\n  onLongPressAvatar?: (user: User) => void\n  /* Generate an id for new messages. Defaults to a simple random string generator */\n  messageIdGenerator?: (message?: TMessage) => string\n  /* Callback when sending a message */\n  onSend?: (messages: TMessage[]) => void\n  /*  Render a loading view when initializing */\n  renderLoading?: () => React.ReactNode\n  /* Custom message avatar; set to null to not render any avatar for the message */\n  renderAvatar?: null | ((props: AvatarProps<TMessage>) => React.ReactNode)\n  /* Custom message bubble */\n  renderBubble?: (props: BubbleProps<TMessage>) => React.ReactNode\n  /* Custom system message */\n  renderSystemMessage?: (props: SystemMessageProps<TMessage>) => React.ReactNode\n  /* Callback when a message bubble is pressed; default is to do nothing */\n  onPressMessage?: (context: unknown, message: TMessage) => void\n  /* Callback when a message bubble is long-pressed; default is to show an ActionSheet with \"Copy Text\" (see example using showActionSheetWithOptions()) */\n  onLongPressMessage?: (context: unknown, message: TMessage) => void\n  /* Custom Username container */\n  renderUsername?: (user: User) => React.ReactNode\n  /* Reverses display order of messages; default is true */\n  /* Custom message text */\n  renderMessageText?: (messageText: MessageTextProps<TMessage>) => React.ReactNode\n  /* Custom message image */\n  renderMessageImage?: (props: MessageImageProps<TMessage>) => React.ReactNode\n  /* Custom message video */\n  renderMessageVideo?: (props: MessageVideoProps<TMessage>) => React.ReactNode\n  /* Custom message audio */\n  renderMessageAudio?: (props: MessageAudioProps<TMessage>) => React.ReactNode\n  /* Custom view inside the bubble */\n  renderCustomView?: (props: BubbleProps<TMessage>) => React.ReactNode\n  /* Custom time inside a message */\n  renderTime?: (props: TimeProps<TMessage>) => React.ReactNode\n  /* Custom component to render below the MessagesContainer */\n  renderChatFooter?: () => React.ReactNode\n  /* Custom message composer container. Can be a component, element, render function, or null */\n  renderInputToolbar?: React.ComponentType<InputToolbarProps<TMessage>> | React.ReactElement | ((props: InputToolbarProps<TMessage>) => React.ReactNode) | null\n  /*  Custom text input message composer */\n  renderComposer?: (props: ComposerProps) => React.ReactNode\n  /* Custom action button on the left of the message composer */\n  renderActions?: (props: ActionsProps) => React.ReactNode\n  /* Custom send button; you can pass children to the original Send component quite easily, for example to use a custom icon (example) */\n  renderSend?: (props: SendProps<TMessage>) => React.ReactNode\n  /* Custom second line of actions below the message composer */\n  renderAccessory?: (props: InputToolbarProps<TMessage>) => React.ReactNode\n  /* Callback when the Action button is pressed (if set, the default actionSheet will not be used) */\n  onPressActionButton?: () => void\n  /* Extra props to be passed to the MessageText component */\n  messageTextProps?: Partial<MessageTextProps<TMessage>>\n  renderQuickReplies?: (\n    quickReplies: QuickRepliesProps<TMessage>\n  ) => React.ReactNode\n  renderQuickReplySend?: () => React.ReactNode\n  keyboardProviderProps?: React.ComponentProps<typeof KeyboardProvider>\n  /** Props for KeyboardAvoidingView. Use `keyboardVerticalOffset` to account for headers or iOS predictive text bar (~44pt). */\n  keyboardAvoidingViewProps?: KeyboardAvoidingViewProps\n  /** Enable animated day label that appears on scroll; default is true */\n  isDayAnimationEnabled?: boolean\n\n  /** Reply functionality configuration */\n  reply?: ReplyProps<TMessage>\n}\n"
  },
  {
    "path": "src/GiftedChatContext.ts",
    "content": "import { createContext, useContext } from 'react'\nimport {\n  ActionSheetOptions,\n} from '@expo/react-native-action-sheet'\n\nexport interface IGiftedChatContext {\n  actionSheet(): {\n    showActionSheetWithOptions: (\n      options: ActionSheetOptions,\n      callback: (buttonIndex?: number) => void | Promise<void>\n    ) => void\n  }\n  getLocale(): string\n  getColorScheme(): 'light' | 'dark' | null | undefined\n}\n\nexport const GiftedChatContext = createContext<IGiftedChatContext>({\n  getLocale: () => 'en',\n  actionSheet: () => ({\n    showActionSheetWithOptions: () => {},\n  }),\n  getColorScheme: () => undefined,\n})\n\nexport const useChatContext = () => useContext(GiftedChatContext)\n"
  },
  {
    "path": "src/InputToolbar.tsx",
    "content": "import React, { useCallback, useMemo } from 'react'\nimport { StyleSheet, View, StyleProp, ViewStyle, TextStyle } from 'react-native'\n\nimport { Actions, ActionsProps } from './Actions'\nimport { Color } from './Color'\nimport { ReplyPreview, ReplyPreviewProps } from './components/ReplyPreview'\nimport { Composer, ComposerProps } from './Composer'\nimport { useColorScheme } from './hooks/useColorScheme'\nimport { IMessage, ReplyMessage } from './Models'\nimport { Send, SendProps } from './Send'\nimport { renderComponentOrElement } from './utils'\n\nexport type { ReplyPreviewProps } from './components/ReplyPreview'\n\nexport interface InputToolbarProps<TMessage extends IMessage> {\n  actions?: Array<{ title: string, action: () => void }>\n  actionSheetOptionTintColor?: string\n  containerStyle?: StyleProp<ViewStyle>\n  primaryStyle?: StyleProp<ViewStyle>\n  renderAccessory?: (props: InputToolbarProps<TMessage>) => React.ReactNode\n  renderActions?: (props: ActionsProps) => React.ReactNode\n  renderSend?: (props: SendProps<TMessage>) => React.ReactNode\n  renderComposer?: (props: ComposerProps) => React.ReactNode\n  onPressActionButton?: () => void\n  icon?: () => React.ReactNode\n  wrapperStyle?: StyleProp<ViewStyle>\n  /** Reply message to show in preview */\n  replyMessage?: ReplyMessage | null\n  /** Callback to clear reply */\n  onClearReply?: () => void\n  /** Custom render for reply preview */\n  renderReplyPreview?: (props: ReplyPreviewProps) => React.ReactNode\n  /** Style for reply preview container */\n  replyPreviewContainerStyle?: StyleProp<ViewStyle>\n  /** Style for reply preview text */\n  replyPreviewTextStyle?: StyleProp<TextStyle>\n}\n\nexport function InputToolbar<TMessage extends IMessage = IMessage> (\n  props: InputToolbarProps<TMessage>\n) {\n  const {\n    renderActions,\n    onPressActionButton,\n    renderComposer,\n    renderSend,\n    renderAccessory,\n    actions,\n    actionSheetOptionTintColor,\n    icon,\n    wrapperStyle,\n    containerStyle,\n    replyMessage,\n    onClearReply,\n    renderReplyPreview: renderReplyPreviewProp,\n    replyPreviewContainerStyle,\n    replyPreviewTextStyle,\n  } = props\n\n  const colorScheme = useColorScheme()\n\n  const containerStyles = useMemo(() => [\n    styles.container,\n    colorScheme === 'dark' && styles.container_dark,\n    containerStyle,\n  ], [colorScheme, containerStyle])\n\n  const primaryStyles = useMemo(() => [\n    styles.primary,\n    props.primaryStyle,\n  ], [props.primaryStyle])\n\n  const actionsFragment = useMemo(() => {\n    const actionsProps = {\n      onPressActionButton,\n      actions,\n      actionSheetOptionTintColor,\n      icon,\n      wrapperStyle,\n      containerStyle,\n    }\n\n    if (renderActions)\n      return renderComponentOrElement(renderActions, actionsProps)\n\n    if (onPressActionButton)\n      return <Actions {...actionsProps} />\n\n    return null\n  }, [\n    renderActions,\n    onPressActionButton,\n    actions,\n    actionSheetOptionTintColor,\n    icon,\n    wrapperStyle,\n    containerStyle,\n  ])\n\n  const composerFragment = useMemo(() => {\n    const composerProps = props as ComposerProps\n\n    if (renderComposer)\n      return renderComponentOrElement(renderComposer, composerProps)\n\n    return <Composer {...composerProps} />\n  }, [renderComposer, props])\n\n  const sendFragment = useMemo(() => {\n    if (renderSend)\n      return renderComponentOrElement(renderSend, props)\n\n    return <Send {...props} />\n  }, [renderSend, props])\n\n  const accessoryFragment = useMemo(() => {\n    if (!renderAccessory)\n      return null\n\n    return renderComponentOrElement(renderAccessory, props)\n  }, [renderAccessory, props])\n\n  const handleClearReply = useCallback(() => {\n    onClearReply?.()\n  }, [onClearReply])\n\n  const replyPreviewFragment = useMemo(() => {\n    if (!replyMessage)\n      return null\n\n    const replyPreviewProps: ReplyPreviewProps = {\n      replyMessage,\n      onClearReply: handleClearReply,\n      containerStyle: replyPreviewContainerStyle,\n      textStyle: replyPreviewTextStyle,\n    }\n\n    if (renderReplyPreviewProp)\n      return renderComponentOrElement(renderReplyPreviewProp, replyPreviewProps)\n\n    return <ReplyPreview {...replyPreviewProps} />\n  }, [\n    replyMessage,\n    handleClearReply,\n    renderReplyPreviewProp,\n    replyPreviewContainerStyle,\n    replyPreviewTextStyle,\n  ])\n\n  return (\n    <View style={containerStyles}>\n      {replyPreviewFragment}\n      <View style={primaryStyles}>\n        {actionsFragment}\n        {composerFragment}\n        {sendFragment}\n      </View>\n      {accessoryFragment}\n    </View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    borderTopWidth: StyleSheet.hairlineWidth,\n    borderTopColor: Color.defaultColor,\n    backgroundColor: Color.white,\n  },\n  container_dark: {\n    backgroundColor: '#1a1a1a',\n    borderTopColor: '#444',\n  },\n  primary: {\n    flexDirection: 'row',\n    alignItems: 'flex-end',\n  },\n})\n"
  },
  {
    "path": "src/LoadEarlierMessages.tsx",
    "content": "import React from 'react'\nimport {\n  ActivityIndicator,\n  StyleSheet,\n  View,\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n} from 'react-native'\nimport { Text } from 'react-native-gesture-handler'\nimport { Color } from './Color'\nimport { TouchableOpacity } from './components/TouchableOpacity'\nimport stylesCommon from './styles'\n\nexport interface LoadEarlierMessagesProps {\n  isAvailable: boolean\n  isLoading: boolean\n  onPress: () => void\n  isInfiniteScrollEnabled?: boolean\n  label?: string\n  containerStyle?: StyleProp<ViewStyle>\n  wrapperStyle?: StyleProp<ViewStyle>\n  textStyle?: StyleProp<TextStyle>\n  activityIndicatorStyle?: StyleProp<ViewStyle>\n  activityIndicatorColor?: string\n  activityIndicatorSize?: number | 'small' | 'large'\n}\n\nexport const LoadEarlierMessages: React.FC<LoadEarlierMessagesProps> = ({\n  isLoading = false,\n  onPress,\n  label = 'Load earlier messages',\n  containerStyle,\n  wrapperStyle,\n  textStyle,\n  activityIndicatorColor = 'white',\n  activityIndicatorSize = 'small',\n  activityIndicatorStyle,\n}) => {\n  return (\n    <TouchableOpacity\n      style={[styles.container, containerStyle]}\n      onPress={onPress}\n      enabled={!isLoading}\n      accessibilityRole='button'\n    >\n      <View style={[stylesCommon.centerItems, styles.wrapper, wrapperStyle]}>\n        {\n          isLoading\n            ? (\n              <ActivityIndicator\n                color={activityIndicatorColor}\n                size={activityIndicatorSize}\n                style={[styles.activityIndicator, activityIndicatorStyle]}\n              />\n            )\n            : (\n              <View style={styles.textContainer}>\n                <Text style={[styles.text, textStyle]}>\n                  {label}\n                </Text>\n              </View>\n            )\n        }\n      </View>\n    </TouchableOpacity>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    alignItems: 'center',\n    marginVertical: 10,\n  },\n  wrapper: {\n    backgroundColor: Color.defaultColor,\n    borderRadius: 15,\n    paddingHorizontal: 10,\n    paddingVertical: 5,\n  },\n  textContainer: {\n    paddingTop: 3,\n    paddingBottom: 4,\n  },\n  text: {\n    backgroundColor: Color.backgroundTransparent,\n    color: Color.white,\n    fontSize: 12,\n    lineHeight: 13,\n  },\n  activityIndicator: {\n    paddingHorizontal: 20,\n  },\n})\n"
  },
  {
    "path": "src/Message/index.tsx",
    "content": "import React, { useCallback, useMemo, useRef } from 'react'\nimport { View, StyleSheet } from 'react-native'\nimport ReanimatedSwipeable, { SwipeableMethods } from 'react-native-gesture-handler/ReanimatedSwipeable'\nimport Animated, { SharedValue, useAnimatedStyle } from 'react-native-reanimated'\n\nimport { Avatar } from '../Avatar'\nimport { Bubble } from '../Bubble'\nimport { Color } from '../Color'\nimport { IMessage } from '../Models'\nimport { SwipeToReplyProps } from '../Reply'\nimport { getStyleWithPosition } from '../styles'\nimport { SystemMessage } from '../SystemMessage'\nimport { isSameUser, renderComponentOrElement } from '../utils'\nimport styles from './styles'\nimport { MessageProps } from './types'\n\nexport * from './types'\n\ninterface ReplyIconProps {\n  progress: SharedValue<number>\n  translation: SharedValue<number>\n  direction: 'left' | 'right'\n  position: 'left' | 'right'\n  style?: SwipeToReplyProps<IMessage>['actionContainerStyle']\n}\n\nconst ReplyIcon = ({ progress, direction, position, style }: ReplyIconProps) => {\n  const animatedStyle = useAnimatedStyle(() => {\n    'worklet'\n\n    const scale = Math.min(progress.value, 1)\n    // When swiping left (icon on right), icon should move left (negative)\n    // When swiping right (icon on left), icon should move right (positive)\n    const translateX = direction === 'left'\n      ? Math.min(progress.value * -12, -12)\n      : Math.max(progress.value * 12, 12)\n\n    return {\n      transform: [{ scale }, { translateX }],\n      marginLeft: position === 'left' ? 0 : 16,\n      marginRight: position === 'right' ? 0 : 16,\n    }\n  })\n\n  return (\n    <Animated.View style={[localStyles.swipeActionContainer, animatedStyle, style]}>\n      <View style={localStyles.replyIconContainer}>\n        <View style={localStyles.replyIcon}>\n          <View style={localStyles.replyIconArrow} />\n          <View style={localStyles.replyIconLine} />\n        </View>\n      </View>\n    </Animated.View>\n  )\n}\n\nexport const Message = <TMessage extends IMessage = IMessage>(props: MessageProps<TMessage>) => {\n  const {\n    currentMessage,\n    renderBubble: renderBubbleProp,\n    renderSystemMessage: renderSystemMessageProp,\n    onMessageLayout,\n    nextMessage,\n    position,\n    containerStyle,\n    user,\n    isUserAvatarVisible,\n    swipeToReply,\n  } = props\n\n  // Extract swipe props\n  const isSwipeToReplyEnabled = swipeToReply?.isEnabled ?? false\n  const swipeToReplyDirection = swipeToReply?.direction ?? 'left'\n  const onSwipeToReply = swipeToReply?.onSwipe\n  const renderSwipeToReplyActionProp = swipeToReply?.renderAction\n  const swipeToReplyActionContainerStyle = swipeToReply?.actionContainerStyle\n\n  const swipeableRef = useRef<SwipeableMethods>(null)\n\n  const renderBubble = useCallback(() => {\n    const {\n      /* eslint-disable @typescript-eslint/no-unused-vars */\n      containerStyle,\n      onMessageLayout,\n      swipeToReply,\n      /* eslint-enable @typescript-eslint/no-unused-vars */\n      ...rest\n    } = props\n\n    if (renderBubbleProp)\n      return renderComponentOrElement(renderBubbleProp, rest)\n\n    return <Bubble {...rest} />\n  }, [props, renderBubbleProp])\n\n  const renderSystemMessage = useCallback(() => {\n    const {\n      /* eslint-disable @typescript-eslint/no-unused-vars */\n      containerStyle,\n      onMessageLayout,\n      swipeToReply,\n      /* eslint-enable @typescript-eslint/no-unused-vars */\n      ...rest\n    } = props\n\n    if (renderSystemMessageProp)\n      return renderComponentOrElement(renderSystemMessageProp, rest)\n\n    return <SystemMessage {...rest} />\n  }, [props, renderSystemMessageProp])\n\n  const renderAvatar = useCallback(() => {\n    if (\n      user?._id &&\n      currentMessage?.user &&\n      user._id === currentMessage.user._id &&\n      !isUserAvatarVisible\n    )\n      return null\n\n    if (currentMessage?.user?.avatar === null)\n      return null\n\n    const {\n      /* eslint-disable @typescript-eslint/no-unused-vars */\n      containerStyle,\n      onMessageLayout,\n      swipeToReply,\n      /* eslint-enable @typescript-eslint/no-unused-vars */\n      ...rest\n    } = props\n\n    return <Avatar {...rest} />\n  }, [\n    props,\n    user,\n    currentMessage,\n    isUserAvatarVisible,\n  ])\n\n  const renderSwipeAction = useCallback((\n    progress: SharedValue<number>,\n    translation: SharedValue<number>\n  ) => {\n    if (renderSwipeToReplyActionProp)\n      return renderSwipeToReplyActionProp(progress, translation, position)\n\n    return (\n      <ReplyIcon\n        progress={progress}\n        translation={translation}\n        direction={swipeToReplyDirection}\n        position={position}\n        style={swipeToReplyActionContainerStyle}\n      />\n    )\n  }, [position, renderSwipeToReplyActionProp, swipeToReplyDirection, swipeToReplyActionContainerStyle])\n\n  const handleSwipeableOpen = useCallback(() => {\n    swipeableRef.current?.close()\n  }, [])\n\n  const handleSwipeableWillOpen = useCallback(() => {\n    if (onSwipeToReply && currentMessage)\n      onSwipeToReply(currentMessage)\n  }, [onSwipeToReply, currentMessage])\n\n  const sameUser = useMemo(() =>\n    isSameUser(currentMessage, nextMessage!)\n  , [currentMessage, nextMessage])\n\n  const messageContent = useMemo(() => {\n    if (currentMessage?.system)\n      return renderSystemMessage()\n\n    return (\n      <View\n        style={[\n          getStyleWithPosition(styles, 'container', position),\n          { marginBottom: sameUser ? 2 : 10 },\n          !props.isInverted && { marginBottom: 2 },\n          containerStyle?.[position],\n        ]}\n      >\n        {position === 'left' && renderAvatar()}\n        {renderBubble()}\n        {position === 'right' && renderAvatar()}\n      </View>\n    )\n  }, [\n    currentMessage?.system,\n    renderSystemMessage,\n    position,\n    sameUser,\n    props.isInverted,\n    containerStyle,\n    renderAvatar,\n    renderBubble,\n  ])\n\n  if (!currentMessage)\n    return null\n\n  // Don't wrap system messages in Swipeable\n  if (currentMessage.system || !isSwipeToReplyEnabled)\n    return (\n      <View onLayout={onMessageLayout}>\n        {messageContent}\n      </View>\n    )\n\n  return (\n    <View onLayout={onMessageLayout}>\n      <ReanimatedSwipeable\n        ref={swipeableRef}\n        friction={2}\n        overshootFriction={8}\n        renderRightActions={swipeToReplyDirection === 'left' ? renderSwipeAction : undefined}\n        renderLeftActions={swipeToReplyDirection === 'right' ? renderSwipeAction : undefined}\n        onSwipeableOpen={handleSwipeableOpen}\n        onSwipeableWillOpen={handleSwipeableWillOpen}\n      >\n        {messageContent}\n      </ReanimatedSwipeable>\n    </View>\n  )\n}\n\nconst localStyles = StyleSheet.create({\n  swipeActionContainer: {\n    width: 40,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  replyIconContainer: {\n    width: 28,\n    height: 28,\n    borderRadius: 14,\n    backgroundColor: Color.defaultBlue,\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n  replyIcon: {\n    width: 14,\n    height: 10,\n    transform: [{ scaleX: -1 }],\n  },\n  replyIconArrow: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    width: 0,\n    height: 0,\n    borderTopWidth: 5,\n    borderTopColor: 'transparent',\n    borderBottomWidth: 5,\n    borderBottomColor: 'transparent',\n    borderRightWidth: 6,\n    borderRightColor: Color.white,\n  },\n  replyIconLine: {\n    position: 'absolute',\n    top: 3,\n    left: 5,\n    width: 9,\n    height: 4,\n    borderTopWidth: 2,\n    borderRightWidth: 2,\n    borderTopColor: Color.white,\n    borderRightColor: Color.white,\n    borderTopRightRadius: 4,\n  },\n})\n"
  },
  {
    "path": "src/Message/styles.ts",
    "content": "import { StyleSheet } from 'react-native'\n\nexport default StyleSheet.create({\n  container: {\n    flexDirection: 'row',\n    alignItems: 'flex-end',\n    maxWidth: '70%',\n  },\n  container_left: {\n    justifyContent: 'flex-start',\n    marginLeft: 8,\n  },\n  container_right: {\n    justifyContent: 'flex-end',\n    marginRight: 8,\n    alignSelf: 'flex-end',\n  },\n})\n"
  },
  {
    "path": "src/Message/types.ts",
    "content": "import { ViewStyle, LayoutChangeEvent } from 'react-native'\n\nimport { AvatarProps } from '../Avatar'\nimport { BubbleProps } from '../Bubble'\nimport { DayProps } from '../Day'\nimport { IMessage, User, LeftRightStyle } from '../Models'\nimport { SwipeToReplyProps } from '../Reply'\nimport { SystemMessageProps } from '../SystemMessage'\n\nexport interface MessageProps<TMessage extends IMessage> {\n  isUserAvatarVisible?: boolean\n  position: 'left' | 'right'\n  currentMessage: TMessage\n  nextMessage?: TMessage\n  previousMessage?: TMessage\n  user: User\n  isInverted?: boolean\n  containerStyle?: LeftRightStyle<ViewStyle>\n  renderBubble?: (props: BubbleProps<TMessage>) => React.ReactNode\n  renderDay?: (props: DayProps) => React.ReactNode\n  renderSystemMessage?: (props: SystemMessageProps<TMessage>) => React.ReactNode\n  renderAvatar?: (props: AvatarProps<TMessage>) => React.ReactNode\n  onMessageLayout?: (event: LayoutChangeEvent) => void\n  /** Swipe to reply configuration */\n  swipeToReply?: SwipeToReplyProps<TMessage>\n}\n"
  },
  {
    "path": "src/MessageAudio.tsx",
    "content": "import React, { useMemo } from 'react'\nimport { View, StyleSheet } from 'react-native'\nimport { Text } from 'react-native-gesture-handler'\nimport { Color } from './Color'\n\nconst styles = StyleSheet.create({\n  container: {\n    padding: 10,\n  },\n  text: {\n    color: Color.alizarin,\n    fontWeight: '600',\n  },\n})\n\nexport function MessageAudio () {\n  const content = useMemo(() => (\n    <View style={styles.container}>\n      <Text style={styles.text}>\n        {'Audio is not implemented by GiftedChat.'}\n      </Text>\n      <Text style={styles.text}>\n        {'\\nYou need to provide your own implementation by using renderMessageAudio prop.'}\n      </Text>\n    </View>\n  ), [])\n\n  return content\n}\n"
  },
  {
    "path": "src/MessageImage.tsx",
    "content": "import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n  Image,\n  StyleSheet,\n  View,\n  ImageProps,\n  ViewStyle,\n  StyleProp,\n  ImageStyle,\n  ImageURISource,\n  LayoutChangeEvent,\n  useWindowDimensions,\n  StatusBar,\n} from 'react-native'\nimport { BaseButton, GestureHandlerRootView, Text } from 'react-native-gesture-handler'\nimport { OverKeyboardView } from 'react-native-keyboard-controller'\nimport Animated, {\n  useAnimatedStyle,\n  useSharedValue,\n  withTiming,\n  Easing,\n  runOnJS,\n} from 'react-native-reanimated'\nimport { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'\nimport Zoom from 'react-native-zoom-reanimated'\nimport { TouchableOpacity } from './components/TouchableOpacity'\nimport { IMessage } from './Models'\nimport commonStyles from './styles'\n\ninterface ModalContentProps {\n  isVisible: boolean\n  imageSource: ImageURISource\n  modalImageDimensions: { width: number, height: number } | undefined\n  imageProps?: Partial<ImageProps>\n  onClose: () => void\n}\n\nfunction ModalContent({ isVisible, imageSource, modalImageDimensions, imageProps, onClose }: ModalContentProps) {\n  const insets = useSafeAreaInsets()\n\n  // Animation values\n  const modalOpacity = useSharedValue(0)\n  const modalScale = useSharedValue(0.9)\n  const modalBorderRadius = useSharedValue(40)\n\n  const handleModalClose = useCallback(() => {\n    modalOpacity.value = withTiming(0, { duration: 200, easing: Easing.in(Easing.ease) })\n    modalScale.value = withTiming(0.9, { duration: 200, easing: Easing.in(Easing.ease) }, () => {\n      runOnJS(onClose)()\n    })\n    modalBorderRadius.value = withTiming(40, { duration: 200, easing: Easing.in(Easing.ease) })\n  }, [onClose, modalOpacity, modalScale, modalBorderRadius])\n\n  // Animate on visibility change\n  useEffect(() => {\n    if (isVisible) {\n      modalOpacity.value = withTiming(1, { duration: 300, easing: Easing.out(Easing.ease) })\n      modalScale.value = withTiming(1, { duration: 300, easing: Easing.out(Easing.ease) })\n      modalBorderRadius.value = withTiming(0, { duration: 300, easing: Easing.out(Easing.ease) })\n    }\n  }, [isVisible, modalOpacity, modalScale, modalBorderRadius])\n\n  const modalAnimatedStyle = useAnimatedStyle(() => ({\n    opacity: modalOpacity.value,\n    transform: [{ scale: modalScale.value }],\n  }), [modalOpacity, modalScale])\n\n  const modalBorderRadiusStyle = useAnimatedStyle(() => ({\n    borderRadius: modalBorderRadius.value,\n  }), [modalBorderRadius])\n\n  return (\n    <>\n      <StatusBar animated barStyle='dark-content' />\n      <Animated.View style={[styles.modalOverlay, modalAnimatedStyle, modalBorderRadiusStyle]}>\n        <GestureHandlerRootView style={commonStyles.fill}>\n          <Animated.View style={[commonStyles.fill, styles.modalContent, modalBorderRadiusStyle, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>\n\n            {/* close button */}\n            <View style={styles.closeButtonContainer}>\n              <BaseButton onPress={handleModalClose}>\n                <View style={styles.closeButtonContent}>\n                  <Text style={styles.closeButtonIcon}>\n                    {'X'}\n                  </Text>\n                </View>\n              </BaseButton>\n            </View>\n\n            <View style={[commonStyles.fill, commonStyles.centerItems]}>\n              <Zoom>\n                <Image\n                  style={modalImageDimensions}\n                  source={imageSource}\n                  resizeMode='contain'\n                  {...imageProps}\n                />\n              </Zoom>\n            </View>\n          </Animated.View>\n        </GestureHandlerRootView>\n      </Animated.View>\n    </>\n  )\n}\n\nexport interface MessageImageProps<TMessage extends IMessage> {\n  currentMessage: TMessage\n  containerStyle?: StyleProp<ViewStyle>\n  imageSourceProps?: Partial<ImageURISource>\n  imageStyle?: StyleProp<ImageStyle>\n  imageProps?: Partial<ImageProps>\n}\n\nexport function MessageImage<TMessage extends IMessage = IMessage> ({\n  containerStyle,\n  imageProps,\n  imageSourceProps,\n  imageStyle,\n  currentMessage,\n}: MessageImageProps<TMessage>) {\n  const [isModalVisible, setIsModalVisible] = useState(false)\n  const [imageDimensions, setImageDimensions] = useState<{ width: number, height: number }>()\n  const windowDimensions = useWindowDimensions()\n\n  const imageSource = useMemo(() => ({\n    ...imageSourceProps,\n    uri: currentMessage?.image,\n  }), [imageSourceProps, currentMessage?.image])\n\n  const isImageSourceChanged = useRef(true)\n\n  const computedImageStyle = useMemo(() => [\n    styles.image,\n    imageStyle,\n  ], [imageStyle])\n\n  const handleImagePress = useCallback(() => {\n    if (!imageSource.uri)\n      return\n\n    setIsModalVisible(true)\n\n    if (isImageSourceChanged.current || !imageDimensions)\n      Image.getSize(imageSource.uri, (width, height) => {\n        setImageDimensions({ width, height })\n      })\n  }, [imageSource.uri, imageDimensions])\n\n  const handleModalClose = useCallback(() => {\n    setIsModalVisible(false)\n  }, [])\n\n  const handleImageLayout = useCallback((e: LayoutChangeEvent) => {\n    setImageDimensions({\n      width: e.nativeEvent.layout.width,\n      height: e.nativeEvent.layout.height,\n    })\n  }, [])\n\n  const modalImageDimensions = useMemo(() => {\n    if (!imageDimensions)\n      return undefined\n\n    const aspectRatio = imageDimensions.width / imageDimensions.height\n\n    let width = windowDimensions.width\n    let height = width / aspectRatio\n\n    if (height > windowDimensions.height) {\n      height = windowDimensions.height\n      width = height * aspectRatio\n    }\n\n    return {\n      width,\n      height,\n    }\n  }, [imageDimensions, windowDimensions.height, windowDimensions.width])\n\n  useEffect(() => {\n    isImageSourceChanged.current = true\n  }, [imageSource.uri])\n\n  if (currentMessage == null)\n    return null\n\n  return (\n    <View style={containerStyle}>\n      <TouchableOpacity onPress={handleImagePress}>\n        <Image\n          {...imageProps}\n          style={computedImageStyle}\n          source={imageSource}\n          onLayout={handleImageLayout}\n          resizeMode='cover'\n        />\n      </TouchableOpacity>\n\n      <OverKeyboardView visible={isModalVisible}>\n        <SafeAreaProvider>\n          <ModalContent\n            isVisible={isModalVisible}\n            imageSource={imageSource}\n            modalImageDimensions={modalImageDimensions}\n            imageProps={imageProps}\n            onClose={handleModalClose}\n          />\n        </SafeAreaProvider>\n      </OverKeyboardView>\n    </View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  image: {\n    width: 150,\n    height: 100,\n    borderRadius: 13,\n    margin: 3,\n  },\n  modalOverlay: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    right: 0,\n    bottom: 0,\n    zIndex: 1000,\n  },\n  modalContent: {\n    backgroundColor: '#000',\n    overflow: 'hidden',\n  },\n  modalImageContainer: {\n    width: '100%',\n    height: '100%',\n  },\n\n  closeButtonContainer: {\n    flexDirection: 'row',\n    justifyContent: 'flex-end',\n  },\n  closeButtonContent: {\n    padding: 20,\n  },\n  closeButtonIcon: {\n    fontSize: 20,\n    lineHeight: 20,\n    color: 'white',\n  },\n})\n"
  },
  {
    "path": "src/MessageReply.tsx",
    "content": "import React, { useMemo, useCallback } from 'react'\nimport {\n  StyleSheet,\n  ViewStyle,\n  View,\n  Pressable,\n  Image,\n  TextStyle,\n  StyleProp,\n  ImageStyle,\n} from 'react-native'\n\nimport { Text } from 'react-native-gesture-handler'\nimport { Color } from './Color'\nimport { LeftRightStyle, IMessage, ReplyMessage } from './Models'\nimport { getStyleWithPosition } from './styles'\n\nexport interface MessageReplyProps<TMessage extends IMessage> {\n  position?: 'left' | 'right'\n  currentMessage: TMessage\n  containerStyle?: LeftRightStyle<ViewStyle>\n  contentContainerStyle?: LeftRightStyle<ViewStyle>\n  imageStyle?: StyleProp<ImageStyle>\n  usernameStyle?: StyleProp<TextStyle>\n  textStyle?: StyleProp<TextStyle>\n  onPress?: (replyMessage: ReplyMessage) => void\n}\n\nexport function MessageReply<TMessage extends IMessage = IMessage> ({\n  currentMessage,\n  position = 'left',\n  containerStyle,\n  contentContainerStyle,\n  imageStyle,\n  usernameStyle,\n  textStyle,\n  onPress: onPressProp,\n}: MessageReplyProps<TMessage>) {\n  const handlePress = useCallback(() => {\n    if (!onPressProp || !currentMessage.replyMessage)\n      return\n\n    onPressProp(currentMessage.replyMessage)\n  }, [onPressProp, currentMessage.replyMessage])\n\n  const containerStyleMemo = useMemo(() => [\n    styles.container,\n    getStyleWithPosition(styles, 'container', position),\n    containerStyle?.[position],\n  ], [position, containerStyle])\n\n  const contentContainerStyleMemo = useMemo(() => [\n    styles.contentContainer,\n    contentContainerStyle?.[position],\n  ], [position, contentContainerStyle])\n\n  const imageStyleMemo = useMemo(() => [\n    styles.image,\n    imageStyle,\n  ], [imageStyle])\n\n  const usernameStyleMemo = useMemo(() => [\n    styles.username,\n    getStyleWithPosition(styles, 'username', position),\n    usernameStyle,\n  ], [position, usernameStyle])\n\n  const textStyleMemo = useMemo(() => [\n    styles.text,\n    getStyleWithPosition(styles, 'text', position),\n    textStyle,\n  ], [position, textStyle])\n\n  if (!currentMessage.replyMessage)\n    return null\n\n  const { replyMessage } = currentMessage\n\n  return (\n    <Pressable\n      onPress={handlePress}\n      style={containerStyleMemo}\n    >\n      <View style={contentContainerStyleMemo}>\n        {replyMessage.image && (\n          <Image\n            source={{ uri: replyMessage.image }}\n            style={imageStyleMemo}\n          />\n        )}\n        <View style={styles.textContainer}>\n          <Text\n            style={usernameStyleMemo}\n            numberOfLines={1}\n          >\n            {replyMessage.user?.name || 'User'}\n          </Text>\n          <Text\n            numberOfLines={1}\n            style={textStyleMemo}\n          >\n            {replyMessage.text || (replyMessage.image ? 'Photo' : (replyMessage.audio ? 'Audio' : 'Message'))}\n          </Text>\n        </View>\n      </View>\n    </Pressable>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    marginHorizontal: 10,\n    marginTop: 5,\n    marginBottom: 2,\n    padding: 8,\n    borderRadius: 8,\n    borderLeftWidth: 3,\n    borderLeftColor: Color.defaultBlue,\n    minWidth: 150,\n  },\n  container_left: {\n    backgroundColor: 'rgba(0, 0, 0, 0.05)',\n  },\n  container_right: {\n    backgroundColor: 'rgba(255, 255, 255, 0.15)',\n  },\n  contentContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  image: {\n    width: 36,\n    height: 36,\n    borderRadius: 4,\n    marginRight: 8,\n  },\n  textContainer: {\n    flex: 1,\n  },\n  username: {\n    fontSize: 13,\n    fontWeight: '600',\n    marginBottom: 2,\n  },\n  username_left: {\n    color: Color.defaultBlue,\n  },\n  username_right: {\n    color: Color.white,\n  },\n  text: {\n    fontSize: 13,\n  },\n  text_left: {\n    color: Color.black,\n  },\n  text_right: {\n    color: 'rgba(255, 255, 255, 0.8)',\n  },\n})\n"
  },
  {
    "path": "src/MessageText.tsx",
    "content": "import React, { useMemo, useCallback } from 'react'\nimport {\n  StyleSheet,\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n  View,\n} from 'react-native'\n\nimport { Text } from 'react-native-gesture-handler'\nimport { LinkParser, LinkMatcher, LinkType } from './linkParser'\nimport { LeftRightStyle, IMessage } from './Models'\n\nexport type MessageTextProps<TMessage extends IMessage> = {\n  position?: 'left' | 'right'\n  currentMessage: TMessage\n  containerStyle?: LeftRightStyle<ViewStyle>\n  textStyle?: LeftRightStyle<TextStyle>\n  linkStyle?: LeftRightStyle<TextStyle>\n  customTextStyle?: StyleProp<TextStyle>\n  onPress?: (\n    message: TMessage,\n    url: string,\n    type: LinkType\n  ) => void\n  // Link parser options\n  matchers?: LinkMatcher[]\n  email?: boolean\n  phone?: boolean\n  url?: boolean\n  hashtag?: boolean\n  mention?: boolean\n  hashtagUrl?: string\n  mentionUrl?: string\n  stripPrefix?: boolean\n}\n\nexport function MessageText<TMessage extends IMessage>({\n  currentMessage,\n  position = 'left',\n  containerStyle,\n  textStyle,\n  linkStyle: linkStyleProp,\n  customTextStyle,\n  onPress: onPressProp,\n  matchers,\n  email = true,\n  phone = true,\n  url = true,\n  hashtag = false,\n  mention = false,\n  hashtagUrl,\n  mentionUrl,\n  stripPrefix = false,\n}: MessageTextProps<TMessage>) {\n  const linkStyle = useMemo(() => StyleSheet.flatten([\n    styles.link,\n    linkStyleProp?.[position],\n  ]), [position, linkStyleProp])\n\n  const style = useMemo(() => [\n    styles[`text_${position}`],\n    textStyle?.[position],\n    customTextStyle,\n  ], [position, textStyle, customTextStyle])\n\n  const handlePress = useCallback((url: string, type: LinkType) => {\n    onPressProp?.(currentMessage, url, type)\n  }, [onPressProp, currentMessage])\n\n  return (\n    <View style={[styles.container, containerStyle?.[position]]}>\n      <LinkParser\n        text={currentMessage!.text}\n        matchers={matchers}\n        email={email}\n        phone={phone}\n        url={url}\n        hashtag={hashtag}\n        mention={mention}\n        hashtagUrl={hashtagUrl}\n        mentionUrl={mentionUrl}\n        stripPrefix={stripPrefix}\n        linkStyle={linkStyle}\n        textStyle={style}\n        onPress={onPressProp ? handlePress : undefined}\n        TextComponent={Text}\n      />\n    </View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    marginVertical: 5,\n    marginHorizontal: 10,\n  },\n  text: {\n    fontSize: 16,\n    lineHeight: 20,\n  },\n  text_left: {\n    color: 'black',\n  },\n  text_right: {\n    color: 'white',\n  },\n  link: {\n    textDecorationLine: 'underline',\n  },\n})\n"
  },
  {
    "path": "src/MessageVideo.tsx",
    "content": "import React, { useMemo } from 'react'\nimport { View, StyleSheet } from 'react-native'\nimport { Text } from 'react-native-gesture-handler'\nimport { Color } from './Color'\n\nconst styles = StyleSheet.create({\n  container: {\n    padding: 10,\n  },\n  text: {\n    color: Color.alizarin,\n    fontWeight: '600',\n  },\n})\n\nexport function MessageVideo () {\n  const content = useMemo(() => (\n    <View style={styles.container}>\n      <Text style={styles.text}>\n        {'Video is not implemented by GiftedChat.'}\n      </Text>\n      <Text style={styles.text}>\n        {'\\nYou need to provide your own implementation by using renderMessageVideo prop.'}\n      </Text>\n    </View>\n  ), [])\n\n  return content\n}\n"
  },
  {
    "path": "src/MessagesContainer/components/DayAnimated/index.tsx",
    "content": "import React, { useCallback, useEffect, useMemo, useState } from 'react'\nimport { LayoutChangeEvent } from 'react-native'\nimport Animated, { interpolate, useAnimatedStyle, useDerivedValue, useSharedValue, useAnimatedReaction, withTiming, runOnJS } from 'react-native-reanimated'\nimport { Day } from '../../../Day'\nimport stylesCommon from '../../../styles'\nimport { isSameDay } from '../../../utils'\nimport { useAbsoluteScrolledPositionToBottomOfDay, useRelativeScrolledPositionToBottomOfDay } from '../Item'\n\nimport styles from './styles'\nimport { DayAnimatedProps } from './types'\n\nexport * from './types'\n\nexport const DayAnimated = ({ scrolledY, daysPositions, listHeight, renderDay, messages, isLoading, ...rest }: DayAnimatedProps) => {\n  const opacity = useSharedValue(0)\n  const fadeOutOpacityTimeoutId = useSharedValue<ReturnType<typeof setTimeout> | undefined>(undefined)\n  const containerHeight = useSharedValue(0)\n\n  const isScrolledOnMount = useSharedValue(false)\n  const isLoadingAnim = useSharedValue(isLoading)\n\n  const daysPositionsArray = useDerivedValue(() => Object.values(daysPositions.value).sort((a, b) => {\n    'worklet'\n\n    return a.y - b.y\n  }))\n\n  const [createdAt, setCreatedAt] = useState<number | undefined>()\n\n  const dayTopOffset = useMemo(() => 10, [])\n  const dayBottomMargin = useMemo(() => 10, [])\n  const absoluteScrolledPositionToBottomOfDay = useAbsoluteScrolledPositionToBottomOfDay(listHeight, scrolledY, containerHeight, dayBottomMargin, dayTopOffset)\n  const relativeScrolledPositionToBottomOfDay = useRelativeScrolledPositionToBottomOfDay(listHeight, scrolledY, daysPositions, containerHeight, dayBottomMargin, dayTopOffset)\n\n  const messagesDates = useMemo(() => {\n    const messagesDates: number[] = []\n\n    for (let i = 1; i < messages.length; i++) {\n      const previousMessage = messages[i - 1]\n      const message = messages[i]\n\n      if (!isSameDay(previousMessage, message) || !messagesDates.includes(new Date(message.createdAt).getTime()))\n        messagesDates.push(new Date(message.createdAt).getTime())\n    }\n\n    return messagesDates\n  }, [messages])\n\n  const createdAtDate = useDerivedValue(() => {\n    for (let i = 0; i < daysPositionsArray.value.length; i++) {\n      const day = daysPositionsArray.value[i]\n      const dayPosition = day.y + day.height - containerHeight.value - dayBottomMargin\n\n      if (absoluteScrolledPositionToBottomOfDay.value < dayPosition)\n        return day.createdAt\n    }\n\n    return messagesDates[messagesDates.length - 1]\n  }, [daysPositionsArray, absoluteScrolledPositionToBottomOfDay, messagesDates, containerHeight, dayBottomMargin])\n\n  const style = useAnimatedStyle(() => ({\n    top: interpolate(\n      relativeScrolledPositionToBottomOfDay.value,\n      [-dayTopOffset, -0.0001, 0, isLoadingAnim.value ? 0 : containerHeight.value + dayTopOffset],\n      [dayTopOffset, dayTopOffset, -containerHeight.value, isLoadingAnim.value ? -containerHeight.value : dayTopOffset],\n      'clamp'\n    ),\n  }), [relativeScrolledPositionToBottomOfDay, containerHeight, dayTopOffset, isLoadingAnim])\n\n  const contentStyle = useAnimatedStyle(() => ({\n    opacity: opacity.value,\n  }), [opacity])\n\n  const fadeOut = useCallback(() => {\n    'worklet'\n\n    opacity.value = withTiming(0, { duration: 500 })\n  }, [opacity])\n\n  const scheduleFadeOut = useCallback(() => {\n    clearTimeout(fadeOutOpacityTimeoutId.value)\n\n    fadeOutOpacityTimeoutId.value = setTimeout(fadeOut, 500)\n  }, [fadeOut, fadeOutOpacityTimeoutId])\n\n  const handleLayout = useCallback(({ nativeEvent }: LayoutChangeEvent) => {\n    containerHeight.value = nativeEvent.layout.height\n  }, [containerHeight])\n\n  useAnimatedReaction(\n    () => [scrolledY.value, daysPositionsArray],\n    (value, prevValue) => {\n      if (!isScrolledOnMount.value) {\n        isScrolledOnMount.value = true\n        return\n      }\n\n      if (value[0] === prevValue?.[0])\n        return\n\n      opacity.value = withTiming(1, { duration: 500 })\n\n      runOnJS(scheduleFadeOut)()\n    },\n    [scrolledY, scheduleFadeOut, daysPositionsArray]\n  )\n\n  useAnimatedReaction(\n    () => createdAtDate.value,\n    (value, prevValue) => {\n      if (value && value !== prevValue)\n        runOnJS(setCreatedAt)(value)\n    },\n    [createdAtDate]\n  )\n\n  useEffect(() => {\n    isLoadingAnim.value = isLoading\n  }, [isLoadingAnim, isLoading])\n\n  const dayContent = useMemo(() => {\n    if (!createdAt)\n      return null\n\n    return renderDay\n      ? renderDay({ ...rest, createdAt })\n      : <Day\n        {...rest}\n        containerStyle={[styles.dayAnimatedDayContainerStyle, rest.containerStyle]}\n        createdAt={createdAt}\n      />\n  }, [createdAt, renderDay, rest])\n\n  if (!createdAt)\n    return null\n\n  return (\n    <Animated.View\n      style={[stylesCommon.centerItems, styles.dayAnimated, style]}\n      onLayout={handleLayout}\n      pointerEvents='none'\n    >\n      <Animated.View\n        style={contentStyle}\n        pointerEvents='none'\n      >\n        {dayContent}\n      </Animated.View>\n    </Animated.View>\n  )\n}\n"
  },
  {
    "path": "src/MessagesContainer/components/DayAnimated/styles.ts",
    "content": "import { StyleSheet } from 'react-native'\n\nexport default StyleSheet.create({\n  dayAnimated: {\n    position: 'absolute',\n    width: '100%',\n  },\n  dayAnimatedDayContainerStyle: {\n    marginTop: 0,\n    marginBottom: 0,\n  },\n})\n"
  },
  {
    "path": "src/MessagesContainer/components/DayAnimated/types.ts",
    "content": "import { DayProps } from '../../../Day'\nimport { IMessage } from '../../../Models'\nimport { DaysPositions } from '../../types'\n\nexport interface DayAnimatedProps extends Omit<DayProps, 'createdAt'> {\n  scrolledY: { value: number }\n  daysPositions: { value: DaysPositions }\n  listHeight: { value: number }\n  renderDay?: (props: DayProps) => React.ReactNode\n  messages: IMessage[]\n  isLoading: boolean\n}\n"
  },
  {
    "path": "src/MessagesContainer/components/Item/index.tsx",
    "content": "import React, { useCallback, useMemo } from 'react'\nimport { LayoutChangeEvent, View } from 'react-native'\nimport Animated, { interpolate, useAnimatedStyle, useDerivedValue, useSharedValue } from 'react-native-reanimated'\nimport { Day } from '../../../Day'\nimport { Message, MessageProps } from '../../../Message'\nimport { IMessage } from '../../../Models'\nimport { isSameDay } from '../../../utils'\nimport { DaysPositions } from '../../types'\nimport { ItemProps } from './types'\n\nexport * from './types'\n\n// y-position of current scroll position relative to the bottom of the day container. (since we have inverted list it is bottom)\nexport const useAbsoluteScrolledPositionToBottomOfDay = (listHeight: { value: number }, scrolledY: { value: number }, containerHeight: { value: number }, dayBottomMargin: number, dayTopOffset: number) => {\n  const absoluteScrolledPositionToBottomOfDay = useDerivedValue(() =>\n    listHeight.value + scrolledY.value - containerHeight.value - dayBottomMargin - dayTopOffset\n  , [listHeight, scrolledY, containerHeight, dayBottomMargin, dayTopOffset])\n\n  return absoluteScrolledPositionToBottomOfDay\n}\n\nexport const useRelativeScrolledPositionToBottomOfDay = (\n  listHeight: { value: number },\n  scrolledY: { value: number },\n  daysPositions: { value: DaysPositions },\n  containerHeight: { value: number },\n  dayBottomMargin: number,\n  dayTopOffset: number,\n  createdAt?: number\n) => {\n  const dayMarginTop = useMemo(() => 5, [])\n\n  const absoluteScrolledPositionToBottomOfDay = useAbsoluteScrolledPositionToBottomOfDay(listHeight, scrolledY, containerHeight, dayBottomMargin, dayTopOffset)\n\n  // find current day position by scrolled position\n  const currentDayPosition = useDerivedValue(() => {\n    'worklet'\n\n    // When createdAt is provided (called from AnimatedDayWrapper for a specific message),\n    // directly find the day position by createdAt without sorting the entire array.\n    // This avoids O(n log n) sorting and O(n) search for each message item.\n    if (createdAt != null) {\n      const values = Object.values(daysPositions.value)\n      for (let i = 0; i < values.length; i++)\n        if (values[i].createdAt === createdAt)\n          return values[i]\n    }\n\n    // Fallback: sort and search when createdAt is not provided (e.g., from DayAnimated)\n    const sortedArray = Object.values(daysPositions.value).sort((a, b) => {\n      'worklet'\n\n      return a.y - b.y\n    })\n    for (let i = 0; i < sortedArray.length; i++) {\n      const day = sortedArray[i]\n      const dayPosition = day.y + day.height\n      if (absoluteScrolledPositionToBottomOfDay.value < dayPosition || i === sortedArray.length - 1)\n        return day\n    }\n\n    return undefined\n  }, [daysPositions, absoluteScrolledPositionToBottomOfDay, createdAt])\n\n  const relativeScrolledPositionToBottomOfDay = useDerivedValue(() => {\n    const scrolledBottomY = listHeight.value + scrolledY.value - (\n      (currentDayPosition.value?.y ?? 0) +\n      (currentDayPosition.value?.height ?? 0) +\n      dayMarginTop\n    )\n\n    return scrolledBottomY\n  }, [listHeight, scrolledY, currentDayPosition, dayMarginTop])\n\n  return relativeScrolledPositionToBottomOfDay\n}\n\nconst DayWrapper = <TMessage extends IMessage>(props: MessageProps<TMessage>) => {\n  const {\n    renderDay: renderDayProp,\n    currentMessage,\n    previousMessage,\n  } = props\n\n  if (!currentMessage?.createdAt || isSameDay(currentMessage, previousMessage))\n    return null\n\n  const {\n    /* eslint-disable @typescript-eslint/no-unused-vars */\n    containerStyle,\n    onMessageLayout,\n    /* eslint-enable @typescript-eslint/no-unused-vars */\n    ...rest\n  } = props\n\n  return (\n    <View>\n      {\n        renderDayProp\n          ? renderDayProp({ ...rest, createdAt: currentMessage.createdAt })\n          : <Day {...rest} createdAt={currentMessage.createdAt} />\n      }\n    </View>\n  )\n}\n\nconst AnimatedDayWrapper = <TMessage extends IMessage>(props: ItemProps<TMessage>) => {\n  const {\n    scrolledY,\n    daysPositions,\n    listHeight,\n    ...rest\n  } = props\n\n  const dayContainerHeight = useSharedValue(0)\n  const dayTopOffset = useMemo(() => 10, [])\n  const dayBottomMargin = useMemo(() => 10, [])\n\n  const createdAt = useMemo(() =>\n    new Date(props.currentMessage.createdAt).getTime()\n  , [props.currentMessage.createdAt])\n\n  const relativeScrolledPositionToBottomOfDay = useRelativeScrolledPositionToBottomOfDay(listHeight, scrolledY, daysPositions, dayContainerHeight, dayBottomMargin, dayTopOffset, createdAt)\n\n  const handleLayoutDayContainer = useCallback(({ nativeEvent }: LayoutChangeEvent) => {\n    dayContainerHeight.value = nativeEvent.layout.height\n  }, [dayContainerHeight])\n\n  const style = useAnimatedStyle(() => ({\n    opacity: interpolate(\n      relativeScrolledPositionToBottomOfDay.value,\n      [\n        -dayTopOffset,\n        -0.0001,\n        0,\n        dayContainerHeight.value + dayTopOffset,\n      ],\n      [\n        0,\n        0,\n        1,\n        1,\n      ],\n      'clamp'\n    ),\n  }), [relativeScrolledPositionToBottomOfDay, dayContainerHeight, dayTopOffset])\n\n  return (\n    <Animated.View\n      style={style}\n      onLayout={handleLayoutDayContainer}\n    >\n      <DayWrapper<TMessage> {...rest as MessageProps<TMessage>} />\n    </Animated.View>\n  )\n}\n\nexport const Item = <TMessage extends IMessage>(props: ItemProps<TMessage>) => {\n  const {\n    renderMessage: renderMessageProp,\n    isDayAnimationEnabled,\n    reply,\n    /* eslint-disable @typescript-eslint/no-unused-vars */\n    scrolledY: _scrolledY,\n    daysPositions: _daysPositions,\n    listHeight: _listHeight,\n    /* eslint-enable @typescript-eslint/no-unused-vars */\n    ...rest\n  } = props\n\n  // Transform reply props for Message and Bubble\n  const messageProps = useMemo(() => ({\n    ...rest,\n    // Swipe to reply for Message component\n    swipeToReply: reply?.swipe,\n    // Message reply styling for Bubble component\n    messageReply: reply ? {\n      renderMessageReply: reply.renderMessageReply,\n      onPress: reply.onPress,\n      ...reply.messageStyle,\n    } : undefined,\n  }), [rest, reply])\n\n  return (\n    // do not remove key. it helps to get correct position of the day container\n    <View key={props.currentMessage._id.toString()}>\n      {isDayAnimationEnabled\n        ? <AnimatedDayWrapper<TMessage> {...props} />\n        : <DayWrapper<TMessage> {...messageProps as MessageProps<TMessage>} />}\n      {\n        renderMessageProp\n          ? renderMessageProp(messageProps as MessageProps<TMessage>)\n          : <Message<TMessage> {...messageProps as MessageProps<TMessage>} />\n      }\n    </View>\n  )\n}\n"
  },
  {
    "path": "src/MessagesContainer/components/Item/types.ts",
    "content": "import { IMessage } from '../../../Models'\nimport { MessagesContainerProps, DaysPositions } from '../../types'\n\nexport interface ItemProps<TMessage extends IMessage> extends MessagesContainerProps<TMessage> {\n  currentMessage: TMessage\n  previousMessage?: TMessage\n  nextMessage?: TMessage\n  position: 'left' | 'right'\n  scrolledY: { value: number }\n  daysPositions: { value: DaysPositions }\n  listHeight: { value: number }\n  isDayAnimationEnabled?: boolean\n}\n"
  },
  {
    "path": "src/MessagesContainer/index.tsx",
    "content": "import React, { useCallback, useEffect, useMemo, useState } from 'react'\nimport {\n  View,\n  LayoutChangeEvent,\n  ListRenderItemInfo,\n  CellRendererProps,\n} from 'react-native'\nimport { Pressable, Text } from 'react-native-gesture-handler'\nimport Animated, { runOnJS, ScrollEvent, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'\nimport { LoadEarlierMessages } from '../LoadEarlierMessages'\nimport { warning } from '../logging'\nimport { IMessage } from '../Models'\nimport stylesCommon from '../styles'\nimport { TypingIndicator } from '../TypingIndicator'\nimport { isSameDay, useCallbackThrottled } from '../utils'\nimport { DayAnimated } from './components/DayAnimated'\n\nimport { Item } from './components/Item'\nimport { ItemProps } from './components/Item/types'\nimport styles from './styles'\nimport { MessagesContainerProps, DaysPositions, AnimatedFlatList } from './types'\n\nexport * from './types'\n\nexport const MessagesContainer = <TMessage extends IMessage>(props: MessagesContainerProps<TMessage>) => {\n  const {\n    messages = [],\n    user,\n    isTyping = false,\n    renderChatEmpty: renderChatEmptyProp,\n    isInverted = true,\n    listProps,\n    isScrollToBottomEnabled = false,\n    scrollToBottomOffset = 200,\n    isAlignedTop = false,\n    scrollToBottomStyle,\n    scrollToBottomContentStyle,\n    loadEarlierMessagesProps,\n    renderTypingIndicator: renderTypingIndicatorProp,\n    renderFooter: renderFooterProp,\n    renderLoadEarlier: renderLoadEarlierProp,\n    forwardRef,\n    scrollToBottomComponent: scrollToBottomComponentProp,\n    renderDay: renderDayProp,\n    isDayAnimationEnabled = true,\n  } = props\n\n  const listPropsOnScrollProp = listProps?.onScroll\n\n  const scrollToBottomOpacity = useSharedValue(0)\n  const isScrollingDown = useSharedValue(false)\n  const lastScrolledY = useSharedValue(0)\n  const [isScrollToBottomVisible, setIsScrollToBottomVisible] = useState(false)\n  const scrollToBottomStyleAnim = useAnimatedStyle(() => ({\n    opacity: scrollToBottomOpacity.value,\n  }), [scrollToBottomOpacity])\n\n  const daysPositions = useSharedValue<DaysPositions>({})\n  const listHeight = useSharedValue(0)\n  const scrolledY = useSharedValue(0)\n\n  const renderTypingIndicator = useCallback(() => {\n    if (renderTypingIndicatorProp)\n      return renderTypingIndicatorProp()\n\n    return <TypingIndicator isTyping={isTyping} style={props.typingIndicatorStyle} />\n  }, [isTyping, renderTypingIndicatorProp, props.typingIndicatorStyle])\n\n  const ListFooterComponent = useMemo(() => {\n    if (renderFooterProp)\n      return renderFooterProp(props)\n\n    return renderTypingIndicator()\n  }, [renderFooterProp, renderTypingIndicator, props])\n\n  const renderLoadEarlier = useCallback(() => {\n    if (loadEarlierMessagesProps?.isAvailable) {\n      if (renderLoadEarlierProp)\n        return renderLoadEarlierProp(loadEarlierMessagesProps)\n\n      return <LoadEarlierMessages {...loadEarlierMessagesProps} />\n    }\n\n    return null\n  }, [loadEarlierMessagesProps, renderLoadEarlierProp])\n\n  const changeScrollToBottomVisibility: (isVisible: boolean) => void = useCallbackThrottled((isVisible: boolean) => {\n    if (isScrollingDown.value && isVisible)\n      return\n\n    if (isVisible)\n      setIsScrollToBottomVisible(true)\n\n    scrollToBottomOpacity.value = withTiming(isVisible ? 1 : 0, { duration: 250 }, isFinished => {\n      if (isFinished && !isVisible)\n        runOnJS(setIsScrollToBottomVisible)(false)\n    })\n  }, [scrollToBottomOpacity, isScrollingDown], 50)\n\n  const scrollTo = useCallback((options: { animated?: boolean, offset: number }) => {\n    if (options)\n      forwardRef?.current?.scrollToOffset(options)\n  }, [forwardRef])\n\n  const doScrollToBottom = useCallback((animated: boolean = true) => {\n    isScrollingDown.value = true\n    changeScrollToBottomVisibility(false)\n\n    if (isInverted)\n      scrollTo({ offset: 0, animated })\n    else if (forwardRef?.current)\n      forwardRef.current.scrollToEnd({ animated })\n  }, [forwardRef, isInverted, scrollTo, isScrollingDown, changeScrollToBottomVisibility])\n\n  const handleOnScroll = useCallback((event: ScrollEvent) => {\n    listPropsOnScrollProp?.(event)\n\n    const {\n      contentOffset: { y: contentOffsetY },\n      contentSize: { height: contentSizeHeight },\n      layoutMeasurement: { height: layoutMeasurementHeight },\n    } = event\n\n    isScrollingDown.value =\n      (isInverted && lastScrolledY.value > contentOffsetY) ||\n      (!isInverted && lastScrolledY.value < contentOffsetY)\n\n    lastScrolledY.value = contentOffsetY\n\n    if (isInverted)\n      if (contentOffsetY > scrollToBottomOffset!)\n        changeScrollToBottomVisibility(true)\n      else\n        changeScrollToBottomVisibility(false)\n    else if (\n      contentOffsetY < scrollToBottomOffset! &&\n      contentSizeHeight - layoutMeasurementHeight > scrollToBottomOffset!\n    )\n      changeScrollToBottomVisibility(false)\n    else\n      changeScrollToBottomVisibility(false)\n  }, [isInverted, scrollToBottomOffset, changeScrollToBottomVisibility, isScrollingDown, lastScrolledY, listPropsOnScrollProp])\n\n  const restProps = useMemo(() => {\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    const { messages: _, ...rest } = props\n    return rest\n  }, [props])\n\n  const renderItem = useCallback(({ item, index }: ListRenderItemInfo<unknown>): React.ReactElement | null => {\n    const messageItem = item as TMessage\n\n    if (!messageItem._id && messageItem._id !== 0)\n      warning('GiftedChat: `_id` is missing for message', JSON.stringify(item))\n\n    if (!messageItem.user) {\n      if (!messageItem.system)\n        warning(\n          'GiftedChat: `user` is missing for message',\n          JSON.stringify(messageItem)\n        )\n\n      messageItem.user = { _id: 0 }\n    }\n\n    if (messages) {\n      const previousMessage =\n        (isInverted ? messages[index + 1] : messages[index - 1]) || {}\n      const nextMessage =\n        (isInverted ? messages[index - 1] : messages[index + 1]) || {}\n\n      const messageProps: ItemProps<TMessage> = {\n        position: user?._id != null && messageItem.user?._id === user._id ? 'right' : 'left',\n        ...restProps,\n        currentMessage: messageItem,\n        previousMessage,\n        nextMessage,\n        scrolledY,\n        daysPositions,\n        listHeight,\n        isDayAnimationEnabled,\n      }\n\n      return (\n        <Item<TMessage> {...messageProps} />\n      )\n    }\n\n    return null\n  }, [messages, restProps, isInverted, scrolledY, daysPositions, listHeight, isDayAnimationEnabled, user])\n\n  const emptyContent = useMemo(() => {\n    if (!renderChatEmptyProp)\n      return null\n\n    return renderChatEmptyProp()\n  }, [renderChatEmptyProp])\n\n  const renderChatEmpty = useCallback(() => {\n    if (renderChatEmptyProp)\n      return isInverted\n        ? (\n          emptyContent\n        )\n        : (\n          <View style={[stylesCommon.fill, styles.emptyChatContainer]}>\n            {emptyContent}\n          </View>\n        )\n\n    return <View style={stylesCommon.fill} />\n  }, [isInverted, renderChatEmptyProp, emptyContent])\n\n  const ListHeaderComponent = useMemo(() => {\n    const content = renderLoadEarlier()\n\n    if (!content)\n      return null\n\n    return (\n      <View style={stylesCommon.fill}>{content}</View>\n    )\n  }, [renderLoadEarlier])\n\n  const renderScrollBottomComponent = useCallback(() => {\n    if (scrollToBottomComponentProp)\n      return scrollToBottomComponentProp()\n\n    return <Text>{'V'}</Text>\n  }, [scrollToBottomComponentProp])\n\n  const handleScrollToBottomPress = useCallback(() => {\n    doScrollToBottom()\n  }, [doScrollToBottom])\n\n  const scrollToBottomContent = useMemo(() => {\n    return (\n      <Animated.View\n        style={[\n          stylesCommon.centerItems,\n          styles.scrollToBottomContent,\n          scrollToBottomContentStyle,\n          scrollToBottomStyleAnim,\n        ]}\n      >\n        {renderScrollBottomComponent()}\n      </Animated.View>\n    )\n  }, [scrollToBottomStyleAnim, scrollToBottomContentStyle, renderScrollBottomComponent])\n\n  const ScrollToBottomWrapper = useCallback(() => {\n    if (!isScrollToBottomEnabled)\n      return null\n\n    if (!isScrollToBottomVisible)\n      return null\n\n    return (\n      <Pressable\n        style={[styles.scrollToBottom, scrollToBottomStyle]}\n        onPress={handleScrollToBottomPress}\n      >\n        {scrollToBottomContent}\n      </Pressable>\n    )\n  }, [isScrollToBottomEnabled, isScrollToBottomVisible, handleScrollToBottomPress, scrollToBottomContent, scrollToBottomStyle])\n\n  const onLayoutList = useCallback((event: LayoutChangeEvent) => {\n    listHeight.value = event.nativeEvent.layout.height\n\n    if (\n      !isInverted &&\n      messages?.length &&\n      isScrollToBottomEnabled\n    )\n      setTimeout(() => {\n        doScrollToBottom(false)\n      }, 500)\n\n    // listProps.onLayout may be a SharedValue in Reanimated types, but we only accept functions\n    const onLayoutProp = listProps?.onLayout as ((event: LayoutChangeEvent) => void) | undefined\n    onLayoutProp?.(event)\n  }, [isInverted, messages, doScrollToBottom, listHeight, listProps, isScrollToBottomEnabled])\n\n  const onEndReached = useCallback(() => {\n    if (\n      loadEarlierMessagesProps &&\n      loadEarlierMessagesProps.isAvailable &&\n      loadEarlierMessagesProps.isInfiniteScrollEnabled &&\n      !loadEarlierMessagesProps.isLoading\n    )\n      loadEarlierMessagesProps.onPress()\n  }, [loadEarlierMessagesProps])\n\n  const keyExtractor = useCallback((item: unknown) => (item as TMessage)._id.toString(), [])\n\n  const renderCell = useCallback((props: CellRendererProps<unknown>) => {\n    const { item, onLayout: onLayoutProp, children } = props\n    const id = (item as IMessage)._id.toString()\n\n    const handleOnLayout = (event: LayoutChangeEvent) => {\n      onLayoutProp?.(event)\n\n      // Only track positions when day animation is enabled\n      if (!isDayAnimationEnabled)\n        return\n\n      const { y, height } = event.nativeEvent.layout\n\n      const newValue = {\n        y,\n        height,\n        createdAt: new Date((item as IMessage).createdAt).getTime(),\n      }\n\n      daysPositions.modify(value => {\n        'worklet'\n\n        const isSameDay = (date1: number, date2: number) => {\n          const d1 = new Date(date1)\n          const d2 = new Date(date2)\n\n          return (\n            d1.getDate() === d2.getDate() &&\n            d1.getMonth() === d2.getMonth() &&\n            d1.getFullYear() === d2.getFullYear()\n          )\n        }\n\n        for (const [key, item] of Object.entries(value))\n          if (isSameDay(newValue.createdAt, item.createdAt) && (isInverted ? item.y <= newValue.y : item.y >= newValue.y)) {\n            delete value[key]\n            break\n          }\n\n        // @ts-expect-error: https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue#remarks\n        value[id] = newValue\n        return value\n      })\n    }\n\n    return (\n      <View\n        {...props}\n        onLayout={handleOnLayout}\n      >\n        {children}\n      </View>\n    )\n  }, [daysPositions, isInverted, isDayAnimationEnabled])\n\n  const scrollHandler = useAnimatedScrollHandler({\n    onScroll: event => {\n      scrolledY.value = event.contentOffset.y\n\n      runOnJS(handleOnScroll)(event)\n    },\n  }, [handleOnScroll])\n\n  // removes unrendered days positions when messages are added/removed\n  useEffect(() => {\n    // Skip cleanup when day animation is disabled\n    if (!isDayAnimationEnabled)\n      return\n\n    Object.keys(daysPositions.value).forEach(key => {\n      const messageIndex = messages.findIndex(m => m._id.toString() === key)\n      let shouldRemove = messageIndex === -1\n\n      if (!shouldRemove) {\n        const prevMessage = messages[messageIndex + (isInverted ? 1 : -1)]\n        const message = messages[messageIndex]\n        shouldRemove = !!prevMessage && isSameDay(message, prevMessage)\n      }\n\n      if (shouldRemove)\n        daysPositions.modify(value => {\n          'worklet'\n\n          delete value[key]\n          return value\n        })\n    })\n  }, [messages, daysPositions, isInverted, isDayAnimationEnabled])\n\n  return (\n    <View\n      style={[\n        styles.contentContainerStyle,\n        isAlignedTop ? styles.containerAlignTop : stylesCommon.fill,\n      ]}\n    >\n      <AnimatedFlatList\n        ref={forwardRef}\n        keyExtractor={keyExtractor}\n        data={messages}\n        renderItem={renderItem}\n        inverted={isInverted}\n        automaticallyAdjustContentInsets={false}\n        style={stylesCommon.fill}\n        contentContainerStyle={styles.messagesContainer}\n        ListEmptyComponent={renderChatEmpty}\n        ListFooterComponent={\n          isInverted ? ListHeaderComponent : <>{ListFooterComponent}</>\n        }\n        ListHeaderComponent={\n          isInverted ? <>{ListFooterComponent}</> : ListHeaderComponent\n        }\n        scrollEventThrottle={1}\n        onEndReached={onEndReached}\n        onEndReachedThreshold={0.1}\n        keyboardDismissMode='interactive'\n        keyboardShouldPersistTaps='handled'\n        {...listProps}\n        onScroll={scrollHandler}\n        onLayout={onLayoutList}\n        CellRendererComponent={renderCell}\n      />\n      <ScrollToBottomWrapper />\n      {isDayAnimationEnabled && (\n        <DayAnimated\n          scrolledY={scrolledY}\n          daysPositions={daysPositions}\n          listHeight={listHeight}\n          renderDay={renderDayProp}\n          messages={messages}\n          isLoading={loadEarlierMessagesProps?.isLoading ?? false}\n          dateFormat={props.dateFormat}\n          dateFormatCalendar={props.dateFormatCalendar}\n        />\n      )}\n    </View>\n  )\n}\n"
  },
  {
    "path": "src/MessagesContainer/styles.ts",
    "content": "import { Platform, StyleSheet } from 'react-native'\nimport { Color } from '../Color'\n\nexport default StyleSheet.create({\n  containerAlignTop: {\n    flexDirection: 'row',\n    alignItems: 'flex-start',\n  },\n  contentContainerStyle: {\n    flexGrow: 1,\n    justifyContent: 'flex-start',\n  },\n  messagesContainer: {\n    paddingBottom: 10,\n  },\n  emptyChatContainer: {\n    transform: [{ scaleY: -1 }],\n  },\n  scrollToBottom: {\n    position: 'absolute',\n    right: 10,\n    bottom: 30,\n    zIndex: 999,\n  },\n  scrollToBottomContent: {\n    height: 40,\n    width: 40,\n    borderRadius: 20,\n    backgroundColor: Color.white,\n    ...Platform.select({\n      ios: {\n        shadowColor: Color.black,\n        shadowOpacity: 0.5,\n        shadowOffset: { width: 0, height: 0 },\n        shadowRadius: 1,\n      },\n      android: {\n        elevation: 5,\n      },\n    }),\n  },\n})\n"
  },
  {
    "path": "src/MessagesContainer/types.ts",
    "content": "import { RefObject } from 'react'\nimport {\n  FlatListProps,\n  StyleProp,\n  ViewStyle,\n} from 'react-native'\nimport { FlatList } from 'react-native-gesture-handler'\nimport Animated, { ScrollEvent } from 'react-native-reanimated'\n\nimport { DayProps } from '../Day'\nimport { LoadEarlierMessagesProps } from '../LoadEarlierMessages'\nimport { MessageProps } from '../Message'\nimport { User, IMessage, Reply } from '../Models'\nimport { ReplyProps } from '../Reply'\nimport { TypingIndicatorProps } from '../TypingIndicator/types'\n\n/** Animated FlatList created from react-native-gesture-handler's FlatList */\nconst RNGHAnimatedFlatList = Animated.createAnimatedComponent(FlatList)\n\n/**\n * Typed AnimatedFlatList component that preserves generic type parameter.\n * Uses react-native-gesture-handler's FlatList which respects keyboardShouldPersistTaps.\n */\nexport const AnimatedFlatList = RNGHAnimatedFlatList as <TMessage>(\n  props: FlatListProps<TMessage> & {\n    ref?: RefObject<FlatList<TMessage>>\n  }\n) => React.ReactElement\n\nexport type AnimatedListProps<TMessage extends IMessage = IMessage> = Partial<\n  Omit<FlatListProps<TMessage>, 'onScroll'> & {\n    onScroll?: (event: ScrollEvent) => void\n  }\n>\n\nexport type AnimatedList<TMessage> = FlatList<TMessage>\n\nexport interface MessagesContainerProps<TMessage extends IMessage = IMessage>\n  extends Omit<TypingIndicatorProps, 'style'> {\n  /** Ref for the FlatList message container */\n  forwardRef?: RefObject<AnimatedList<TMessage>>\n  /** Messages to display */\n  messages?: TMessage[]\n  /** Format to use for rendering dates; default is 'll' */\n  dateFormat?: string\n  /** Format to use for rendering relative times */\n  dateFormatCalendar?: object\n  /** User sending the messages: { _id, name, avatar } */\n  user?: User\n  /** Additional props for FlatList */\n  listProps?: AnimatedListProps<TMessage>\n  /** Reverses display order of messages; default is true */\n  isInverted?: boolean\n  /** Controls whether or not the message bubbles appear at the top of the chat */\n  isAlignedTop?: boolean\n  /** Enables the isScrollToBottomEnabled Component */\n  isScrollToBottomEnabled?: boolean\n  /** Scroll to bottom wrapper style */\n  scrollToBottomStyle?: StyleProp<ViewStyle>\n  /** Scroll to bottom content style */\n  scrollToBottomContentStyle?: StyleProp<ViewStyle>\n  /** Distance from bottom before showing scroll to bottom button */\n  scrollToBottomOffset?: number\n  /** Custom component to render when messages are empty */\n  renderChatEmpty?: () => React.ReactNode\n  /** Custom footer component on the ListView, e.g. 'User is typing...' */\n  renderFooter?: (props: MessagesContainerProps<TMessage>) => React.ReactNode\n  /** Custom message container */\n  renderMessage?: (props: MessageProps<TMessage>) => React.ReactElement\n  /** Custom day above a message */\n  renderDay?: (props: DayProps) => React.ReactNode\n  /** Custom \"Load earlier messages\" button */\n  renderLoadEarlier?: (props: LoadEarlierMessagesProps) => React.ReactNode\n  /** Custom typing indicator */\n  renderTypingIndicator?: () => React.ReactNode\n  /** Scroll to bottom custom component */\n  scrollToBottomComponent?: () => React.ReactNode\n  /** Callback when quick reply is sent */\n  onQuickReply?: (replies: Reply[]) => void\n  /** Props to pass to the LoadEarlierMessages component. The LoadEarlierMessages button is only visible when isAvailable is true. Includes isAvailable (controls button visibility), isInfiniteScrollEnabled (infinite scroll up when reach the top of messages container, automatically call onPress function if it exists - not yet supported for web), onPress (callback when button is pressed), isLoading (display loading indicator), label (override default \"Load earlier messages\" text), and styling props (containerStyle, wrapperStyle, textStyle, activityIndicatorStyle, activityIndicatorColor, activityIndicatorSize). */\n  loadEarlierMessagesProps?: LoadEarlierMessagesProps\n  /** Style for TypingIndicator component */\n  typingIndicatorStyle?: StyleProp<ViewStyle>\n  /** Enable animated day label that appears on scroll; default is true */\n  isDayAnimationEnabled?: boolean\n  /** Reply functionality configuration */\n  reply?: ReplyProps<TMessage>\n}\n\nexport interface State {\n  showScrollBottom: boolean\n  hasScrolled: boolean\n}\n\ninterface ViewLayout {\n  x: number\n  y: number\n  width: number\n  height: number\n}\n\nexport type DaysPositions = { [key: string]: ViewLayout & { createdAt: number } }\n"
  },
  {
    "path": "src/Models.ts",
    "content": "import { StyleProp, ViewStyle } from 'react-native'\n\nexport type Omit<T, K> = Pick<T, Exclude<keyof T, K>>\n\nexport interface LeftRightStyle<T> {\n  left?: StyleProp<T>\n  right?: StyleProp<T>\n}\n\ntype renderFunction = (x: unknown) => React.ReactNode\n\nexport interface User {\n  _id: string | number\n  name?: string\n  avatar?: string | number | renderFunction\n}\n\nexport interface Reply {\n  title: string\n  value: string\n  messageId?: number | string\n}\n\nexport interface QuickReplies {\n  type: 'radio' | 'checkbox'\n  values: Reply[]\n  keepIt?: boolean\n}\n\nexport interface ReplyMessage extends Pick<IMessage, '_id' | 'text' | 'user' | 'audio' | 'image'> {}\n\nexport interface IMessage {\n  _id: string | number\n  text: string\n  createdAt: Date | number\n  user: User\n  image?: string\n  video?: string\n  audio?: string\n  system?: boolean\n  sent?: boolean\n  received?: boolean\n  pending?: boolean\n  quickReplies?: QuickReplies\n  replyMessage?: ReplyMessage\n  location?: {\n    latitude: number\n    longitude: number\n  }\n}\n\nexport type IChatMessage = IMessage\n\nexport interface MessageVideoProps<TMessage extends IMessage> {\n  currentMessage: TMessage\n  containerStyle?: StyleProp<ViewStyle>\n  videoStyle?: StyleProp<ViewStyle>\n  videoProps?: object\n}\n\nexport interface MessageAudioProps<TMessage extends IMessage> {\n  currentMessage: TMessage\n  containerStyle?: StyleProp<ViewStyle>\n  audioStyle?: StyleProp<ViewStyle>\n  audioProps?: object\n}\n"
  },
  {
    "path": "src/QuickReplies.tsx",
    "content": "import React, { useState, useMemo, useCallback } from 'react'\nimport {\n  StyleSheet,\n  View,\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n} from 'react-native'\nimport { Text } from 'react-native-gesture-handler'\nimport { Color } from './Color'\nimport { TouchableOpacity } from './components/TouchableOpacity'\nimport { warning } from './logging'\nimport { IMessage, Reply } from './Models'\nimport stylesCommon from './styles'\n\nconst styles = StyleSheet.create({\n  container: {\n    flexDirection: 'row',\n    flexWrap: 'wrap',\n    maxWidth: 300,\n  },\n  quickReply: {\n    borderWidth: 1,\n    maxWidth: 200,\n    paddingVertical: 7,\n    paddingHorizontal: 12,\n    minHeight: 50,\n    borderRadius: 13,\n    margin: 3,\n  },\n  quickReplyText: {\n    overflow: 'visible',\n  },\n  sendLink: {\n    borderWidth: 0,\n  },\n  sendLinkText: {\n    color: Color.defaultBlue,\n    fontWeight: '600',\n    fontSize: 17,\n  },\n})\n\nexport interface QuickRepliesProps<TMessage extends IMessage = IMessage> {\n  nextMessage?: TMessage\n  currentMessage: TMessage\n  color?: string\n  sendText?: string\n  quickReplyStyle?: StyleProp<ViewStyle>\n  quickReplyTextStyle?: StyleProp<TextStyle>\n  quickReplyContainerStyle?: StyleProp<ViewStyle>\n  onQuickReply?(reply: Reply[]): void\n  renderQuickReplySend?(): React.ReactNode\n}\n\nconst sameReply = (currentReply: Reply) => (reply: Reply) =>\n  currentReply.value === reply.value\n\nconst diffReply = (currentReply: Reply) => (reply: Reply) =>\n  currentReply.value !== reply.value\n\nexport function QuickReplies ({\n  currentMessage,\n  nextMessage,\n  color = Color.peterRiver,\n  quickReplyStyle,\n  quickReplyTextStyle,\n  quickReplyContainerStyle,\n  onQuickReply,\n  sendText = 'Send',\n  renderQuickReplySend,\n}: QuickRepliesProps<IMessage>) {\n  const { type } = currentMessage!.quickReplies!\n  const [replies, setReplies] = useState<Reply[]>([])\n\n  const shouldComponentDisplay = useMemo(() => {\n    const hasReplies = !!currentMessage && !!currentMessage!.quickReplies\n    const hasNext = !!nextMessage && !!nextMessage!._id\n    const keepIt = currentMessage!.quickReplies!.keepIt\n\n    if (hasReplies && !hasNext)\n      return true\n\n    if (hasReplies && hasNext && keepIt)\n      return true\n\n    return false\n  }, [currentMessage, nextMessage])\n\n  const handleSend = useCallback((repliesData: Reply[]) => () => {\n    onQuickReply?.(\n      repliesData.map((reply: Reply) => ({\n        ...reply,\n        messageId: currentMessage!._id,\n      }))\n    )\n  }, [onQuickReply, currentMessage])\n\n  const handlePress = useCallback(\n    (reply: Reply) => () => {\n      if (currentMessage) {\n        const { type } = currentMessage.quickReplies!\n        switch (type) {\n          case 'radio': {\n            handleSend([reply])()\n            return\n          }\n          case 'checkbox': {\n            if (replies.find(sameReply(reply)))\n              setReplies(replies.filter(diffReply(reply)))\n            else\n              setReplies([...replies, reply])\n\n            return\n          }\n          default: {\n            warning(`onQuickReply unknown type: ${type}`)\n          }\n        }\n      }\n    },\n    [replies, currentMessage, handleSend]\n  )\n\n  const renderSendButton = useMemo(() => {\n    if (!replies.length)\n      return null\n\n    return (\n      <TouchableOpacity\n        style={[stylesCommon.centerItems, styles.quickReply, styles.sendLink]}\n        onPress={handleSend(replies)}\n      >\n        {renderQuickReplySend?.() || (\n          <Text style={styles.sendLinkText}>{sendText}</Text>\n        )}\n      </TouchableOpacity>\n    )\n  }, [replies, handleSend, renderQuickReplySend, sendText])\n\n  if (!shouldComponentDisplay)\n    return null\n\n  return (\n    <View style={[styles.container, quickReplyContainerStyle]}>\n      {currentMessage!.quickReplies!.values.map(\n        (reply: Reply, index: number) => {\n          const selected =\n            type === 'checkbox' && replies.find(sameReply(reply))\n\n          return (\n            <TouchableOpacity\n              onPress={handlePress(reply)}\n              style={[\n                stylesCommon.centerItems,\n                styles.quickReply,\n                quickReplyStyle,\n                { borderColor: color },\n                selected && { backgroundColor: color },\n              ]}\n              key={`${reply.value}-${index}`}\n            >\n              <Text\n                numberOfLines={10}\n                ellipsizeMode='tail'\n                style={[\n                  styles.quickReplyText,\n                  { color: selected ? Color.white : color },\n                  quickReplyTextStyle,\n                ]}\n              >\n                {reply.title}\n              </Text>\n            </TouchableOpacity>\n          )\n        }\n      )}\n      {renderSendButton}\n    </View>\n  )\n}\n"
  },
  {
    "path": "src/Reply/index.ts",
    "content": "export * from './types'\n"
  },
  {
    "path": "src/Reply/types.ts",
    "content": "import { ImageStyle, StyleProp, TextStyle, ViewStyle } from 'react-native'\nimport { SharedValue } from 'react-native-reanimated'\n\nimport { MessageReplyProps } from '../components/MessageReply'\nimport { ReplyPreviewProps } from '../components/ReplyPreview'\nimport { IMessage, ReplyMessage } from '../Models'\n\n/**\n * Props for swipe-to-reply gesture behavior\n */\nexport interface SwipeToReplyProps<TMessage extends IMessage> {\n  /** Enable swipe to reply on messages; default is false */\n  isEnabled?: boolean\n  /** Direction to swipe for reply; default is 'left' (swipe left, icon appears on right) */\n  direction?: 'left' | 'right'\n  /** Callback when swipe to reply is triggered */\n  onSwipe?: (message: TMessage) => void\n  /** Custom render for swipe action indicator */\n  renderAction?: (\n    progress: SharedValue<number>,\n    translation: SharedValue<number>,\n    position: 'left' | 'right'\n  ) => React.ReactNode\n  /** Style for the swipe action container */\n  actionContainerStyle?: StyleProp<ViewStyle>\n}\n\n/**\n * Props for reply preview shown above input toolbar\n */\nexport interface ReplyPreviewStyleProps {\n  /** Style for reply preview container */\n  containerStyle?: StyleProp<ViewStyle>\n  /** Style for reply preview text */\n  textStyle?: StyleProp<TextStyle>\n  /** Style for reply preview image */\n  imageStyle?: StyleProp<ImageStyle>\n}\n\n/**\n * Props for message reply display inside bubble\n */\nexport interface MessageReplyStyleProps {\n  /** Style for message reply container */\n  containerStyle?: StyleProp<ViewStyle>\n  /** Style for message reply container on left side */\n  containerStyleLeft?: StyleProp<ViewStyle>\n  /** Style for message reply container on right side */\n  containerStyleRight?: StyleProp<ViewStyle>\n  /** Style for message reply image */\n  imageStyle?: StyleProp<ImageStyle>\n  /** Style for message reply text */\n  textStyle?: StyleProp<TextStyle>\n  /** Style for message reply text on left side */\n  textStyleLeft?: StyleProp<TextStyle>\n  /** Style for message reply text on right side */\n  textStyleRight?: StyleProp<TextStyle>\n}\n\n/**\n * Grouped props for reply functionality\n */\nexport interface ReplyProps<TMessage extends IMessage> {\n  /** Reply message to show in input toolbar preview */\n  message?: ReplyMessage | null\n  /** Callback when reply is cleared */\n  onClear?: () => void\n  /** Callback when message reply is pressed inside bubble */\n  onPress?: (replyMessage: ReplyMessage) => void\n  /** Custom render for reply preview in input toolbar */\n  renderPreview?: (props: ReplyPreviewProps) => React.ReactNode\n  /** Custom render for message reply inside bubble */\n  renderMessageReply?: (props: MessageReplyProps<TMessage>) => React.ReactNode\n  /** Swipe-to-reply configuration */\n  swipe?: SwipeToReplyProps<TMessage>\n  /** Reply preview styling */\n  previewStyle?: ReplyPreviewStyleProps\n  /** Message reply styling */\n  messageStyle?: MessageReplyStyleProps\n}\n"
  },
  {
    "path": "src/ReplyPreview.tsx",
    "content": "import React, { useMemo } from 'react'\nimport { StyleSheet, View, StyleProp, ViewStyle, TextStyle, Pressable } from 'react-native'\n\nimport { Text } from 'react-native-gesture-handler'\nimport { Color } from './Color'\nimport { useColorScheme } from './hooks/useColorScheme'\nimport { ReplyMessage } from './Models'\n\nexport interface ReplyPreviewProps {\n  replyMessage: ReplyMessage\n  onClearReply: () => void\n  containerStyle?: StyleProp<ViewStyle>\n  usernameStyle?: StyleProp<TextStyle>\n  textStyle?: StyleProp<TextStyle>\n  clearButtonStyle?: StyleProp<ViewStyle>\n  clearButtonTextStyle?: StyleProp<TextStyle>\n}\n\nexport function ReplyPreview ({\n  replyMessage,\n  onClearReply,\n  containerStyle,\n  usernameStyle,\n  textStyle,\n  clearButtonStyle,\n  clearButtonTextStyle,\n}: ReplyPreviewProps) {\n  const colorScheme = useColorScheme()\n\n  const containerStyles = useMemo(() => [\n    styles.container,\n    colorScheme === 'dark' && styles.container_dark,\n    containerStyle,\n  ], [colorScheme, containerStyle])\n\n  const usernameStyles = useMemo(() => [\n    styles.username,\n    colorScheme === 'dark' && styles.username_dark,\n    usernameStyle,\n  ], [colorScheme, usernameStyle])\n\n  const textStyles = useMemo(() => [\n    styles.text,\n    colorScheme === 'dark' && styles.text_dark,\n    textStyle,\n  ], [colorScheme, textStyle])\n\n  return (\n    <View style={containerStyles}>\n      <View style={styles.border} />\n      <View style={styles.content}>\n        <Text\n          style={usernameStyles}\n          numberOfLines={1}\n        >\n          {replyMessage.user?.name || 'User'}\n        </Text>\n        <Text\n          style={textStyles}\n          numberOfLines={1}\n        >\n          {replyMessage.text || (replyMessage.image ? 'Photo' : (replyMessage.audio ? 'Audio' : 'Message'))}\n        </Text>\n      </View>\n      <Pressable\n        onPress={onClearReply}\n        style={[styles.clearButton, clearButtonStyle]}\n        hitSlop={8}\n      >\n        <Text style={[styles.clearButtonText, clearButtonTextStyle]}>\n          {'✕'}\n        </Text>\n      </Pressable>\n    </View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingHorizontal: 10,\n    paddingVertical: 8,\n    backgroundColor: '#f5f5f5',\n    borderBottomWidth: StyleSheet.hairlineWidth,\n    borderBottomColor: Color.defaultColor,\n  },\n  container_dark: {\n    backgroundColor: '#2a2a2a',\n    borderBottomColor: '#444',\n  },\n  border: {\n    width: 3,\n    height: '100%',\n    backgroundColor: Color.defaultBlue,\n    borderRadius: 1.5,\n    marginRight: 10,\n  },\n  content: {\n    flex: 1,\n  },\n  username: {\n    fontSize: 13,\n    fontWeight: '600',\n    color: Color.defaultBlue,\n    marginBottom: 2,\n  },\n  username_dark: {\n    color: '#6eb5ff',\n  },\n  text: {\n    fontSize: 13,\n    color: '#666',\n  },\n  text_dark: {\n    color: '#999',\n  },\n  clearButton: {\n    width: 24,\n    height: 24,\n    borderRadius: 12,\n    backgroundColor: Color.defaultColor,\n    justifyContent: 'center',\n    alignItems: 'center',\n    marginLeft: 10,\n  },\n  clearButtonText: {\n    fontSize: 12,\n    fontWeight: '600',\n    color: '#666',\n  },\n})\n"
  },
  {
    "path": "src/Send.tsx",
    "content": "import React, { useMemo, useCallback, useEffect } from 'react'\nimport {\n  StyleSheet,\n  StyleProp,\n  ViewStyle,\n  TextStyle,\n} from 'react-native'\nimport { Text } from 'react-native-gesture-handler'\nimport Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated'\n\nimport { Color } from './Color'\nimport { TouchableOpacity, TouchableOpacityProps } from './components/TouchableOpacity'\nimport { TEST_ID } from './Constant'\nimport { useColorScheme } from './hooks/useColorScheme'\nimport { IMessage } from './Models'\nimport { getColorSchemeStyle } from './styles'\n\nexport interface SendProps<TMessage extends IMessage> {\n  text?: string\n  label?: string\n  containerStyle?: StyleProp<ViewStyle>\n  textStyle?: StyleProp<TextStyle>\n  children?: React.ReactNode\n  /** Always show send button, even when text is empty */\n  isSendButtonAlwaysVisible?: boolean\n  /** Text is optional, allow sending empty messages (useful for media-only messages) */\n  isTextOptional?: boolean\n  sendButtonProps?: Partial<TouchableOpacityProps>\n  onSend?(\n    messages: Partial<TMessage> | Partial<TMessage>[],\n    shouldResetInputToolbar: boolean,\n  ): void\n}\n\nexport const Send = <TMessage extends IMessage = IMessage>({\n  text,\n  containerStyle,\n  children,\n  textStyle,\n  label = 'Send',\n  isSendButtonAlwaysVisible = false,\n  isTextOptional = false,\n  sendButtonProps,\n  onSend,\n}: SendProps<TMessage>) => {\n  const colorScheme = useColorScheme()\n  const opacity = useSharedValue(0)\n\n  const handleOnPress = useCallback(() => {\n    const trimmedText = text?.trim() ?? ''\n    const message = { text: trimmedText } as Partial<TMessage>\n\n    if (onSend && (trimmedText.length || isTextOptional))\n      onSend(message, true)\n  }, [text, onSend, isTextOptional])\n\n  const isVisible = useMemo(\n    () => isSendButtonAlwaysVisible || !!text?.trim().length,\n    [isSendButtonAlwaysVisible, text]\n  )\n\n  useEffect(() => {\n    opacity.value = withTiming(isVisible ? 1 : 0, { duration: 200 })\n  }, [isVisible, opacity])\n\n  const animatedStyle = useAnimatedStyle(() => ({\n    opacity: opacity.value,\n  }), [opacity])\n\n  return (\n    <Animated.View style={[styles.container, containerStyle, animatedStyle]} pointerEvents={isVisible ? 'auto' : 'none'}>\n      <TouchableOpacity\n        testID={TEST_ID.SEND_TOUCHABLE}\n        style={styles.touchable}\n        onPress={handleOnPress}\n        accessible\n        accessibilityLabel='send'\n        accessibilityRole='button'\n        {...sendButtonProps}\n      >\n        {\n          children ||\n          <Text\n            style={[\n              getColorSchemeStyle(styles, 'text', colorScheme),\n              textStyle,\n            ]}\n          >\n            {label}\n          </Text>\n        }\n      </TouchableOpacity>\n    </Animated.View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    justifyContent: 'flex-end',\n  },\n  touchable: {\n    justifyContent: 'flex-end',\n  },\n  text: {\n    color: Color.defaultBlue,\n    fontWeight: '600',\n    fontSize: 17,\n    backgroundColor: Color.backgroundTransparent,\n    paddingVertical: 10,\n    paddingHorizontal: 10,\n  },\n  text_dark: {\n    color: '#4da6ff',\n  },\n})\n"
  },
  {
    "path": "src/SystemMessage.tsx",
    "content": "import React from 'react'\nimport {\n  StyleSheet,\n  View,\n  ViewStyle,\n  StyleProp,\n  TextStyle,\n} from 'react-native'\nimport { Color } from './Color'\nimport { MessageText, MessageTextProps } from './MessageText'\nimport { IMessage } from './Models'\nimport stylesCommon from './styles'\n\nexport interface SystemMessageProps<TMessage extends IMessage> {\n  currentMessage: TMessage\n  containerStyle?: StyleProp<ViewStyle>\n  messageContainerStyle?: StyleProp<ViewStyle>\n  textStyle?: StyleProp<TextStyle>\n  messageTextProps?: Partial<MessageTextProps<TMessage>>\n  children?: React.ReactNode\n}\n\nexport function SystemMessage<TMessage extends IMessage> ({\n  currentMessage,\n  containerStyle,\n  messageContainerStyle,\n  textStyle,\n  messageTextProps,\n  children,\n}: SystemMessageProps<TMessage>) {\n  if (currentMessage == null)\n    return null\n\n  return (\n    <View style={[stylesCommon.fill, styles.wrapper]}>\n      <View style={[styles.container, containerStyle]}>\n        {\n          !!currentMessage.text && (\n            <MessageText\n              currentMessage={currentMessage}\n              customTextStyle={[styles.text, textStyle]}\n              position='left'\n              containerStyle={{ left: [styles.messageContainer, messageContainerStyle] }}\n              {...messageTextProps}\n            />\n          )\n        }\n        {children}\n      </View>\n    </View>\n  )\n}\n\nconst styles = StyleSheet.create({\n  wrapper: {\n    alignItems: 'center',\n    marginVertical: 5,\n    marginHorizontal: 10,\n  },\n  container: {\n    maxWidth: '70%',\n    borderRadius: 20,\n    borderWidth: 1,\n    borderColor: 'rgba(0,0,0,0.1)',\n    paddingHorizontal: 10,\n    paddingVertical: 10,\n    backgroundColor: 'rgba(0,0,0,0.05)',\n  },\n  messageContainer: {\n    marginVertical: 0,\n    marginHorizontal: 0,\n  },\n  text: {\n    backgroundColor: Color.backgroundTransparent,\n    fontSize: 12,\n    fontWeight: '300',\n    textAlign: 'center',\n  },\n})\n"
  },
  {
    "path": "src/Time.tsx",
    "content": "import React, { useMemo } from 'react'\nimport { StyleSheet, View, ViewStyle, TextStyle } from 'react-native'\nimport dayjs from 'dayjs'\n\nimport { Text } from 'react-native-gesture-handler'\nimport { Color } from './Color'\nimport { TIME_FORMAT } from './Constant'\nimport { useChatContext } from './GiftedChatContext'\nimport { LeftRightStyle, IMessage } from './Models'\nimport { getStyleWithPosition } from './styles'\n\nconst styles = StyleSheet.create({\n  text: {\n    fontSize: 10,\n    textAlign: 'right',\n  },\n  text_left: {\n    color: Color.timeTextColor,\n  },\n  text_right: {\n    color: Color.white,\n  },\n})\n\nexport interface TimeProps<TMessage extends IMessage> {\n  position?: 'left' | 'right'\n  currentMessage: TMessage\n  containerStyle?: LeftRightStyle<ViewStyle>\n  timeTextStyle?: LeftRightStyle<TextStyle>\n  timeFormat?: string\n}\n\nexport const Time = <TMessage extends IMessage = IMessage>({\n  position = 'left',\n  containerStyle,\n  currentMessage,\n  timeFormat = TIME_FORMAT,\n  timeTextStyle,\n}: TimeProps<TMessage>) => {\n  const { getLocale } = useChatContext()\n\n  const formattedTime = useMemo(() => {\n    if (!currentMessage)\n      return null\n\n    return dayjs(currentMessage.createdAt).locale(getLocale()).format(timeFormat)\n  }, [currentMessage, getLocale, timeFormat])\n\n  if (!currentMessage)\n    return null\n\n  return (\n    <View style={containerStyle?.[position]}>\n      <Text\n        style={[\n          getStyleWithPosition(styles, 'text', position),\n          timeTextStyle?.[position],\n        ]}\n      >\n        {formattedTime}\n      </Text>\n    </View>\n  )\n}\n"
  },
  {
    "path": "src/TypingIndicator/index.tsx",
    "content": "import React, { useCallback, useEffect, useState, useMemo } from 'react'\nimport { View } from 'react-native'\nimport Animated, {\n  runOnJS,\n  useAnimatedStyle,\n  useSharedValue,\n  withDelay,\n  withRepeat,\n  withSequence,\n  withTiming,\n} from 'react-native-reanimated'\nimport stylesCommon from '../styles'\n\nimport styles from './styles'\nimport { TypingIndicatorProps } from './types'\n\nexport * from './types'\n\nconst DotsAnimation = () => {\n  const dot1 = useSharedValue(0)\n  const dot2 = useSharedValue(0)\n  const dot3 = useSharedValue(0)\n\n  const topY = useMemo(() => -5, [])\n  const bottomY = useMemo(() => 5, [])\n  const duration = useMemo(() => 500, [])\n\n  const dot1Style = useAnimatedStyle(() => ({\n    transform: [{\n      translateY: dot1.value,\n    }],\n  }), [dot1])\n\n  const dot2Style = useAnimatedStyle(() => ({\n    transform: [{\n      translateY: dot2.value,\n    }],\n  }), [dot2])\n\n  const dot3Style = useAnimatedStyle(() => ({\n    transform: [{\n      translateY: dot3.value,\n    }],\n  }), [dot3])\n\n  useEffect(() => {\n    dot1.value = withRepeat(\n      withSequence(\n        withTiming(topY, { duration }),\n        withTiming(bottomY, { duration })\n      ),\n      0,\n      true\n    )\n  }, [dot1, topY, bottomY, duration])\n\n  useEffect(() => {\n    dot2.value = withDelay(100,\n      withRepeat(\n        withSequence(\n          withTiming(topY, { duration }),\n          withTiming(bottomY, { duration })\n        ),\n        0,\n        true\n      )\n    )\n  }, [dot2, topY, bottomY, duration])\n\n  useEffect(() => {\n    dot3.value = withDelay(200,\n      withRepeat(\n        withSequence(\n          withTiming(topY, { duration }),\n          withTiming(bottomY, { duration })\n        ),\n        0,\n        true\n      )\n    )\n  }, [dot3, topY, bottomY, duration])\n\n  return (\n    <View style={[stylesCommon.fill, stylesCommon.centerItems, styles.dots]}>\n      <Animated.View style={[styles.dot, dot1Style]} />\n      <Animated.View style={[styles.dot, dot2Style]} />\n      <Animated.View style={[styles.dot, dot3Style]} />\n    </View>\n  )\n}\n\nexport const TypingIndicator = ({ isTyping, style }: TypingIndicatorProps) => {\n  const yCoords = useSharedValue(200)\n  const heightScale = useSharedValue(0)\n  const marginScale = useSharedValue(0)\n\n  const [isVisible, setIsVisible] = useState(isTyping)\n\n  const containerStyle = useAnimatedStyle(() => ({\n    transform: [\n      {\n        translateY: yCoords.value,\n      },\n    ],\n    height: heightScale.value,\n    marginBottom: marginScale.value,\n  }), [yCoords, heightScale, marginScale])\n\n  const slideIn = useCallback(() => {\n    const duration = 250\n\n    yCoords.value = withTiming(0, { duration })\n    heightScale.value = withTiming(35, { duration })\n    marginScale.value = withTiming(8, { duration })\n  }, [yCoords, heightScale, marginScale])\n\n  const slideOut = useCallback(() => {\n    const duration = 250\n\n    yCoords.value = withTiming(200, { duration }, isFinished => {\n      if (isFinished)\n        runOnJS(setIsVisible)(false)\n    })\n    heightScale.value = withTiming(0, { duration })\n    marginScale.value = withTiming(0, { duration })\n  }, [yCoords, heightScale, marginScale])\n\n  useEffect(() => {\n    if (isVisible)\n      if (isTyping)\n        slideIn()\n      else\n        slideOut()\n  }, [isVisible, isTyping, slideIn, slideOut])\n\n  useEffect(() => {\n    if (isTyping)\n      setIsVisible(true)\n  }, [isTyping])\n\n  if (!isVisible)\n    return null\n\n  return (\n    <Animated.View\n      style={[\n        styles.container,\n        containerStyle,\n        style,\n      ]}\n    >\n      <DotsAnimation />\n    </Animated.View>\n  )\n}\n"
  },
  {
    "path": "src/TypingIndicator/styles.ts",
    "content": "import { StyleSheet } from 'react-native'\nimport { Color } from '../Color'\n\nexport default StyleSheet.create({\n  container: {\n    marginLeft: 8,\n    width: 45,\n    borderRadius: 15,\n    backgroundColor: Color.leftBubbleBackground,\n  },\n  dots: {\n    flexDirection: 'row',\n  },\n  dot: {\n    marginLeft: 2,\n    marginRight: 2,\n    borderRadius: 4,\n    width: 8,\n    height: 8,\n    backgroundColor: 'rgba(0, 0, 0, 0.38)',\n  },\n})\n"
  },
  {
    "path": "src/TypingIndicator/types.ts",
    "content": "import { StyleProp, ViewStyle } from 'react-native'\n\nexport interface TypingIndicatorProps {\n  isTyping?: boolean\n  style?: StyleProp<ViewStyle>\n}\n"
  },
  {
    "path": "src/__tests__/Actions.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Actions } from '..'\n\nit('should render <Actions /> and compare with snapshot', () => {\n  const { toJSON } = render(<Actions />)\n  expect(toJSON()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/Avatar.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Avatar } from '..'\nimport { DEFAULT_TEST_MESSAGE } from './data'\n\nit('should render <Avatar /> and compare with snapshot', () => {\n  const { toJSON } = render(\n    <Avatar\n      renderAvatar={() => 'renderAvatar'}\n      position='left'\n      currentMessage={DEFAULT_TEST_MESSAGE}\n    />\n  )\n\n  expect(toJSON()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/Bubble.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Bubble } from '..'\nimport { DEFAULT_TEST_MESSAGE } from './data'\n\nit('should render <Bubble /> and compare with snapshot', () => {\n  const { toJSON } = render(\n    <Bubble\n      user={{ _id: 1 }}\n      currentMessage={DEFAULT_TEST_MESSAGE}\n      position='left'\n    />\n  )\n\n  expect(toJSON()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/Color.test.tsx",
    "content": "import { Color } from '../Color'\n\nit('should compare Color with snapshot', () => {\n  expect(Color).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/Composer.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Composer } from '..'\n\nit('should render <Composer /> and compare with snapshot', () => {\n  const { toJSON } = render(<Composer />)\n\n  expect(toJSON()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/Constant.test.tsx",
    "content": "import * as Constant from '../Constant'\n\nit('should compare Constant with snapshot', () => {\n  expect(Constant).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/Day.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Day } from '..'\nimport { DEFAULT_TEST_MESSAGE } from './data'\n\ndescribe('Day', () => {\n  it('should not render <Day /> and compare with snapshot', () => {\n    const { toJSON } = render(<Day createdAt={DEFAULT_TEST_MESSAGE.createdAt} />)\n\n    expect(toJSON()).toMatchSnapshot()\n  })\n\n  it('should render <Day /> and compare with snapshot', () => {\n    const { toJSON } = render(\n      <Day createdAt={DEFAULT_TEST_MESSAGE.createdAt} />\n    )\n    expect(toJSON()).toMatchSnapshot()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/DayAnimated.test.tsx",
    "content": "import React from 'react'\nimport { View, Text } from 'react-native'\nimport { render } from '@testing-library/react-native'\nimport { DayProps } from '../Day'\nimport { DayAnimated } from '../MessagesContainer/components/DayAnimated'\nimport { DEFAULT_TEST_MESSAGE } from './data'\n\nconst mockDaysPositions = { value: {} }\nconst mockScrolledY = { value: 0 }\nconst mockListHeight = { value: 800 }\n\ndescribe('DayAnimated', () => {\n  it('should render DayAnimated with default Day component', () => {\n    const { toJSON } = render(\n      <DayAnimated\n        scrolledY={mockScrolledY}\n        daysPositions={mockDaysPositions}\n        listHeight={mockListHeight}\n        messages={[DEFAULT_TEST_MESSAGE]}\n        isLoading={false}\n      />\n    )\n    expect(toJSON()).toMatchSnapshot()\n  })\n\n  it('should use custom renderDay when provided', () => {\n    const customRenderDay = jest.fn((props: DayProps) => (\n      <View testID='custom-day'>\n        <Text>Custom Day: {props.createdAt.toLocaleString()}</Text>\n      </View>\n    ))\n\n    const { toJSON } = render(\n      <DayAnimated\n        scrolledY={mockScrolledY}\n        daysPositions={mockDaysPositions}\n        listHeight={mockListHeight}\n        messages={[DEFAULT_TEST_MESSAGE]}\n        isLoading={false}\n        renderDay={customRenderDay}\n      />\n    )\n\n    expect(toJSON()).toMatchSnapshot()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/GiftedAvatar.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { GiftedAvatar } from '..'\n\nit('should render <GiftedAvatar /> and compare with snapshot', () => {\n  const { toJSON } = render(<GiftedAvatar />)\n\n  expect(toJSON()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/GiftedChat.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { GiftedChat } from '..'\n\nconst messages = [\n  {\n    _id: 1,\n    text: 'Hello developer',\n    createdAt: new Date(),\n    user: {\n      _id: 2,\n      name: 'John Doe',\n    },\n  },\n]\n\nit('should render <GiftedChat/> and compare with snapshot', () => {\n  const { toJSON } = render(\n    <GiftedChat\n      messages={messages}\n      onSend={() => {}}\n      user={{\n        _id: 1,\n      }}\n    />\n  )\n\n  expect(toJSON()).toMatchSnapshot()\n})\n\nit('should render <GiftedChat/> with light colorScheme and compare with snapshot', () => {\n  const { toJSON } = render(\n    <GiftedChat\n      messages={messages}\n      onSend={() => {}}\n      user={{\n        _id: 1,\n      }}\n      colorScheme='light'\n    />\n  )\n\n  expect(toJSON()).toMatchSnapshot()\n})\n\nit('should render <GiftedChat/> with dark colorScheme and compare with snapshot', () => {\n  const { toJSON } = render(\n    <GiftedChat\n      messages={messages}\n      onSend={() => {}}\n      user={{\n        _id: 1,\n      }}\n      colorScheme='dark'\n    />\n  )\n\n  expect(toJSON()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/InputToolbar.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { InputToolbar } from '..'\n\nit('should render <InputToolbar /> and compare with snapshot', () => {\n  const { toJSON } = render(<InputToolbar />)\n\n  expect(toJSON()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/LoadEarlier.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { LoadEarlierMessages } from '..'\n\nit('should render <LoadEarlierMessages /> and compare with snapshot', () => {\n  const { toJSON } = render(<LoadEarlierMessages isAvailable isLoading={false} onPress={() => {}} />)\n\n  expect(toJSON()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/Message.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Message } from '..'\nimport { DEFAULT_TEST_MESSAGE } from './data'\n\ndescribe('Message component', () => {\n  it('should render <Message /> and compare with snapshot', () => {\n    const { toJSON } = render(\n      <Message\n        key='123'\n        user={{ _id: 1 }}\n        currentMessage={{\n          _id: 1,\n          text: 'test',\n          createdAt: 1554744013721,\n          user: { _id: 1 },\n        }}\n        position='left'\n      />\n    )\n\n    expect(toJSON()).toMatchSnapshot()\n  })\n\n  it('should NOT render <Message />', () => {\n    const { toJSON } = render(\n      <Message key='123' user={{ _id: 1 }} currentMessage={null} position='left' />\n    )\n\n    expect(toJSON()).toMatchSnapshot()\n  })\n\n  it('should render <Message /> with Avatar', () => {\n    const { toJSON } = render(\n      <Message\n        key='123'\n        user={{ _id: 1 }}\n        currentMessage={DEFAULT_TEST_MESSAGE}\n        position='left'\n        isUserAvatarVisible\n      />\n    )\n\n    expect(toJSON()).toMatchSnapshot()\n  })\n\n  it('should render null if user has no Avatar', () => {\n    const { toJSON } = render(\n      <Message\n        key='123'\n        user={{ _id: 1 }}\n        currentMessage={{\n          ...DEFAULT_TEST_MESSAGE,\n          user: {\n            _id: 1,\n            avatar: undefined,\n          },\n        }}\n        position='left'\n        isUserAvatarVisible\n      />\n    )\n\n    expect(toJSON()).toMatchSnapshot()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/MessageImage.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { MessageImage } from '..'\nimport { DEFAULT_TEST_MESSAGE } from './data'\n\ndescribe('MessageImage', () => {\n  it('should not render <MessageImage /> and compare with snapshot', () => {\n    const { toJSON } = render(<MessageImage currentMessage={null} />)\n    expect(toJSON()).toMatchSnapshot()\n  })\n\n  it('should  render <MessageImage /> and compare with snapshot', () => {\n    const { toJSON } = render(\n      <MessageImage\n        currentMessage={{\n          ...DEFAULT_TEST_MESSAGE,\n          image: 'url://to/image.png',\n        }}\n      />\n    )\n    expect(toJSON()).toMatchSnapshot()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/MessageReply.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { MessageReply } from '../components/MessageReply'\nimport { IMessage, ReplyMessage } from '../Models'\n\nconst replyMessage: ReplyMessage = {\n  _id: 'reply-1',\n  text: 'Original message text',\n  user: {\n    _id: 2,\n    name: 'John Doe',\n  },\n}\n\nconst currentMessage: IMessage = {\n  _id: 'msg-1',\n  text: 'Reply text',\n  createdAt: new Date(),\n  user: {\n    _id: 1,\n    name: 'Jane Doe',\n  },\n  replyMessage,\n}\n\nit('should render <MessageReply /> and compare with snapshot', () => {\n  const { toJSON } = render(\n    <MessageReply\n      replyMessage={replyMessage}\n      currentMessage={currentMessage}\n      position='left'\n    />\n  )\n\n  expect(toJSON()).toMatchSnapshot()\n})\n\nit('should render <MessageReply /> on right position and compare with snapshot', () => {\n  const currentMessageFromCurrentUser: IMessage = {\n    ...currentMessage,\n    user: replyMessage.user,\n  }\n\n  const { toJSON } = render(\n    <MessageReply\n      replyMessage={replyMessage}\n      currentMessage={currentMessageFromCurrentUser}\n      position='right'\n    />\n  )\n\n  expect(toJSON()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/MessageText.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { MessageText } from '..'\nimport { DEFAULT_TEST_MESSAGE } from './data'\n\nit('should render <MessageText /> and compare with snapshot', () => {\n  const { toJSON } = render(\n    <MessageText\n      currentMessage={DEFAULT_TEST_MESSAGE}\n    />\n  )\n\n  expect(toJSON()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/MessagesContainer.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { MessagesContainer } from '..'\nimport { DEFAULT_TEST_MESSAGE } from './data'\n\nit('should render <MessagesContainer /> without crashing', () => {\n  // Just verify it renders without throwing\n  expect(() => render(\n    <MessagesContainer\n      messages={[DEFAULT_TEST_MESSAGE]}\n      user={{ _id: 1 }}\n    />\n  )).not.toThrow()\n})\n\nit('should render <MessagesContainer /> with multiple messages', () => {\n  const messages = [\n    { ...DEFAULT_TEST_MESSAGE, _id: 'test1' },\n    { ...DEFAULT_TEST_MESSAGE, _id: 'test2' },\n  ]\n\n  expect(() => render(\n    <MessagesContainer\n      messages={messages}\n      user={{ _id: 1 }}\n    />\n  )).not.toThrow()\n})\n\nit('should render <MessagesContainer /> with empty messages', () => {\n  expect(() => render(\n    <MessagesContainer\n      messages={[]}\n      user={{ _id: 1 }}\n    />\n  )).not.toThrow()\n})\n"
  },
  {
    "path": "src/__tests__/ReplyPreview.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { ReplyPreview } from '../components/ReplyPreview'\nimport { ReplyMessage } from '../Models'\n\nconst replyMessage: ReplyMessage = {\n  _id: 'reply-1',\n  text: 'Original message to reply to',\n  user: {\n    _id: 2,\n    name: 'John Doe',\n  },\n}\n\nit('should render <ReplyPreview /> and compare with snapshot', () => {\n  const { toJSON } = render(\n    <ReplyPreview\n      replyMessage={replyMessage}\n      onClearReply={() => {}}\n    />\n  )\n\n  expect(toJSON()).toMatchSnapshot()\n})\n\nit('should render <ReplyPreview /> with image and compare with snapshot', () => {\n  const replyWithImage: ReplyMessage = {\n    ...replyMessage,\n    image: 'https://example.com/image.jpg',\n  }\n\n  const { toJSON } = render(\n    <ReplyPreview\n      replyMessage={replyWithImage}\n      onClearReply={() => {}}\n    />\n  )\n\n  expect(toJSON()).toMatchSnapshot()\n})\n"
  },
  {
    "path": "src/__tests__/Send.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Send } from '..'\n\ndescribe('Send', () => {\n  it('should not render <Send /> and compare with snapshot', () => {\n    const { toJSON } = render(<Send />)\n    expect(toJSON()).toMatchSnapshot()\n  })\n\n  it('should always render <Send /> and compare with snapshot', () => {\n    const { toJSON } = render(<Send isSendButtonAlwaysVisible />)\n    expect(toJSON()).toMatchSnapshot()\n  })\n\n  it('should render <Send /> where there is input and compare with snapshot', () => {\n    const { toJSON } = render(<Send text='test input' />)\n    expect(toJSON()).toMatchSnapshot()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/SystemMessage.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { SystemMessage } from '..'\nimport { DEFAULT_TEST_MESSAGE } from './data'\n\ndescribe('SystemMessage', () => {\n  it('should not render <SystemMessage /> and compare with snapshot', () => {\n    const { toJSON } = render(<SystemMessage />)\n    expect(toJSON()).toMatchSnapshot()\n  })\n\n  it('should render <SystemMessage /> and compare with snapshot', () => {\n    const { toJSON } = render(\n      <SystemMessage\n        currentMessage={{\n          ...DEFAULT_TEST_MESSAGE,\n          system: true,\n        }}\n      />\n    )\n    expect(toJSON()).toMatchSnapshot()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/Time.test.tsx",
    "content": "import React from 'react'\nimport { render } from '@testing-library/react-native'\n\nimport { Time } from '..'\nimport { DEFAULT_TEST_MESSAGE } from './data'\n\ndescribe('Time', () => {\n  it('should not render <Time /> and compare with snapshot', () => {\n    const { toJSON } = render(<Time />)\n\n    expect(toJSON()).toMatchSnapshot()\n  })\n\n  it('should render <Time /> and compare with snapshot', () => {\n    const { toJSON } = render(\n      <Time\n        currentMessage={{\n          ...DEFAULT_TEST_MESSAGE,\n          createdAt: new Date(2022, 3, 17, 10, 5, 2),\n        }}\n      />\n    )\n\n    expect(toJSON()).toMatchSnapshot()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/__snapshots__/Actions.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <Actions /> and compare with snapshot 1`] = `\n<View\n  style={\n    {\n      \"alignItems\": \"flex-end\",\n    }\n  }\n>\n  <View\n    accessibilityState={\n      {\n        \"busy\": undefined,\n        \"checked\": undefined,\n        \"disabled\": false,\n        \"expanded\": undefined,\n        \"selected\": undefined,\n      }\n    }\n    accessibilityValue={\n      {\n        \"max\": undefined,\n        \"min\": undefined,\n        \"now\": undefined,\n        \"text\": undefined,\n      }\n    }\n    accessible={true}\n    focusable={true}\n    onClick={[Function]}\n    onResponderGrant={[Function]}\n    onResponderMove={[Function]}\n    onResponderRelease={[Function]}\n    onResponderTerminate={[Function]}\n    onResponderTerminationRequest={[Function]}\n    onStartShouldSetResponder={[Function]}\n  />\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/Avatar.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <Avatar /> and compare with snapshot 1`] = `\n<View\n  style={\n    [\n      {\n        \"marginRight\": 8,\n      },\n      undefined,\n      undefined,\n    ]\n  }\n>\n  renderAvatar\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/Bubble.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <Bubble /> and compare with snapshot 1`] = `\n<View>\n  <View\n    style={\n      [\n        {\n          \"backgroundColor\": \"#f0f0f0\",\n          \"borderRadius\": 15,\n          \"justifyContent\": \"flex-end\",\n          \"minHeight\": 20,\n        },\n        null,\n        null,\n        undefined,\n      ]\n    }\n  >\n    <View\n      accessibilityState={\n        {\n          \"busy\": undefined,\n          \"checked\": undefined,\n          \"disabled\": undefined,\n          \"expanded\": undefined,\n          \"selected\": undefined,\n        }\n      }\n      accessibilityValue={\n        {\n          \"max\": undefined,\n          \"min\": undefined,\n          \"now\": undefined,\n          \"text\": undefined,\n        }\n      }\n      accessible={true}\n      collapsable={false}\n      focusable={true}\n      onBlur={[Function]}\n      onClick={[Function]}\n      onFocus={[Function]}\n      onResponderGrant={[Function]}\n      onResponderMove={[Function]}\n      onResponderRelease={[Function]}\n      onResponderTerminate={[Function]}\n      onResponderTerminationRequest={[Function]}\n      onStartShouldSetResponder={[Function]}\n    >\n      <View\n        style={\n          [\n            {\n              \"marginHorizontal\": 10,\n              \"marginVertical\": 5,\n            },\n            undefined,\n          ]\n        }\n      >\n        <Text\n          style={\n            [\n              {\n                \"color\": \"black\",\n              },\n              undefined,\n              undefined,\n            ]\n          }\n        >\n          test\n        </Text>\n      </View>\n      <View\n        style={\n          [\n            {\n              \"alignItems\": \"flex-end\",\n              \"flexDirection\": \"row\",\n              \"justifyContent\": \"space-between\",\n              \"paddingBottom\": 5,\n              \"paddingHorizontal\": 10,\n            },\n            undefined,\n          ]\n        }\n      >\n        <View\n          style={\n            {\n              \"alignItems\": \"center\",\n              \"flexDirection\": \"row\",\n              \"flexGrow\": 1,\n              \"justifyContent\": \"flex-end\",\n            }\n          }\n        >\n          <View>\n            <Text\n              style={\n                [\n                  {\n                    \"color\": \"#aaa\",\n                    \"fontSize\": 10,\n                    \"textAlign\": \"right\",\n                  },\n                  undefined,\n                ]\n              }\n            >\n              12:00 AM\n            </Text>\n          </View>\n        </View>\n      </View>\n    </View>\n  </View>\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/Color.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should compare Color with snapshot 1`] = `\n{\n  \"alizarin\": \"#e74c3c\",\n  \"backgroundTransparent\": \"transparent\",\n  \"black\": \"#000\",\n  \"carrot\": \"#e67e22\",\n  \"defaultBlue\": \"#0084ff\",\n  \"defaultColor\": \"#b2b2b2\",\n  \"emerald\": \"#2ecc71\",\n  \"leftBubbleBackground\": \"#f0f0f0\",\n  \"midnightBlue\": \"#2c3e50\",\n  \"optionTintColor\": \"#007AFF\",\n  \"peterRiver\": \"#3498db\",\n  \"timeTextColor\": \"#aaa\",\n  \"turquoise\": \"#1abc9c\",\n  \"white\": \"#fff\",\n  \"wisteria\": \"#8e44ad\",\n}\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/Composer.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <Composer /> and compare with snapshot 1`] = `\n<View\n  style={\n    {\n      \"flex\": 1,\n    }\n  }\n>\n  <TextInput\n    accessibilityLabel=\"Type a message...\"\n    accessible={true}\n    collapsable={false}\n    enablesReturnKeyAutomatically={true}\n    handlerTag={-1}\n    handlerType=\"NativeViewGestureHandler\"\n    keyboardAppearance=\"default\"\n    multiline={true}\n    onChange={[Function]}\n    onGestureHandlerEvent={[Function]}\n    onGestureHandlerStateChange={[Function]}\n    placeholder=\"Type a message...\"\n    placeholderTextColor=\"#b2b2b2\"\n    style={\n      [\n        [\n          {\n            \"fontSize\": 16,\n            \"lineHeight\": 22,\n            \"paddingBottom\": 10,\n            \"paddingHorizontal\": 8,\n            \"paddingTop\": 8,\n          },\n          undefined,\n        ],\n        {\n          \"outlineStyle\": \"none\",\n        },\n        {\n          \"height\": undefined,\n        },\n        undefined,\n      ]\n    }\n    testID=\"Type a message...\"\n    underlineColorAndroid=\"transparent\"\n    value=\"\"\n  />\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/Constant.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should compare Constant with snapshot 1`] = `\n{\n  \"DATE_FORMAT\": \"D MMMM\",\n  \"TEST_ID\": {\n    \"LOADING_WRAPPER\": \"GC_LOADING_CONTAINER\",\n    \"SEND_TOUCHABLE\": \"GC_SEND_TOUCHABLE\",\n    \"WRAPPER\": \"GC_WRAPPER\",\n  },\n  \"TIME_FORMAT\": \"LT\",\n}\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/Day.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Day should not render <Day /> and compare with snapshot 1`] = `\n<View\n  style={\n    [\n      {\n        \"alignItems\": \"center\",\n        \"justifyContent\": \"center\",\n      },\n      {\n        \"marginBottom\": 10,\n        \"marginTop\": 5,\n      },\n      undefined,\n    ]\n  }\n>\n  <View\n    style={\n      [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 0, 0.75)\",\n          \"borderRadius\": 15,\n          \"paddingBottom\": 6,\n          \"paddingLeft\": 10,\n          \"paddingRight\": 10,\n          \"paddingTop\": 6,\n        },\n        undefined,\n      ]\n    }\n  >\n    <Text\n      style={\n        [\n          {\n            \"color\": \"#fff\",\n            \"fontSize\": 12,\n            \"fontWeight\": \"600\",\n          },\n          undefined,\n        ]\n      }\n    >\n      17 April 2022\n    </Text>\n  </View>\n</View>\n`;\n\nexports[`Day should render <Day /> and compare with snapshot 1`] = `\n<View\n  style={\n    [\n      {\n        \"alignItems\": \"center\",\n        \"justifyContent\": \"center\",\n      },\n      {\n        \"marginBottom\": 10,\n        \"marginTop\": 5,\n      },\n      undefined,\n    ]\n  }\n>\n  <View\n    style={\n      [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 0, 0.75)\",\n          \"borderRadius\": 15,\n          \"paddingBottom\": 6,\n          \"paddingLeft\": 10,\n          \"paddingRight\": 10,\n          \"paddingTop\": 6,\n        },\n        undefined,\n      ]\n    }\n  >\n    <Text\n      style={\n        [\n          {\n            \"color\": \"#fff\",\n            \"fontSize\": 12,\n            \"fontWeight\": \"600\",\n          },\n          undefined,\n        ]\n      }\n    >\n      17 April 2022\n    </Text>\n  </View>\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/DayAnimated.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`DayAnimated should render DayAnimated with default Day component 1`] = `null`;\n\nexports[`DayAnimated should use custom renderDay when provided 1`] = `null`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/GiftedAvatar.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <GiftedAvatar /> and compare with snapshot 1`] = `\n<View\n  accessibilityRole=\"image\"\n  style={\n    [\n      {\n        \"alignItems\": \"center\",\n        \"justifyContent\": \"center\",\n      },\n      {\n        \"borderRadius\": 20,\n        \"height\": 40,\n        \"width\": 40,\n      },\n      {\n        \"backgroundColor\": \"transparent\",\n      },\n      undefined,\n    ]\n  }\n/>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/GiftedChat.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <GiftedChat/> and compare with snapshot 1`] = `\n<View\n  style={\n    {\n      \"flex\": 1,\n    }\n  }\n>\n  <KeyboardProvider\n    navigationBarTranslucent={true}\n    statusBarTranslucent={true}\n  >\n    <View\n      style={\n        {\n          \"flex\": 1,\n        }\n      }\n    >\n      <View\n        onLayout={[Function]}\n        style={\n          [\n            {\n              \"flex\": 1,\n            },\n            {\n              \"overflow\": \"hidden\",\n            },\n          ]\n        }\n        testID=\"GC_WRAPPER\"\n      >\n        <View\n          behavior=\"translate-with-padding\"\n          keyboardVerticalOffset={0}\n          style={\n            {\n              \"flex\": 1,\n            }\n          }\n        >\n          <View\n            style={\n              [\n                {\n                  \"flex\": 1,\n                },\n                {\n                  \"opacity\": 0,\n                },\n              ]\n            }\n          />\n        </View>\n      </View>\n    </View>\n  </KeyboardProvider>\n</View>\n`;\n\nexports[`should render <GiftedChat/> with dark colorScheme and compare with snapshot 1`] = `\n<View\n  style={\n    {\n      \"flex\": 1,\n    }\n  }\n>\n  <KeyboardProvider\n    navigationBarTranslucent={true}\n    statusBarTranslucent={true}\n  >\n    <View\n      style={\n        {\n          \"flex\": 1,\n        }\n      }\n    >\n      <View\n        onLayout={[Function]}\n        style={\n          [\n            {\n              \"flex\": 1,\n            },\n            {\n              \"overflow\": \"hidden\",\n            },\n          ]\n        }\n        testID=\"GC_WRAPPER\"\n      >\n        <View\n          behavior=\"translate-with-padding\"\n          keyboardVerticalOffset={0}\n          style={\n            {\n              \"flex\": 1,\n            }\n          }\n        >\n          <View\n            style={\n              [\n                {\n                  \"flex\": 1,\n                },\n                {\n                  \"opacity\": 0,\n                },\n              ]\n            }\n          />\n        </View>\n      </View>\n    </View>\n  </KeyboardProvider>\n</View>\n`;\n\nexports[`should render <GiftedChat/> with light colorScheme and compare with snapshot 1`] = `\n<View\n  style={\n    {\n      \"flex\": 1,\n    }\n  }\n>\n  <KeyboardProvider\n    navigationBarTranslucent={true}\n    statusBarTranslucent={true}\n  >\n    <View\n      style={\n        {\n          \"flex\": 1,\n        }\n      }\n    >\n      <View\n        onLayout={[Function]}\n        style={\n          [\n            {\n              \"flex\": 1,\n            },\n            {\n              \"overflow\": \"hidden\",\n            },\n          ]\n        }\n        testID=\"GC_WRAPPER\"\n      >\n        <View\n          behavior=\"translate-with-padding\"\n          keyboardVerticalOffset={0}\n          style={\n            {\n              \"flex\": 1,\n            }\n          }\n        >\n          <View\n            style={\n              [\n                {\n                  \"flex\": 1,\n                },\n                {\n                  \"opacity\": 0,\n                },\n              ]\n            }\n          />\n        </View>\n      </View>\n    </View>\n  </KeyboardProvider>\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/InputToolbar.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <InputToolbar /> and compare with snapshot 1`] = `\n<View\n  style={\n    [\n      {\n        \"backgroundColor\": \"#fff\",\n        \"borderTopColor\": \"#b2b2b2\",\n        \"borderTopWidth\": 0.5,\n      },\n      false,\n      undefined,\n    ]\n  }\n>\n  <View\n    style={\n      [\n        {\n          \"alignItems\": \"flex-end\",\n          \"flexDirection\": \"row\",\n        },\n        undefined,\n      ]\n    }\n  >\n    <View\n      style={\n        {\n          \"flex\": 1,\n        }\n      }\n    >\n      <TextInput\n        accessibilityLabel=\"Type a message...\"\n        accessible={true}\n        collapsable={false}\n        enablesReturnKeyAutomatically={true}\n        handlerTag={-1}\n        handlerType=\"NativeViewGestureHandler\"\n        keyboardAppearance=\"default\"\n        multiline={true}\n        onChange={[Function]}\n        onGestureHandlerEvent={[Function]}\n        onGestureHandlerStateChange={[Function]}\n        placeholder=\"Type a message...\"\n        placeholderTextColor=\"#b2b2b2\"\n        style={\n          [\n            [\n              {\n                \"fontSize\": 16,\n                \"lineHeight\": 22,\n                \"paddingBottom\": 10,\n                \"paddingHorizontal\": 8,\n                \"paddingTop\": 8,\n              },\n              undefined,\n            ],\n            {\n              \"outlineStyle\": \"none\",\n            },\n            {\n              \"height\": undefined,\n            },\n            undefined,\n          ]\n        }\n        testID=\"Type a message...\"\n        underlineColorAndroid=\"transparent\"\n        value=\"\"\n      />\n    </View>\n    <View\n      pointerEvents=\"none\"\n      style={\n        [\n          {\n            \"justifyContent\": \"flex-end\",\n          },\n          undefined,\n          {\n            \"opacity\": 0,\n          },\n        ]\n      }\n    >\n      <View\n        accessibilityLabel=\"send\"\n        accessibilityRole=\"button\"\n        accessibilityState={\n          {\n            \"busy\": undefined,\n            \"checked\": undefined,\n            \"disabled\": false,\n            \"expanded\": undefined,\n            \"selected\": undefined,\n          }\n        }\n        accessibilityValue={\n          {\n            \"max\": undefined,\n            \"min\": undefined,\n            \"now\": undefined,\n            \"text\": undefined,\n          }\n        }\n        accessible={true}\n        focusable={true}\n        onClick={[Function]}\n        onResponderGrant={[Function]}\n        onResponderMove={[Function]}\n        onResponderRelease={[Function]}\n        onResponderTerminate={[Function]}\n        onResponderTerminationRequest={[Function]}\n        onStartShouldSetResponder={[Function]}\n        testID=\"GC_SEND_TOUCHABLE\"\n      />\n    </View>\n  </View>\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/LoadEarlier.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <LoadEarlierMessages /> and compare with snapshot 1`] = `\n<View\n  accessibilityRole=\"button\"\n  accessibilityState={\n    {\n      \"busy\": undefined,\n      \"checked\": undefined,\n      \"disabled\": false,\n      \"expanded\": undefined,\n      \"selected\": undefined,\n    }\n  }\n  accessibilityValue={\n    {\n      \"max\": undefined,\n      \"min\": undefined,\n      \"now\": undefined,\n      \"text\": undefined,\n    }\n  }\n  accessible={true}\n  focusable={true}\n  onClick={[Function]}\n  onResponderGrant={[Function]}\n  onResponderMove={[Function]}\n  onResponderRelease={[Function]}\n  onResponderTerminate={[Function]}\n  onResponderTerminationRequest={[Function]}\n  onStartShouldSetResponder={[Function]}\n/>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/Message.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Message component should NOT render <Message /> 1`] = `null`;\n\nexports[`Message component should render <Message /> and compare with snapshot 1`] = `\n<View>\n  <View\n    style={\n      [\n        {\n          \"alignItems\": \"flex-end\",\n          \"flexDirection\": \"row\",\n          \"justifyContent\": \"flex-start\",\n          \"marginLeft\": 8,\n          \"maxWidth\": \"70%\",\n        },\n        {\n          \"marginBottom\": 10,\n        },\n        {\n          \"marginBottom\": 2,\n        },\n        undefined,\n      ]\n    }\n  >\n    <View>\n      <View\n        style={\n          [\n            {\n              \"backgroundColor\": \"#f0f0f0\",\n              \"borderRadius\": 15,\n              \"justifyContent\": \"flex-end\",\n              \"minHeight\": 20,\n            },\n            null,\n            null,\n            undefined,\n          ]\n        }\n      >\n        <View\n          accessibilityState={\n            {\n              \"busy\": undefined,\n              \"checked\": undefined,\n              \"disabled\": undefined,\n              \"expanded\": undefined,\n              \"selected\": undefined,\n            }\n          }\n          accessibilityValue={\n            {\n              \"max\": undefined,\n              \"min\": undefined,\n              \"now\": undefined,\n              \"text\": undefined,\n            }\n          }\n          accessible={true}\n          collapsable={false}\n          focusable={true}\n          onBlur={[Function]}\n          onClick={[Function]}\n          onFocus={[Function]}\n          onResponderGrant={[Function]}\n          onResponderMove={[Function]}\n          onResponderRelease={[Function]}\n          onResponderTerminate={[Function]}\n          onResponderTerminationRequest={[Function]}\n          onStartShouldSetResponder={[Function]}\n        >\n          <View\n            style={\n              [\n                {\n                  \"marginHorizontal\": 10,\n                  \"marginVertical\": 5,\n                },\n                undefined,\n              ]\n            }\n          >\n            <Text\n              style={\n                [\n                  {\n                    \"color\": \"black\",\n                  },\n                  undefined,\n                  undefined,\n                ]\n              }\n            >\n              test\n            </Text>\n          </View>\n          <View\n            style={\n              [\n                {\n                  \"alignItems\": \"flex-end\",\n                  \"flexDirection\": \"row\",\n                  \"justifyContent\": \"space-between\",\n                  \"paddingBottom\": 5,\n                  \"paddingHorizontal\": 10,\n                },\n                undefined,\n              ]\n            }\n          >\n            <View\n              style={\n                {\n                  \"alignItems\": \"center\",\n                  \"flexDirection\": \"row\",\n                  \"flexGrow\": 1,\n                  \"justifyContent\": \"flex-end\",\n                }\n              }\n            >\n              <View>\n                <Text\n                  style={\n                    [\n                      {\n                        \"color\": \"#aaa\",\n                        \"fontSize\": 10,\n                        \"textAlign\": \"right\",\n                      },\n                      undefined,\n                    ]\n                  }\n                >\n                  7:20 PM\n                </Text>\n              </View>\n            </View>\n          </View>\n        </View>\n      </View>\n    </View>\n  </View>\n</View>\n`;\n\nexports[`Message component should render <Message /> with Avatar 1`] = `\n<View>\n  <View\n    style={\n      [\n        {\n          \"alignItems\": \"flex-end\",\n          \"flexDirection\": \"row\",\n          \"justifyContent\": \"flex-start\",\n          \"marginLeft\": 8,\n          \"maxWidth\": \"70%\",\n        },\n        {\n          \"marginBottom\": 10,\n        },\n        {\n          \"marginBottom\": 2,\n        },\n        undefined,\n      ]\n    }\n  >\n    <View\n      style={\n        [\n          {\n            \"marginRight\": 8,\n          },\n          undefined,\n          undefined,\n        ]\n      }\n    >\n      <View\n        accessibilityRole=\"image\"\n        style={\n          [\n            {\n              \"alignItems\": \"center\",\n              \"justifyContent\": \"center\",\n            },\n            {\n              \"borderRadius\": 20,\n              \"height\": 40,\n              \"width\": 40,\n            },\n            {\n              \"backgroundColor\": \"transparent\",\n            },\n            [\n              {\n                \"borderRadius\": 18,\n                \"height\": 36,\n                \"width\": 36,\n              },\n              undefined,\n            ],\n          ]\n        }\n      />\n    </View>\n    <View>\n      <View\n        style={\n          [\n            {\n              \"backgroundColor\": \"#f0f0f0\",\n              \"borderRadius\": 15,\n              \"justifyContent\": \"flex-end\",\n              \"minHeight\": 20,\n            },\n            null,\n            null,\n            undefined,\n          ]\n        }\n      >\n        <View\n          accessibilityState={\n            {\n              \"busy\": undefined,\n              \"checked\": undefined,\n              \"disabled\": undefined,\n              \"expanded\": undefined,\n              \"selected\": undefined,\n            }\n          }\n          accessibilityValue={\n            {\n              \"max\": undefined,\n              \"min\": undefined,\n              \"now\": undefined,\n              \"text\": undefined,\n            }\n          }\n          accessible={true}\n          collapsable={false}\n          focusable={true}\n          onBlur={[Function]}\n          onClick={[Function]}\n          onFocus={[Function]}\n          onResponderGrant={[Function]}\n          onResponderMove={[Function]}\n          onResponderRelease={[Function]}\n          onResponderTerminate={[Function]}\n          onResponderTerminationRequest={[Function]}\n          onStartShouldSetResponder={[Function]}\n        >\n          <View\n            style={\n              [\n                {\n                  \"marginHorizontal\": 10,\n                  \"marginVertical\": 5,\n                },\n                undefined,\n              ]\n            }\n          >\n            <Text\n              style={\n                [\n                  {\n                    \"color\": \"black\",\n                  },\n                  undefined,\n                  undefined,\n                ]\n              }\n            >\n              test\n            </Text>\n          </View>\n          <View\n            style={\n              [\n                {\n                  \"alignItems\": \"flex-end\",\n                  \"flexDirection\": \"row\",\n                  \"justifyContent\": \"space-between\",\n                  \"paddingBottom\": 5,\n                  \"paddingHorizontal\": 10,\n                },\n                undefined,\n              ]\n            }\n          >\n            <View\n              style={\n                {\n                  \"alignItems\": \"center\",\n                  \"flexDirection\": \"row\",\n                  \"flexGrow\": 1,\n                  \"justifyContent\": \"flex-end\",\n                }\n              }\n            >\n              <View>\n                <Text\n                  style={\n                    [\n                      {\n                        \"color\": \"#aaa\",\n                        \"fontSize\": 10,\n                        \"textAlign\": \"right\",\n                      },\n                      undefined,\n                    ]\n                  }\n                >\n                  12:00 AM\n                </Text>\n              </View>\n            </View>\n          </View>\n        </View>\n      </View>\n    </View>\n  </View>\n</View>\n`;\n\nexports[`Message component should render null if user has no Avatar 1`] = `\n<View>\n  <View\n    style={\n      [\n        {\n          \"alignItems\": \"flex-end\",\n          \"flexDirection\": \"row\",\n          \"justifyContent\": \"flex-start\",\n          \"marginLeft\": 8,\n          \"maxWidth\": \"70%\",\n        },\n        {\n          \"marginBottom\": 10,\n        },\n        {\n          \"marginBottom\": 2,\n        },\n        undefined,\n      ]\n    }\n  >\n    <View\n      style={\n        [\n          {\n            \"marginRight\": 8,\n          },\n          undefined,\n          undefined,\n        ]\n      }\n    >\n      <View\n        accessibilityRole=\"image\"\n        style={\n          [\n            {\n              \"alignItems\": \"center\",\n              \"justifyContent\": \"center\",\n            },\n            {\n              \"borderRadius\": 20,\n              \"height\": 40,\n              \"width\": 40,\n            },\n            {\n              \"backgroundColor\": \"transparent\",\n            },\n            [\n              {\n                \"borderRadius\": 18,\n                \"height\": 36,\n                \"width\": 36,\n              },\n              undefined,\n            ],\n          ]\n        }\n      />\n    </View>\n    <View>\n      <View\n        style={\n          [\n            {\n              \"backgroundColor\": \"#f0f0f0\",\n              \"borderRadius\": 15,\n              \"justifyContent\": \"flex-end\",\n              \"minHeight\": 20,\n            },\n            null,\n            null,\n            undefined,\n          ]\n        }\n      >\n        <View\n          accessibilityState={\n            {\n              \"busy\": undefined,\n              \"checked\": undefined,\n              \"disabled\": undefined,\n              \"expanded\": undefined,\n              \"selected\": undefined,\n            }\n          }\n          accessibilityValue={\n            {\n              \"max\": undefined,\n              \"min\": undefined,\n              \"now\": undefined,\n              \"text\": undefined,\n            }\n          }\n          accessible={true}\n          collapsable={false}\n          focusable={true}\n          onBlur={[Function]}\n          onClick={[Function]}\n          onFocus={[Function]}\n          onResponderGrant={[Function]}\n          onResponderMove={[Function]}\n          onResponderRelease={[Function]}\n          onResponderTerminate={[Function]}\n          onResponderTerminationRequest={[Function]}\n          onStartShouldSetResponder={[Function]}\n        >\n          <View\n            style={\n              [\n                {\n                  \"marginHorizontal\": 10,\n                  \"marginVertical\": 5,\n                },\n                undefined,\n              ]\n            }\n          >\n            <Text\n              style={\n                [\n                  {\n                    \"color\": \"black\",\n                  },\n                  undefined,\n                  undefined,\n                ]\n              }\n            >\n              test\n            </Text>\n          </View>\n          <View\n            style={\n              [\n                {\n                  \"alignItems\": \"flex-end\",\n                  \"flexDirection\": \"row\",\n                  \"justifyContent\": \"space-between\",\n                  \"paddingBottom\": 5,\n                  \"paddingHorizontal\": 10,\n                },\n                undefined,\n              ]\n            }\n          >\n            <View\n              style={\n                {\n                  \"alignItems\": \"center\",\n                  \"flexDirection\": \"row\",\n                  \"flexGrow\": 1,\n                  \"justifyContent\": \"flex-end\",\n                }\n              }\n            >\n              <View>\n                <Text\n                  style={\n                    [\n                      {\n                        \"color\": \"#aaa\",\n                        \"fontSize\": 10,\n                        \"textAlign\": \"right\",\n                      },\n                      undefined,\n                    ]\n                  }\n                >\n                  12:00 AM\n                </Text>\n              </View>\n            </View>\n          </View>\n        </View>\n      </View>\n    </View>\n  </View>\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/MessageImage.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`MessageImage should  render <MessageImage /> and compare with snapshot 1`] = `\n<View>\n  <View\n    accessibilityState={\n      {\n        \"busy\": undefined,\n        \"checked\": undefined,\n        \"disabled\": false,\n        \"expanded\": undefined,\n        \"selected\": undefined,\n      }\n    }\n    accessibilityValue={\n      {\n        \"max\": undefined,\n        \"min\": undefined,\n        \"now\": undefined,\n        \"text\": undefined,\n      }\n    }\n    accessible={true}\n    focusable={true}\n    onClick={[Function]}\n    onResponderGrant={[Function]}\n    onResponderMove={[Function]}\n    onResponderRelease={[Function]}\n    onResponderTerminate={[Function]}\n    onResponderTerminationRequest={[Function]}\n    onStartShouldSetResponder={[Function]}\n  />\n  <OverKeyboardView\n    visible={false}\n  >\n    <View\n      style={\n        [\n          {\n            \"bottom\": 0,\n            \"left\": 0,\n            \"position\": \"absolute\",\n            \"right\": 0,\n            \"top\": 0,\n            \"zIndex\": 1000,\n          },\n          {\n            \"opacity\": 0,\n            \"transform\": [\n              {\n                \"scale\": 0.9,\n              },\n            ],\n          },\n          {\n            \"borderRadius\": 40,\n          },\n        ]\n      }\n    >\n      <View\n        style={\n          {\n            \"flex\": 1,\n          }\n        }\n      >\n        <View\n          style={\n            [\n              {\n                \"flex\": 1,\n              },\n              {\n                \"backgroundColor\": \"#000\",\n                \"overflow\": \"hidden\",\n              },\n              {\n                \"borderRadius\": 40,\n              },\n              {\n                \"paddingBottom\": 0,\n                \"paddingTop\": 0,\n              },\n            ]\n          }\n        >\n          <View\n            style={\n              {\n                \"flexDirection\": \"row\",\n                \"justifyContent\": \"flex-end\",\n              }\n            }\n          >\n            <View\n              accessibilityState={\n                {\n                  \"busy\": undefined,\n                  \"checked\": undefined,\n                  \"disabled\": false,\n                  \"expanded\": undefined,\n                  \"selected\": undefined,\n                }\n              }\n              accessibilityValue={\n                {\n                  \"max\": undefined,\n                  \"min\": undefined,\n                  \"now\": undefined,\n                  \"text\": undefined,\n                }\n              }\n              accessible={true}\n              focusable={true}\n              onClick={[Function]}\n              onResponderGrant={[Function]}\n              onResponderMove={[Function]}\n              onResponderRelease={[Function]}\n              onResponderTerminate={[Function]}\n              onResponderTerminationRequest={[Function]}\n              onStartShouldSetResponder={[Function]}\n            />\n          </View>\n          <View\n            style={\n              [\n                {\n                  \"flex\": 1,\n                },\n                {\n                  \"alignItems\": \"center\",\n                  \"justifyContent\": \"center\",\n                },\n              ]\n            }\n          >\n            <View\n              style={\n                [\n                  {\n                    \"alignItems\": \"center\",\n                    \"flex\": 1,\n                    \"justifyContent\": \"center\",\n                    \"overflow\": \"hidden\",\n                  },\n                  undefined,\n                ]\n              }\n            >\n              <View\n                collapsable={false}\n                onLayout={[Function]}\n                style={\n                  {\n                    \"alignItems\": \"center\",\n                    \"flex\": 1,\n                    \"justifyContent\": \"center\",\n                    \"overflow\": \"hidden\",\n                  }\n                }\n              >\n                <View\n                  onLayout={[Function]}\n                  style={\n                    [\n                      {\n                        \"transform\": [\n                          {\n                            \"translateX\": 0,\n                          },\n                          {\n                            \"translateY\": 0,\n                          },\n                          {\n                            \"scale\": 1,\n                          },\n                        ],\n                      },\n                      undefined,\n                    ]\n                  }\n                >\n                  <Image\n                    resizeMode=\"contain\"\n                    source={\n                      {\n                        \"uri\": \"url://to/image.png\",\n                      }\n                    }\n                  />\n                </View>\n              </View>\n            </View>\n          </View>\n        </View>\n      </View>\n    </View>\n  </OverKeyboardView>\n</View>\n`;\n\nexports[`MessageImage should not render <MessageImage /> and compare with snapshot 1`] = `null`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/MessageReply.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <MessageReply /> and compare with snapshot 1`] = `\n<View\n  accessibilityState={\n    {\n      \"busy\": undefined,\n      \"checked\": undefined,\n      \"disabled\": undefined,\n      \"expanded\": undefined,\n      \"selected\": undefined,\n    }\n  }\n  accessibilityValue={\n    {\n      \"max\": undefined,\n      \"min\": undefined,\n      \"now\": undefined,\n      \"text\": undefined,\n    }\n  }\n  accessible={true}\n  collapsable={false}\n  focusable={true}\n  onBlur={[Function]}\n  onClick={[Function]}\n  onFocus={[Function]}\n  onResponderGrant={[Function]}\n  onResponderMove={[Function]}\n  onResponderRelease={[Function]}\n  onResponderTerminate={[Function]}\n  onResponderTerminationRequest={[Function]}\n  onStartShouldSetResponder={[Function]}\n>\n  <View\n    style={\n      [\n        {\n          \"borderRadius\": 8,\n          \"marginBottom\": 4,\n          \"paddingHorizontal\": 10,\n          \"paddingVertical\": 6,\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 0, 0, 0.06)\",\n          \"borderLeftColor\": \"#0084ff\",\n          \"borderLeftWidth\": 3,\n        },\n        undefined,\n        undefined,\n      ]\n    }\n  >\n    <Text\n      numberOfLines={1}\n      style={\n        [\n          {\n            \"fontWeight\": \"600\",\n            \"marginBottom\": 2,\n          },\n          {\n            \"color\": \"#0084ff\",\n          },\n          undefined,\n          undefined,\n        ]\n      }\n    >\n      John Doe\n    </Text>\n    <Text\n      numberOfLines={2}\n      style={\n        [\n          {\n            \"fontSize\": 13,\n          },\n          {\n            \"color\": \"#333\",\n          },\n          undefined,\n          undefined,\n        ]\n      }\n    >\n      Original message text\n    </Text>\n  </View>\n</View>\n`;\n\nexports[`should render <MessageReply /> on right position and compare with snapshot 1`] = `\n<View\n  accessibilityState={\n    {\n      \"busy\": undefined,\n      \"checked\": undefined,\n      \"disabled\": undefined,\n      \"expanded\": undefined,\n      \"selected\": undefined,\n    }\n  }\n  accessibilityValue={\n    {\n      \"max\": undefined,\n      \"min\": undefined,\n      \"now\": undefined,\n      \"text\": undefined,\n    }\n  }\n  accessible={true}\n  collapsable={false}\n  focusable={true}\n  onBlur={[Function]}\n  onClick={[Function]}\n  onFocus={[Function]}\n  onResponderGrant={[Function]}\n  onResponderMove={[Function]}\n  onResponderRelease={[Function]}\n  onResponderTerminate={[Function]}\n  onResponderTerminationRequest={[Function]}\n  onStartShouldSetResponder={[Function]}\n>\n  <View\n    style={\n      [\n        {\n          \"borderRadius\": 8,\n          \"marginBottom\": 4,\n          \"paddingHorizontal\": 10,\n          \"paddingVertical\": 6,\n        },\n        {\n          \"backgroundColor\": \"rgba(255, 255, 255, 0.15)\",\n          \"borderLeftColor\": \"rgba(255, 255, 255, 0.6)\",\n          \"borderLeftWidth\": 3,\n        },\n        undefined,\n        undefined,\n      ]\n    }\n  >\n    <Text\n      numberOfLines={1}\n      style={\n        [\n          {\n            \"fontWeight\": \"600\",\n            \"marginBottom\": 2,\n          },\n          {\n            \"color\": \"rgba(255, 255, 255, 0.9)\",\n          },\n          undefined,\n          undefined,\n        ]\n      }\n    >\n      You\n    </Text>\n    <Text\n      numberOfLines={2}\n      style={\n        [\n          {\n            \"fontSize\": 13,\n          },\n          {\n            \"color\": \"rgba(255, 255, 255, 0.9)\",\n          },\n          undefined,\n          undefined,\n        ]\n      }\n    >\n      Original message text\n    </Text>\n  </View>\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/MessageText.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <MessageText /> and compare with snapshot 1`] = `\n<View\n  style={\n    [\n      {\n        \"marginHorizontal\": 10,\n        \"marginVertical\": 5,\n      },\n      undefined,\n    ]\n  }\n>\n  <Text\n    style={\n      [\n        {\n          \"color\": \"black\",\n        },\n        undefined,\n        undefined,\n      ]\n    }\n  >\n    test\n  </Text>\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/ReplyPreview.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`should render <ReplyPreview /> and compare with snapshot 1`] = `\n<View\n  style={\n    [\n      {\n        \"overflow\": \"hidden\",\n      },\n      {\n        \"height\": undefined,\n        \"opacity\": undefined,\n        \"transform\": [\n          {\n            \"translateY\": undefined,\n          },\n        ],\n      },\n    ]\n  }\n>\n  <View\n    onLayout={[Function]}\n    style={\n      [\n        {\n          \"borderRadius\": 8,\n          \"flexDirection\": \"row\",\n          \"marginBottom\": 8,\n          \"marginHorizontal\": 10,\n          \"overflow\": \"hidden\",\n        },\n        {\n          \"backgroundColor\": \"#e9e9eb\",\n        },\n        undefined,\n      ]\n    }\n  >\n    <View\n      style={\n        {\n          \"backgroundColor\": \"#0084ff\",\n          \"borderTopLeftRadius\": 4,\n          \"height\": \"100%\",\n          \"width\": 4,\n        }\n      }\n    />\n    <View\n      style={\n        {\n          \"flex\": 1,\n          \"paddingHorizontal\": 10,\n          \"paddingVertical\": 8,\n        }\n      }\n    >\n      <View\n        style={\n          {\n            \"alignItems\": \"center\",\n            \"flexDirection\": \"row\",\n          }\n        }\n      >\n        <View\n          style={\n            {\n              \"flex\": 1,\n            }\n          }\n        >\n          <Text\n            numberOfLines={1}\n            style={\n              {\n                \"color\": \"#0084ff\",\n                \"fontSize\": 13,\n                \"fontWeight\": \"600\",\n                \"marginBottom\": 2,\n              }\n            }\n          >\n            Replying to \n            John Doe\n          </Text>\n          <Text\n            numberOfLines={2}\n            style={\n              [\n                {\n                  \"fontSize\": 14,\n                },\n                {\n                  \"color\": \"#333\",\n                },\n                undefined,\n              ]\n            }\n          >\n            Original message to reply to\n          </Text>\n        </View>\n      </View>\n    </View>\n    <View\n      accessibilityState={\n        {\n          \"busy\": undefined,\n          \"checked\": undefined,\n          \"disabled\": undefined,\n          \"expanded\": undefined,\n          \"selected\": undefined,\n        }\n      }\n      accessibilityValue={\n        {\n          \"max\": undefined,\n          \"min\": undefined,\n          \"now\": undefined,\n          \"text\": undefined,\n        }\n      }\n      accessible={true}\n      collapsable={false}\n      focusable={true}\n      hitSlop={8}\n      onBlur={[Function]}\n      onClick={[Function]}\n      onFocus={[Function]}\n      onResponderGrant={[Function]}\n      onResponderMove={[Function]}\n      onResponderRelease={[Function]}\n      onResponderTerminate={[Function]}\n      onResponderTerminationRequest={[Function]}\n      onStartShouldSetResponder={[Function]}\n      style={\n        {\n          \"alignItems\": \"center\",\n          \"borderRadius\": 12,\n          \"height\": 24,\n          \"justifyContent\": \"center\",\n          \"width\": 24,\n        }\n      }\n    >\n      <Text\n        style={\n          [\n            {\n              \"fontSize\": 18,\n              \"fontWeight\": \"600\",\n            },\n            {\n              \"color\": \"#333\",\n            },\n          ]\n        }\n      >\n        ×\n      </Text>\n    </View>\n  </View>\n</View>\n`;\n\nexports[`should render <ReplyPreview /> with image and compare with snapshot 1`] = `\n<View\n  style={\n    [\n      {\n        \"overflow\": \"hidden\",\n      },\n      {\n        \"height\": undefined,\n        \"opacity\": undefined,\n        \"transform\": [\n          {\n            \"translateY\": undefined,\n          },\n        ],\n      },\n    ]\n  }\n>\n  <View\n    onLayout={[Function]}\n    style={\n      [\n        {\n          \"borderRadius\": 8,\n          \"flexDirection\": \"row\",\n          \"marginBottom\": 8,\n          \"marginHorizontal\": 10,\n          \"overflow\": \"hidden\",\n        },\n        {\n          \"backgroundColor\": \"#e9e9eb\",\n        },\n        undefined,\n      ]\n    }\n  >\n    <View\n      style={\n        {\n          \"backgroundColor\": \"#0084ff\",\n          \"borderTopLeftRadius\": 4,\n          \"height\": \"100%\",\n          \"width\": 4,\n        }\n      }\n    />\n    <View\n      style={\n        {\n          \"flex\": 1,\n          \"paddingHorizontal\": 10,\n          \"paddingVertical\": 8,\n        }\n      }\n    >\n      <View\n        style={\n          {\n            \"alignItems\": \"center\",\n            \"flexDirection\": \"row\",\n          }\n        }\n      >\n        <Image\n          source={\n            {\n              \"uri\": \"https://example.com/image.jpg\",\n            }\n          }\n          style={\n            [\n              {\n                \"borderRadius\": 4,\n                \"height\": 40,\n                \"marginRight\": 8,\n                \"width\": 40,\n              },\n              undefined,\n            ]\n          }\n        />\n        <View\n          style={\n            {\n              \"flex\": 1,\n            }\n          }\n        >\n          <Text\n            numberOfLines={1}\n            style={\n              {\n                \"color\": \"#0084ff\",\n                \"fontSize\": 13,\n                \"fontWeight\": \"600\",\n                \"marginBottom\": 2,\n              }\n            }\n          >\n            Replying to \n            John Doe\n          </Text>\n          <Text\n            numberOfLines={2}\n            style={\n              [\n                {\n                  \"fontSize\": 14,\n                },\n                {\n                  \"color\": \"#333\",\n                },\n                undefined,\n              ]\n            }\n          >\n            Original message to reply to\n          </Text>\n        </View>\n      </View>\n    </View>\n    <View\n      accessibilityState={\n        {\n          \"busy\": undefined,\n          \"checked\": undefined,\n          \"disabled\": undefined,\n          \"expanded\": undefined,\n          \"selected\": undefined,\n        }\n      }\n      accessibilityValue={\n        {\n          \"max\": undefined,\n          \"min\": undefined,\n          \"now\": undefined,\n          \"text\": undefined,\n        }\n      }\n      accessible={true}\n      collapsable={false}\n      focusable={true}\n      hitSlop={8}\n      onBlur={[Function]}\n      onClick={[Function]}\n      onFocus={[Function]}\n      onResponderGrant={[Function]}\n      onResponderMove={[Function]}\n      onResponderRelease={[Function]}\n      onResponderTerminate={[Function]}\n      onResponderTerminationRequest={[Function]}\n      onStartShouldSetResponder={[Function]}\n      style={\n        {\n          \"alignItems\": \"center\",\n          \"borderRadius\": 12,\n          \"height\": 24,\n          \"justifyContent\": \"center\",\n          \"width\": 24,\n        }\n      }\n    >\n      <Text\n        style={\n          [\n            {\n              \"fontSize\": 18,\n              \"fontWeight\": \"600\",\n            },\n            {\n              \"color\": \"#333\",\n            },\n          ]\n        }\n      >\n        ×\n      </Text>\n    </View>\n  </View>\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/Send.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Send should always render <Send /> and compare with snapshot 1`] = `\n<View\n  pointerEvents=\"auto\"\n  style={\n    [\n      {\n        \"justifyContent\": \"flex-end\",\n      },\n      undefined,\n      {\n        \"opacity\": 0,\n      },\n    ]\n  }\n>\n  <View\n    accessibilityLabel=\"send\"\n    accessibilityRole=\"button\"\n    accessibilityState={\n      {\n        \"busy\": undefined,\n        \"checked\": undefined,\n        \"disabled\": false,\n        \"expanded\": undefined,\n        \"selected\": undefined,\n      }\n    }\n    accessibilityValue={\n      {\n        \"max\": undefined,\n        \"min\": undefined,\n        \"now\": undefined,\n        \"text\": undefined,\n      }\n    }\n    accessible={true}\n    focusable={true}\n    onClick={[Function]}\n    onResponderGrant={[Function]}\n    onResponderMove={[Function]}\n    onResponderRelease={[Function]}\n    onResponderTerminate={[Function]}\n    onResponderTerminationRequest={[Function]}\n    onStartShouldSetResponder={[Function]}\n    testID=\"GC_SEND_TOUCHABLE\"\n  />\n</View>\n`;\n\nexports[`Send should not render <Send /> and compare with snapshot 1`] = `\n<View\n  pointerEvents=\"none\"\n  style={\n    [\n      {\n        \"justifyContent\": \"flex-end\",\n      },\n      undefined,\n      {\n        \"opacity\": 0,\n      },\n    ]\n  }\n>\n  <View\n    accessibilityLabel=\"send\"\n    accessibilityRole=\"button\"\n    accessibilityState={\n      {\n        \"busy\": undefined,\n        \"checked\": undefined,\n        \"disabled\": false,\n        \"expanded\": undefined,\n        \"selected\": undefined,\n      }\n    }\n    accessibilityValue={\n      {\n        \"max\": undefined,\n        \"min\": undefined,\n        \"now\": undefined,\n        \"text\": undefined,\n      }\n    }\n    accessible={true}\n    focusable={true}\n    onClick={[Function]}\n    onResponderGrant={[Function]}\n    onResponderMove={[Function]}\n    onResponderRelease={[Function]}\n    onResponderTerminate={[Function]}\n    onResponderTerminationRequest={[Function]}\n    onStartShouldSetResponder={[Function]}\n    testID=\"GC_SEND_TOUCHABLE\"\n  />\n</View>\n`;\n\nexports[`Send should render <Send /> where there is input and compare with snapshot 1`] = `\n<View\n  pointerEvents=\"auto\"\n  style={\n    [\n      {\n        \"justifyContent\": \"flex-end\",\n      },\n      undefined,\n      {\n        \"opacity\": 0,\n      },\n    ]\n  }\n>\n  <View\n    accessibilityLabel=\"send\"\n    accessibilityRole=\"button\"\n    accessibilityState={\n      {\n        \"busy\": undefined,\n        \"checked\": undefined,\n        \"disabled\": false,\n        \"expanded\": undefined,\n        \"selected\": undefined,\n      }\n    }\n    accessibilityValue={\n      {\n        \"max\": undefined,\n        \"min\": undefined,\n        \"now\": undefined,\n        \"text\": undefined,\n      }\n    }\n    accessible={true}\n    focusable={true}\n    onClick={[Function]}\n    onResponderGrant={[Function]}\n    onResponderMove={[Function]}\n    onResponderRelease={[Function]}\n    onResponderTerminate={[Function]}\n    onResponderTerminationRequest={[Function]}\n    onStartShouldSetResponder={[Function]}\n    testID=\"GC_SEND_TOUCHABLE\"\n  />\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/SystemMessage.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`SystemMessage should not render <SystemMessage /> and compare with snapshot 1`] = `null`;\n\nexports[`SystemMessage should render <SystemMessage /> and compare with snapshot 1`] = `\n<View\n  style={\n    [\n      {\n        \"flex\": 1,\n      },\n      {\n        \"alignItems\": \"center\",\n        \"marginHorizontal\": 10,\n        \"marginVertical\": 5,\n      },\n    ]\n  }\n>\n  <View\n    style={\n      [\n        {\n          \"backgroundColor\": \"rgba(0,0,0,0.05)\",\n          \"borderColor\": \"rgba(0,0,0,0.1)\",\n          \"borderRadius\": 20,\n          \"borderWidth\": 1,\n          \"maxWidth\": \"70%\",\n          \"paddingHorizontal\": 10,\n          \"paddingVertical\": 10,\n        },\n        undefined,\n      ]\n    }\n  >\n    <View\n      style={\n        [\n          {\n            \"marginHorizontal\": 10,\n            \"marginVertical\": 5,\n          },\n          [\n            {\n              \"marginHorizontal\": 0,\n              \"marginVertical\": 0,\n            },\n            undefined,\n          ],\n        ]\n      }\n    >\n      <Text\n        style={\n          [\n            {\n              \"color\": \"black\",\n            },\n            undefined,\n            [\n              {\n                \"backgroundColor\": \"transparent\",\n                \"fontSize\": 12,\n                \"fontWeight\": \"300\",\n                \"textAlign\": \"center\",\n              },\n              undefined,\n            ],\n          ]\n        }\n      >\n        test\n      </Text>\n    </View>\n  </View>\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/__snapshots__/Time.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Time should not render <Time /> and compare with snapshot 1`] = `null`;\n\nexports[`Time should render <Time /> and compare with snapshot 1`] = `\n<View>\n  <Text\n    style={\n      [\n        {\n          \"color\": \"#aaa\",\n          \"fontSize\": 10,\n          \"textAlign\": \"right\",\n        },\n        undefined,\n      ]\n    }\n  >\n    10:05 AM\n  </Text>\n</View>\n`;\n"
  },
  {
    "path": "src/__tests__/data.ts",
    "content": "import { IMessage } from '../Models'\n\nexport const DEFAULT_TEST_MESSAGE: IMessage = {\n  _id: 'test',\n  text: 'test',\n  user: { _id: 'test' },\n  createdAt: new Date(2022, 3, 17).getTime(),\n}\n"
  },
  {
    "path": "src/__tests__/utils.test.ts",
    "content": "import { isSameDay, isSameUser } from '../utils'\n\nit('should test if same day', () => {\n  const now = new Date()\n  expect(\n    isSameDay(\n      {\n        _id: 1,\n        text: 'test',\n        createdAt: now,\n        user: { _id: 1 },\n      },\n      {\n        _id: 2,\n        text: 'test2',\n        createdAt: now,\n        user: { _id: 2 },\n      }\n    )\n  ).toBe(true)\n})\n\nit('should test if same user', () => {\n  const message = {\n    _id: 1,\n    text: 'test',\n    createdAt: new Date(),\n    user: { _id: 1 },\n  }\n  expect(isSameUser(message, message)).toBe(true)\n})\n"
  },
  {
    "path": "src/components/MessageReply.tsx",
    "content": "import React, { useMemo } from 'react'\nimport {\n  Image,\n  ImageStyle,\n  Pressable,\n  StyleProp,\n  StyleSheet,\n  Text,\n  TextStyle,\n  View,\n  ViewStyle,\n} from 'react-native'\n\nimport { IMessage, ReplyMessage } from '../Models'\nimport { isSameUser } from '../utils'\n\nexport interface MessageReplyProps<TMessage extends IMessage = IMessage> {\n  /** The reply message to display */\n  replyMessage: ReplyMessage\n  /** The current message containing the reply */\n  currentMessage: TMessage\n  /** Position of the bubble (left or right) */\n  position: 'left' | 'right'\n  /** Container style for the reply */\n  containerStyle?: StyleProp<ViewStyle>\n  /** Container style for left position */\n  containerStyleLeft?: StyleProp<ViewStyle>\n  /** Container style for right position */\n  containerStyleRight?: StyleProp<ViewStyle>\n  /** Text style for the reply */\n  textStyle?: StyleProp<TextStyle>\n  /** Text style for left position */\n  textStyleLeft?: StyleProp<TextStyle>\n  /** Text style for right position */\n  textStyleRight?: StyleProp<TextStyle>\n  /** Image style for the reply */\n  imageStyle?: StyleProp<ImageStyle>\n  /** Callback when reply is pressed */\n  onPress?: (replyMessage: ReplyMessage) => void\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    borderRadius: 8,\n    marginBottom: 4,\n    paddingHorizontal: 10,\n    paddingVertical: 6,\n  },\n  containerLeft: {\n    backgroundColor: 'rgba(0, 0, 0, 0.06)',\n    borderLeftColor: '#0084ff',\n    borderLeftWidth: 3,\n  },\n  containerRight: {\n    backgroundColor: 'rgba(255, 255, 255, 0.15)',\n    borderLeftColor: 'rgba(255, 255, 255, 0.6)',\n    borderLeftWidth: 3,\n  },\n  image: {\n    borderRadius: 4,\n    height: 40,\n    marginTop: 4,\n    width: 40,\n  },\n  text: {\n    fontSize: 13,\n  },\n  textLeft: {\n    color: '#333',\n  },\n  textRight: {\n    color: 'rgba(255, 255, 255, 0.9)',\n  },\n  username: {\n    fontWeight: '600',\n    marginBottom: 2,\n  },\n  usernameLeft: {\n    color: '#0084ff',\n  },\n  usernameRight: {\n    color: 'rgba(255, 255, 255, 0.9)',\n  },\n})\n\nexport function MessageReply<TMessage extends IMessage = IMessage> ({\n  replyMessage,\n  currentMessage,\n  position,\n  containerStyle,\n  containerStyleLeft,\n  containerStyleRight,\n  textStyle,\n  textStyleLeft,\n  textStyleRight,\n  imageStyle,\n  onPress,\n}: MessageReplyProps<TMessage>) {\n  const isCurrentUser = useMemo(\n    () => isSameUser(currentMessage, { user: replyMessage.user } as TMessage),\n    [currentMessage, replyMessage.user]\n  )\n\n  const displayName = useMemo(() => {\n    if (isCurrentUser)\n      return 'You'\n\n    return replyMessage.user?.name || 'Unknown'\n  }, [isCurrentUser, replyMessage.user?.name])\n\n  const handlePress = () => {\n    onPress?.(replyMessage)\n  }\n\n  const containerStyles = [\n    styles.container,\n    position === 'left' ? styles.containerLeft : styles.containerRight,\n    containerStyle,\n    position === 'left' ? containerStyleLeft : containerStyleRight,\n  ]\n\n  const usernameStyles = [\n    styles.username,\n    position === 'left' ? styles.usernameLeft : styles.usernameRight,\n    textStyle,\n    position === 'left' ? textStyleLeft : textStyleRight,\n  ]\n\n  const textStyles = [\n    styles.text,\n    position === 'left' ? styles.textLeft : styles.textRight,\n    textStyle,\n    position === 'left' ? textStyleLeft : textStyleRight,\n  ]\n\n  return (\n    <Pressable onPress={handlePress}>\n      <View style={containerStyles}>\n        <Text style={usernameStyles} numberOfLines={1}>\n          {displayName}\n        </Text>\n        {replyMessage.text && (\n          <Text style={textStyles} numberOfLines={2}>\n            {replyMessage.text}\n          </Text>\n        )}\n        {replyMessage.image && (\n          <Image\n            source={{ uri: replyMessage.image }}\n            style={[styles.image, imageStyle]}\n          />\n        )}\n      </View>\n    </Pressable>\n  )\n}\n"
  },
  {
    "path": "src/components/ReplyPreview.tsx",
    "content": "import React, { useEffect } from 'react'\nimport {\n  Image,\n  ImageStyle,\n  Pressable,\n  StyleProp,\n  StyleSheet,\n  Text,\n  TextStyle,\n  View,\n  ViewStyle,\n} from 'react-native'\nimport Animated, {\n  useAnimatedStyle,\n  useSharedValue,\n  withTiming,\n  Easing,\n  interpolate,\n  runOnJS,\n} from 'react-native-reanimated'\n\nimport { useColorScheme } from '../hooks/useColorScheme'\nimport { ReplyMessage } from '../Models'\n\nconst ANIMATION_DURATION = 200\nconst ANIMATION_EASING = Easing.bezier(0.25, 0.1, 0.25, 1)\nconst DEFAULT_HEIGHT = 68\n\nexport interface ReplyPreviewProps {\n  /** The reply message to preview */\n  replyMessage: ReplyMessage\n  /** Callback to clear the reply */\n  onClearReply?: () => void\n  /** Container style */\n  containerStyle?: StyleProp<ViewStyle>\n  /** Text style */\n  textStyle?: StyleProp<TextStyle>\n  /** Image style */\n  imageStyle?: StyleProp<ImageStyle>\n}\n\nconst styles = StyleSheet.create({\n  borderIndicator: {\n    backgroundColor: '#0084ff',\n    borderTopLeftRadius: 4,\n    height: '100%',\n    width: 4,\n  },\n  clearButton: {\n    alignItems: 'center',\n    borderRadius: 12,\n    height: 24,\n    justifyContent: 'center',\n    width: 24,\n  },\n  clearButtonText: {\n    fontSize: 18,\n    fontWeight: '600',\n  },\n  container: {\n    borderRadius: 8,\n    flexDirection: 'row',\n    marginBottom: 8,\n    marginHorizontal: 10,\n    overflow: 'hidden',\n  },\n  containerDark: {\n    backgroundColor: '#2c2c2e',\n  },\n  containerLight: {\n    backgroundColor: '#e9e9eb',\n  },\n  content: {\n    flex: 1,\n    paddingHorizontal: 10,\n    paddingVertical: 8,\n  },\n  image: {\n    borderRadius: 4,\n    height: 40,\n    marginRight: 8,\n    width: 40,\n  },\n  row: {\n    alignItems: 'center',\n    flexDirection: 'row',\n  },\n  text: {\n    fontSize: 14,\n  },\n  textDark: {\n    color: '#fff',\n  },\n  textLight: {\n    color: '#333',\n  },\n  username: {\n    color: '#0084ff',\n    fontSize: 13,\n    fontWeight: '600',\n    marginBottom: 2,\n  },\n  wrapper: {\n    overflow: 'hidden',\n  },\n})\n\nexport function ReplyPreview ({\n  replyMessage,\n  onClearReply,\n  containerStyle,\n  textStyle,\n  imageStyle,\n}: ReplyPreviewProps) {\n  const colorScheme = useColorScheme()\n  const isDark = colorScheme === 'dark'\n\n  const animationProgress = useSharedValue(0)\n  const contentHeight = useSharedValue(DEFAULT_HEIGHT)\n\n  // Animate in on mount\n  useEffect(() => {\n    animationProgress.value = withTiming(1, {\n      duration: ANIMATION_DURATION,\n      easing: ANIMATION_EASING,\n    })\n  }, [animationProgress])\n\n  const handleClear = () => {\n    'worklet'\n    animationProgress.value = withTiming(0, {\n      duration: ANIMATION_DURATION,\n      easing: ANIMATION_EASING,\n    }, finished => {\n      if (finished && onClearReply)\n        runOnJS(onClearReply)()\n    })\n  }\n\n  const wrapperAnimatedStyle = useAnimatedStyle(() => {\n    const height = interpolate(\n      animationProgress.value,\n      [0, 1],\n      [0, contentHeight.value]\n    )\n\n    const opacity = interpolate(\n      animationProgress.value,\n      [0, 0.5, 1],\n      [0, 0.5, 1]\n    )\n\n    const translateY = interpolate(\n      animationProgress.value,\n      [0, 1],\n      [10, 0]\n    )\n\n    return {\n      height,\n      opacity,\n      transform: [{ translateY }],\n    }\n  })\n\n  const displayName = replyMessage.user?.name || 'Unknown'\n\n  return (\n    <Animated.View style={[styles.wrapper, wrapperAnimatedStyle]}>\n      <View\n        style={[\n          styles.container,\n          isDark ? styles.containerDark : styles.containerLight,\n          containerStyle,\n        ]}\n        onLayout={e => {\n          const newHeight = e.nativeEvent.layout.height + 8\n          // Animate height change smoothly when content changes\n          contentHeight.value = withTiming(newHeight, {\n            duration: ANIMATION_DURATION,\n            easing: ANIMATION_EASING,\n          })\n        }}\n      >\n        <View style={styles.borderIndicator} />\n        <View style={styles.content}>\n          <View style={styles.row}>\n            {replyMessage.image && (\n              <Image\n                source={{ uri: replyMessage.image }}\n                style={[styles.image, imageStyle]}\n              />\n            )}\n            <View style={{ flex: 1 }}>\n              <Text style={styles.username} numberOfLines={1}>\n                Replying to {displayName}\n              </Text>\n              {replyMessage.text && (\n                <Text\n                  style={[\n                    styles.text,\n                    isDark ? styles.textDark : styles.textLight,\n                    textStyle,\n                  ]}\n                  numberOfLines={2}\n                >\n                  {replyMessage.text}\n                </Text>\n              )}\n            </View>\n          </View>\n        </View>\n        <Pressable\n          style={styles.clearButton}\n          onPress={handleClear}\n          hitSlop={8}\n        >\n          <Text\n            style={[\n              styles.clearButtonText,\n              isDark ? styles.textDark : styles.textLight,\n            ]}\n          >\n            ×\n          </Text>\n        </Pressable>\n      </View>\n    </Animated.View>\n  )\n}\n"
  },
  {
    "path": "src/components/TouchableOpacity.tsx",
    "content": "import React, { useCallback } from 'react'\nimport { BaseButton } from 'react-native-gesture-handler'\nimport Animated, {\n  useAnimatedStyle,\n  useSharedValue,\n  withTiming,\n} from 'react-native-reanimated'\n\nexport type TouchableOpacityProps = Omit<React.ComponentProps<typeof BaseButton>, 'onPress'> & {\n  activeOpacity?: number\n  onPress?: () => void\n} & React.ComponentProps<typeof Animated.View>\n\nexport const TouchableOpacity: React.FC<TouchableOpacityProps> = ({\n  children,\n  style,\n  activeOpacity = 0.2,\n  onPress,\n  ...rest\n}) => {\n  const opacity = useSharedValue(1)\n  const isAnimationInFinished = useSharedValue(false)\n\n  const handlePressIn = useCallback(() => {\n    opacity.value = withTiming(activeOpacity, { duration: 150 }, () => {\n      isAnimationInFinished.value = true\n    })\n  }, [activeOpacity, opacity, isAnimationInFinished])\n\n  const handlePressOut = useCallback(() => {\n    setTimeout(() => {\n      'worklet'\n\n      opacity.value = withTiming(1, { duration: 150 })\n      isAnimationInFinished.value = false\n    }, isAnimationInFinished.value ? 0 : 150)\n  }, [opacity, isAnimationInFinished])\n\n  const handleActiveStateChange = useCallback((isActive: boolean) => {\n    if (isActive)\n      handlePressIn()\n    else\n      handlePressOut()\n  }, [handlePressIn, handlePressOut])\n\n  const animatedStyle = useAnimatedStyle(() => ({\n    opacity: opacity.value,\n  }))\n\n  const handlePress = useCallback(() => {\n    onPress?.()\n  }, [onPress])\n\n  return (\n    <BaseButton\n      {...rest}\n      onPress={handlePress}\n      onActiveStateChange={handleActiveStateChange}\n    >\n      <Animated.View\n        style={[style, animatedStyle]}\n      >\n        {children}\n      </Animated.View>\n    </BaseButton>\n  )\n}\n"
  },
  {
    "path": "src/hooks/useColorScheme.ts",
    "content": "import { useColorScheme as useRNColorScheme } from 'react-native'\nimport { useChatContext } from '../GiftedChatContext'\n\n/**\n * Custom hook that returns the color scheme from GiftedChat context if provided,\n * otherwise falls back to the system color scheme from React Native.\n * \n * @returns The current color scheme ('light', 'dark', null, or undefined)\n */\nexport function useColorScheme() {\n  const { getColorScheme } = useChatContext()\n  const contextColorScheme = getColorScheme()\n  const systemColorScheme = useRNColorScheme()\n  \n  return contextColorScheme !== undefined && contextColorScheme !== null\n    ? contextColorScheme\n    : systemColorScheme\n}\n"
  },
  {
    "path": "src/hooks/useUpdateLayoutEffect.ts",
    "content": "import { DependencyList, useLayoutEffect, useRef } from 'react'\n\n/**\n * A custom useEffect hook that only triggers on updates, not on initial mount\n * Idea stolen from: https://stackoverflow.com/a/55075818/1526448\n * @param {()=>void} effect the function to call\n * @param {DependencyList} dependencies the state(s) that fires the update\n */\nexport function useUpdateLayoutEffect (\n  effect: () => void,\n  dependencies: DependencyList = []\n) {\n  const isInitialMount = useRef(true)\n\n  useLayoutEffect(() => {\n    if (isInitialMount.current)\n      isInitialMount.current = false\n    else\n      effect()\n  }, dependencies)\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import * as utils from './utils'\n\nexport * from './GiftedChat'\nexport * from './Constant'\nexport { utils }\nexport * from './GiftedChatContext'\nexport * from './types'\nexport * from './linkParser'\nexport * from './Reply'\nexport { Actions } from './Actions'\nexport { Avatar } from './Avatar'\nexport { Bubble } from './Bubble'\nexport { SystemMessage } from './SystemMessage'\nexport { MessageImage } from './MessageImage'\nexport { MessageText } from './MessageText'\nexport { Composer } from './Composer'\nexport { Day } from './Day'\nexport { InputToolbar } from './InputToolbar'\nexport { LoadEarlierMessages } from './LoadEarlierMessages'\nexport { Message } from './Message'\nexport { MessagesContainer } from './MessagesContainer'\nexport { Send } from './Send'\nexport { Time } from './Time'\nexport { GiftedAvatar } from './GiftedAvatar'\nexport { MessageAudio } from './MessageAudio'\nexport { MessageVideo } from './MessageVideo'\nexport { MessageReply } from './components/MessageReply'\nexport { ReplyPreview } from './components/ReplyPreview'\nexport { useColorScheme } from './hooks/useColorScheme'\n"
  },
  {
    "path": "src/linkParser.tsx",
    "content": "import React from 'react'\nimport { Text, TextStyle, StyleProp, Linking } from 'react-native'\n\nexport type LinkType = 'url' | 'email' | 'phone' | 'mention' | 'hashtag'\n\nexport interface ParsedLink {\n  type: LinkType\n  text: string\n  url: string\n  index: number\n  length: number\n}\n\nexport interface LinkMatcher {\n  type: LinkType\n  pattern: RegExp\n  getLinkUrl?: (text: string) => string\n  getLinkText?: (text: string) => string\n  baseUrl?: string\n  style?: StyleProp<TextStyle>\n  renderLink?: (text: string, url: string, index: number, type: LinkType) => React.ReactNode\n  onPress?: (url: string, type: LinkType) => void\n}\n\ninterface LinkParserProps {\n  text: string\n  matchers?: LinkMatcher[]\n  email?: boolean\n  phone?: boolean\n  url?: boolean\n  hashtag?: boolean\n  mention?: boolean\n  hashtagUrl?: string\n  mentionUrl?: string\n  linkStyle?: StyleProp<TextStyle>\n  onPress?: (url: string, type: LinkType) => void\n  stripPrefix?: boolean\n  textStyle?: StyleProp<TextStyle>\n  TextComponent?: React.ComponentType<any>\n}\n\nconst DEFAULT_MATCHERS: LinkMatcher[] = [\n  {\n    type: 'url',\n    pattern: /(?:https?:\\/\\/(?:www\\.)?|www\\.)[^\\s]+|(?<![A-Za-z0-9_.@])(?![A-Za-z0-9._%+-]*@)[a-zA-Z0-9][a-zA-Z0-9-]*\\.(?!@)[a-zA-Z]{2,}(?![A-Za-z0-9._%+-]*@)(?:\\/[^\\s]*)?/gi,\n    getLinkUrl: (text: string) => {\n      if (!/^https?:\\/\\//i.test(text))\n        return `http://${text}`\n\n      return text\n    },\n  },\n  {\n    type: 'email',\n    pattern: /(?<![A-Za-z0-9])([a-zA-Z0-9][a-zA-Z0-9._%+-]*@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})/gi,\n    getLinkUrl: (text: string) => `mailto:${text}`,\n  },\n  {\n    type: 'phone',\n    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,\n    getLinkUrl: (text: string) => {\n      const cleaned = text.replace(/[\\s.()\\-]/g, '')\n      return `tel:${cleaned}`\n    },\n  },\n  {\n    type: 'hashtag',\n    pattern: /#[\\w]+/g,\n    getLinkUrl: (text: string) => text,\n    baseUrl: undefined,\n  },\n  {\n    type: 'mention',\n    pattern: /(?<![a-zA-Z0-9._%+-])@[\\w-]+/g,\n    getLinkUrl: (text: string) => text,\n    baseUrl: undefined,\n  },\n]\n\nfunction parseLinks(text: string, matchers: LinkMatcher[]): ParsedLink[] {\n  const links: ParsedLink[] = []\n\n  matchers.forEach(matcher => {\n    const matches = text.matchAll(matcher.pattern)\n    for (const match of matches)\n      if (match.index !== undefined) {\n        const matchText = match[0]\n        const url = matcher.getLinkUrl\n          ? matcher.getLinkUrl(matchText)\n          : matchText\n        const linkText = matcher.getLinkText\n          ? matcher.getLinkText(matchText)\n          : matchText\n\n        links.push({\n          type: matcher.type,\n          text: linkText,\n          url,\n          index: match.index,\n          length: matchText.length,\n        })\n      }\n\n  })\n\n  // Sort by index to maintain order\n  return links.sort((a, b) => a.index - b.index)\n}\n\nfunction removeOverlaps(links: ParsedLink[]): ParsedLink[] {\n  const filtered: ParsedLink[] = []\n\n  for (const link of links) {\n    const hasOverlap = filtered.some(existing => {\n      const existingEnd = existing.index + existing.length\n      const linkEnd = link.index + link.length\n\n      return (\n        (link.index >= existing.index && link.index < existingEnd) ||\n        (linkEnd > existing.index && linkEnd <= existingEnd) ||\n        (link.index <= existing.index && linkEnd >= existingEnd)\n      )\n    })\n\n    if (!hasOverlap)\n      filtered.push(link)\n\n  }\n\n  return filtered\n}\n\nexport function LinkParser({\n  text,\n  matchers: customMatchers,\n  email = true,\n  phone = true,\n  url = true,\n  hashtag = false,\n  mention = false,\n  hashtagUrl,\n  mentionUrl,\n  linkStyle,\n  onPress,\n  stripPrefix = true,\n  textStyle,\n  TextComponent = Text,\n}: LinkParserProps): React.ReactElement {\n  const activeMatchers: LinkMatcher[] = []\n\n  // Add custom matchers first (they take precedence)\n  if (customMatchers)\n    activeMatchers.push(...customMatchers)\n\n\n  // Add default matchers based on flags\n  if (url && !customMatchers?.some(m => m.type === 'url'))\n    activeMatchers.push(DEFAULT_MATCHERS.find(m => m.type === 'url')!)\n\n  if (email && !customMatchers?.some(m => m.type === 'email'))\n    activeMatchers.push(DEFAULT_MATCHERS.find(m => m.type === 'email')!)\n\n  if (phone && !customMatchers?.some(m => m.type === 'phone'))\n    activeMatchers.push(DEFAULT_MATCHERS.find(m => m.type === 'phone')!)\n\n  if (hashtag && !customMatchers?.some(m => m.type === 'hashtag')) {\n    const hashtagMatcher = { ...DEFAULT_MATCHERS.find(m => m.type === 'hashtag')! }\n    if (hashtagUrl) {\n      hashtagMatcher.baseUrl = hashtagUrl\n      const baseUrl = hashtagUrl.endsWith('/') ? hashtagUrl : `${hashtagUrl}/`\n      hashtagMatcher.getLinkUrl = (text: string) => `${baseUrl}${text.substring(1)}`\n    }\n    activeMatchers.push(hashtagMatcher)\n  }\n\n  if (mention && !customMatchers?.some(m => m.type === 'mention')) {\n    const mentionMatcher = { ...DEFAULT_MATCHERS.find(m => m.type === 'mention')! }\n    if (mentionUrl) {\n      mentionMatcher.baseUrl = mentionUrl\n      const baseUrl = mentionUrl.endsWith('/') ? mentionUrl : `${mentionUrl}/`\n      mentionMatcher.getLinkUrl = (text: string) => `${baseUrl}${text.substring(1)}`\n    }\n    activeMatchers.push(mentionMatcher)\n  }\n\n\n  const links = removeOverlaps(parseLinks(text, activeMatchers))\n\n  if (links.length === 0)\n    return <TextComponent style={textStyle}>{text}</TextComponent>\n\n\n  const elements: React.ReactNode[] = []\n  let lastIndex = 0\n\n  links.forEach((link, index) => {\n    // Add text before link\n    if (link.index > lastIndex)\n      elements.push(\n        <TextComponent key={`text-${index}`} style={textStyle}>\n          {text.substring(lastIndex, link.index)}\n        </TextComponent>\n      )\n\n\n    // Find the matcher for this link\n    const matcher = activeMatchers.find(m => m.type === link.type)\n\n    // Handle link rendering\n    if (matcher?.renderLink) {\n      elements.push(matcher.renderLink(link.text, link.url, index, link.type))\n    } else {\n      const handlePress = () => {\n        if (matcher?.onPress)\n          matcher.onPress(link.url, link.type)\n        else if (onPress)\n          onPress(link.url, link.type)\n        else\n          // Default behavior\n          Linking.openURL(link.url).catch(err => {\n            console.warn('Failed to open URL:', err)\n          })\n\n      }\n\n      let displayText = link.text\n      if (stripPrefix && link.type === 'url')\n        displayText = displayText.replace(/^https?:\\/\\//i, '')\n\n\n      elements.push(\n        <TextComponent\n          key={`link-${index}`}\n          style={[linkStyle, matcher?.style]}\n          onPress={handlePress}\n        >\n          {displayText}\n        </TextComponent>\n      )\n    }\n\n    lastIndex = link.index + link.length\n  })\n\n  // Add remaining text\n  if (lastIndex < text.length)\n    elements.push(\n      <TextComponent key='text-end' style={textStyle}>\n        {text.substring(lastIndex)}\n      </TextComponent>\n    )\n\n\n  return <TextComponent style={textStyle}>{elements}</TextComponent>\n}\n"
  },
  {
    "path": "src/logging.ts",
    "content": "const styleString = (color: string) => `color: ${color}; font-weight: bold`\nconst headerLog = '%c[react-native-gifted-chat]'\n\nexport const warning = (...args: unknown[]) =>\n  console.log(headerLog, styleString('orange'), ...args)\n\nexport const error = (...args: unknown[]) =>\n  console.log(headerLog, styleString('red'), ...args)\n"
  },
  {
    "path": "src/styles.ts",
    "content": "import { StyleProp, StyleSheet, TextStyle, ViewStyle } from 'react-native'\n\nexport default StyleSheet.create({\n  fill: {\n    flex: 1,\n  },\n  centerItems: {\n    justifyContent: 'center',\n    alignItems: 'center',\n  },\n})\n\nexport function getColorSchemeStyle<T>(styles: T, baseName: string, colorScheme?: string | null) {\n  const key = `${baseName}_${colorScheme}` as keyof T\n  return [styles[baseName as keyof T], styles[key]]\n}\n\nexport function getStyleWithPosition<T>(styles: T, baseName: string, position?: 'left' | 'right' | null) {\n  const stylesArray = [styles[baseName as keyof T]]\n  if (position) {\n    const key = `${baseName}_${position}` as keyof T\n    stylesArray.push(styles[key])\n  }\n  return StyleSheet.flatten(stylesArray) as StyleProp<ViewStyle> | StyleProp<TextStyle>\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "export * from './Models'\n\nexport type { ActionsProps } from './Actions'\nexport type { AvatarProps } from './Avatar'\nexport type {\n  BubbleProps,\n  RenderMessageImageProps,\n  RenderMessageVideoProps,\n  RenderMessageAudioProps,\n  RenderMessageTextProps\n} from './Bubble'\nexport type { ComposerProps } from './Composer'\nexport type { DayProps } from './Day'\nexport type { GiftedAvatarProps } from './GiftedAvatar'\nexport type { InputToolbarProps, ReplyPreviewProps } from './InputToolbar'\nexport type { LoadEarlierMessagesProps } from './LoadEarlierMessages'\nexport type { MessageProps } from './Message'\nexport type { MessagesContainerProps } from './MessagesContainer'\nexport type { MessageImageProps } from './MessageImage'\nexport type { MessageTextProps } from './MessageText'\nexport type { MessageReplyProps } from './components/MessageReply'\nexport type { QuickRepliesProps } from './QuickReplies'\nexport type { SendProps } from './Send'\nexport type { SystemMessageProps } from './SystemMessage'\nexport type { TimeProps } from './Time'\n"
  },
  {
    "path": "src/utils.ts",
    "content": "import React, { useCallback, useEffect, useRef } from 'react'\nimport dayjs from 'dayjs'\nimport { IMessage } from './Models'\n\nexport function renderComponentOrElement<TProps extends Record<string, any>>(\n  component: React.ComponentType<TProps> | React.ReactElement | ((props: TProps) => React.ReactNode) | null | undefined,\n  props: TProps\n): React.ReactNode {\n  if (!component)\n    return null\n\n  if (React.isValidElement(component))\n    // If it's already a React element, clone it with props\n    return React.cloneElement(component, props as any)\n\n  if (typeof component === 'function') {\n    // Check if it's a class component (has prototype.isReactComponent)\n    // Class components must use React.createElement\n    const isClassComponent = component.prototype && component.prototype.isReactComponent\n\n    if (isClassComponent)\n      return React.createElement(component as React.ComponentType<TProps>, props as any)\n\n    // For function components and render functions, call directly\n    // Using createElement with inline arrow functions causes unmount/remount\n    // when function reference changes, this matches v2.x behavior\n    return (component as (props: TProps) => React.ReactNode)(props)\n  }\n\n  // Check for React.memo or React.forwardRef wrapped components\n  // These have $$typeof property and should be rendered with createElement\n  if (typeof component === 'object' && component !== null && '$$typeof' in component)\n    return React.createElement(component as React.ComponentType<TProps>, props as any)\n\n  // If it's neither, return it as-is\n  return component\n}\n\nexport function isSameDay (\n  currentMessage: IMessage,\n  diffMessage: IMessage | null | undefined\n) {\n  if (!diffMessage || !diffMessage.createdAt)\n    return false\n\n  const currentCreatedAt = dayjs(currentMessage.createdAt)\n  const diffCreatedAt = dayjs(diffMessage.createdAt)\n\n  if (!currentCreatedAt.isValid() || !diffCreatedAt.isValid())\n    return false\n\n  return currentCreatedAt.isSame(diffCreatedAt, 'day')\n}\n\nexport function isSameUser (\n  currentMessage: IMessage,\n  diffMessage: IMessage | null | undefined\n) {\n  return !!(\n    diffMessage &&\n    diffMessage.user &&\n    currentMessage.user &&\n    diffMessage.user._id === currentMessage.user._id\n  )\n}\n\nfunction processCallbackArguments (args: unknown[]): unknown[] {\n  const [e, ...rest] = args\n  const { nativeEvent } = (e as { nativeEvent?: unknown }) || {}\n  let params: unknown[] = []\n  if (e) {\n    if (nativeEvent)\n      params.push({ nativeEvent })\n    else\n      params.push(e)\n    if (rest)\n      params = params.concat(rest)\n  }\n\n  return params\n}\n\nexport function useCallbackDebounced<T extends (...args: any[]) => any>(callbackFunc: T, deps: React.DependencyList = [], time: number): (...args: Parameters<T>) => void {\n  const timeoutId = useRef<ReturnType<typeof setTimeout>>(undefined)\n\n  const savedFunc = useCallback((...args: Parameters<T>) => {\n    const params = processCallbackArguments(args)\n    if (timeoutId.current)\n      clearTimeout(timeoutId.current)\n    timeoutId.current = setTimeout(() => {\n      callbackFunc(...params as Parameters<T>)\n    }, time)\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [callbackFunc, time, ...deps])\n\n  useEffect(() => {\n    return () => {\n      if (timeoutId.current)\n        clearTimeout(timeoutId.current)\n    }\n  }, [])\n\n  return savedFunc\n}\n\nexport function useCallbackThrottled<T extends (...args: any[]) => any>(callbackFunc: T, deps: React.DependencyList = [], time: number): (...args: Parameters<T>) => void {\n  const lastExecution = useRef<number>(0)\n  const timeoutId = useRef<ReturnType<typeof setTimeout>>(undefined)\n\n  // we use function instead of arrow to access arguments object\n  const savedFunc = useCallback((...args: Parameters<T>) => {\n    const params = processCallbackArguments(args)\n\n    const now = Date.now()\n    const timeSinceLastExecution = now - lastExecution.current\n\n    if (timeSinceLastExecution >= time) {\n      // Execute immediately if enough time has passed\n      lastExecution.current = now\n      callbackFunc(...params as Parameters<T>)\n    } else {\n      // Schedule execution for the remaining time\n      clearTimeout(timeoutId.current)\n      timeoutId.current = setTimeout(() => {\n        lastExecution.current = Date.now()\n        callbackFunc(...params as Parameters<T>)\n      }, time - timeSinceLastExecution)\n    }\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [callbackFunc, time, ...deps])\n\n  useEffect(() => {\n    return () => {\n      clearTimeout(timeoutId.current)\n    }\n  }, [])\n\n  return savedFunc\n}\n"
  },
  {
    "path": "tests/setup.ts",
    "content": "jest.mock('react-native-worklets', () =>\n  require('react-native-worklets/lib/module/mock')\n)\n\njest.mock('react-native-reanimated', () =>\n  require('react-native-reanimated/mock')\n)\n\njest.mock('react-native-safe-area-context', () => {\n  const inset = { top: 0, right: 0, bottom: 0, left: 0 }\n  return {\n    SafeAreaProvider: ({ children }: any) => children,\n    SafeAreaInsetsContext: {\n      Consumer: ({ children }: any) => children(inset),\n    },\n    useSafeAreaInsets: () => inset,\n    useSafeAreaFrame: () => ({ x: 0, y: 0, width: 390, height: 844 }),\n  }\n})\n\njest.mock('react-native-keyboard-controller', () =>\n  require('react-native-keyboard-controller/jest')\n)\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"outDir\": \"./lib\",\n    \"strict\": true,\n    \"jsx\": \"react-native\",\n    \"target\": \"ES2020\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true,\n    \"noImplicitAny\": true,\n    \"experimentalDecorators\": true,\n    \"preserveConstEnums\": true,\n    \"sourceMap\": true,\n    \"allowJs\": true,\n    \"checkJs\": true,\n    \"strictNullChecks\": true,\n    \"skipDefaultLibCheck\": true,\n    \"skipLibCheck\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"importHelpers\": false,\n    \"alwaysStrict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"strictFunctionTypes\": true,\n    \"resolveJsonModule\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"strictPropertyInitialization\": false,\n    \"lib\": [\"ES2020\"],\n    \"typeRoots\": [\"./node_modules/@types\", \"./@types\"]\n  },\n  \"include\": [\"src\", \"./types.d.ts\"],\n  \"exclude\": [\"node_modules\", \"src/__tests__\", \"example/node_modules\", \"lib\"]\n}\n"
  }
]