[
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n\t\"root\": true,\n\t\"env\": {\n\t\t\"es2021\": true,\n\t\t\"node\": true\n\t},\n\t\"parser\": \"@typescript-eslint/parser\",\n\t\"parserOptions\": {\n\t\t\"ecmaVersion\": 12,\n\t\t\"sourceType\": \"module\",\n\t\t\"project\": [\"./tsconfig.json\"],\n\t},\n\t\"plugins\": [\n\t  \"@typescript-eslint\",\n\t  \"import\"\n\t],\n\t\"extends\": [\n\t\t\"airbnb\",\n\t\t\"airbnb-typescript\"\n\t],\n\t\"rules\": {\n\t\t// React\n\t\t\"react/jsx-props-no-spreading\": \"off\",\n\t\t\"react/prop-types\": \"off\",\n\t\t\"react/function-component-definition\": \"off\",\n\n\t\t// Spaces\n\t\t'semi': [\n\t\t\t'error',\n\t\t\t'always'\n\t\t],\n\t\t'no-trailing-spaces': [\n\t\t\t'error', {\n\t\t\t\t'ignoreComments': true\n\t\t\t}\n\t\t],\n\t\t'space-before-function-paren': [\n\t\t\t'error', {\n\t\t\t\t'anonymous': 'always',\n\t\t\t\t'named': 'never',\n\t\t\t\t'asyncArrow': 'always'\n\t\t\t}\n\t\t],\n\t\t'space-in-parens': [\n\t\t\t'error', 'always'\n\t\t],\n\t\t'space-before-blocks': 'error',\n\t\t'no-whitespace-before-property': 'error',\n\t\t'newline-before-return': 'error',\n\t\t'no-multi-spaces': 'error',\n\t\t'arrow-parens': [ 'error', 'always' ],\n\t\t'array-bracket-spacing': [ 'error', 'always' ],\n\t\t'arrow-spacing': 'error',\n\t\t'lines-between-class-members': [ 'error', 'always', { 'exceptAfterSingleLine': true } ],\n\t\t'@typescript-eslint/lines-between-class-members': [ 'error', 'always', { 'exceptAfterSingleLine': true } ],\n\t\t\n\t\t// Basics\n\t\t'camelcase': 'error',\n\t\t'no-var': 'error',\n\t\t'prefer-const': 'error',\n\t\t'eqeqeq': [ 'error', 'always' ],\n\t\t'no-return-assign': 'error',\n\t\t'no-return-await': 'error',\n\t\t'no-throw-literal': 'error',\n\t\t'new-cap': [ 'error', { 'newIsCap': true } ],\n\t\t'no-unneeded-ternary': 'error',\n\t\t'no-template-curly-in-string': 'error',\n\t\t'template-curly-spacing': 'error',\n\t\t'curly': [ 'error', 'all' ],\n\t\t'padded-blocks': [ 'error', 'always' ],\n\t\t'no-underscore-dangle': [\"error\", { \"allowAfterThis\": true }],\n\t\t'import/extensions':'off',\n\t\t'no-plusplus': 'off',\n\t\t'import/prefer-default-export': 'off',\n\t\t'no-mixed-operators': 'off',\n\t\t'no-param-reassign': 'off',\n\t\t'import/no-named-as-default': 'off',\n\t\t'import/no-extraneous-dependencies': 'off',\n\t}\n};\n"
  },
  {
    "path": ".github/workflows/public.yml",
    "content": "name: NPM Package Publish\non:\n  release:\n    types: [created]\n \njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-node@v3\n        with:\n          node-version: '16.x'\n          registry-url: 'https://registry.npmjs.org'\n      - run: npm ci\n      - run: npm run build\n      - run: |\n          npm version ${{ github.event.release.tag_name }} --no-git-tag-version --allow-same-version && \\\n          npm publish --access public\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n  slackNotification:\n    name: Slack Notification\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n    - name: Slack Notification\n      uses: rtCamp/action-slack-notify@v2\n      env:\n        SLACK_CHANNEL: frontend\n        SLACK_COLOR: ${{ job.status }}\n        SLACK_ICON: https://raw.githubusercontent.com/birdwingo/react-native-instagram-stories/main/src/assets/images/logo.png\n        SLACK_MESSAGE: Publish Release ${{ github.event.release.tag_name }} ${{ job.status == 'success' && 'has been successful' || 'has been failed' }}\n        SLACK_TITLE: 'Instagram stories publish release :rocket:'\n        SLACK_USERNAME: NPM\n        SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Github Release\non:\n  pull_request:\n    types: [closed]\n    branch: main\njobs:\n  release:\n    if: github.event.pull_request.merged == true\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          token: ${{ secrets.AUTH_TOKEN }}\n      - uses: actions/setup-node@v2\n        with:\n          node-version: '16.x'\n          registry-url: 'https://registry.npmjs.org'\n      - run: npm ci\n      - name: Set up git user for release\n        run: |\n          git config --global user.email \"actions@github.com\"\n          git config --global user.name \"GitHub Actions\"\n      - run: npm run release\n      - name: Push changes\n        run: git push --follow-tags origin main\n      - run: npm run build\n      - run: npm run test\n      - name: Get version from package-lock.json\n        id: get_version\n        run: echo \"::set-output name=version::$(node -p \"require('./package-lock.json').version\")\"\n      - name: Get changelog\n        id: get_changelog\n        run: |\n          CHANGELOG=$(awk '/^### \\[[0-9]+\\.[0-9]+\\.[0-9]+\\]/{if (version!=\"\") {exit}; version=$2} version!=\"\" {print}' CHANGELOG.md)\n          echo \"::set-output name=changelog::${CHANGELOG}\"\n      - name: Create Release\n        uses: actions/create-release@master\n        env:\n          GITHUB_TOKEN: ${{ secrets.AUTH_TOKEN }}\n        with:\n          tag_name: \"v${{ steps.get_version.outputs.version }}\"\n          release_name: \"v${{ steps.get_version.outputs.version }}\""
  },
  {
    "path": ".gitignore",
    "content": "/node_modules\n/coverage\n/dist"
  },
  {
    "path": ".husky/commit-msg",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx --no -- commitlint --edit ${1}\nnpm run test"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.\n\n### 1.3.18 (2025-07-17)\n\n### 1.3.17 (2025-07-09)\n\n### Bug Fixes\n\n* readme ([ec9493f](https://github.com/birdwingo/react-native-instagram-stories/commit/ec9493fafc27122b6bb5b0c1312f89819a834569))\n\n### 1.3.16 (2025-07-07)\n\n### 1.3.15 (2025-07-04)\n\n* image preload error\n\n### 1.3.14 (2025-07-03)\n\n\n### Bug Fixes\n\n* ts error ([04905cb](https://github.com/birdwingo/react-native-instagram-stories/commit/04905cb051cdee58611c7d2a7d4104d945ed7fef))\n\n### 1.3.13 (2025-07-03)\n\n\n### Bug Fixes\n\n* prefetch image ([5d8435d](https://github.com/birdwingo/react-native-instagram-stories/commit/5d8435d8b818fdb36a6dc51da4eba5e3257b9798))\n\n### 1.3.12 (2025-02-01)\n\n### 1.3.11 (2025-02-01)\n\n### 1.3.10 (2024-10-21)\n\n### 1.3.9 (2024-10-17)\n\n### 1.3.8 (2024-10-12)\n\n### 1.3.7 (2024-10-08)\n\n### 1.3.6 (2024-10-08)\n\n### 1.3.5 (2024-08-10)\n\n### 1.3.4 (2024-07-30)\n\n### 1.3.3 (2024-07-22)\n\n### 1.3.2 (2024-07-12)\n\n\n### Bug Fixes\n\n* avatarSource property ([4ceed53](https://github.com/birdwingo/react-native-instagram-stories/commit/4ceed53ecc2df9e33a7a8584acd3f1159d553fca))\n\n### 1.3.1 (2024-06-24)\n\n\n### Bug Fixes\n\n* import TextProps ([593503d](https://github.com/birdwingo/react-native-instagram-stories/commit/593503dcfc9acafbc131eb4cbe445bef05ee0529))\n\n## 1.3.0 (2024-06-24)\n\n\n### Features\n\n* continue on last viewed story when scrolling between users ([697a157](https://github.com/birdwingo/react-native-instagram-stories/commit/697a157d9f3faa8b473a088c6f13da0335f75154))\n\n### 1.2.12 (2024-06-24)\n\n### 1.2.11 (2024-06-14)\n\n\n### Bug Fixes\n\n* opening url on footer press causes images to get stuck ([6aabd43](https://github.com/birdwingo/react-native-instagram-stories/commit/6aabd438afc645fe0324b374f9d4378d291e802f))\n\n### 1.2.10 (2024-05-27)\n\n\n### Bug Fixes\n\n* show elements when swiping ([0d6da2f](https://github.com/birdwingo/react-native-instagram-stories/commit/0d6da2ff83c83c7497a19a2494ca69158bd0598a))\n\n### 1.2.9 (2024-05-27)\n\n\n### Bug Fixes\n\n* avoid press when swiping ([8c003c9](https://github.com/birdwingo/react-native-instagram-stories/commit/8c003c98bab91584a92e191637fcb64fcf544daf))\n\n### 1.2.8 (2024-05-27)\n\n### 1.2.7 (2024-04-25)\n\n\n### Bug Fixes\n\n* imageOverlayView property ([7442dc9](https://github.com/birdwingo/react-native-instagram-stories/commit/7442dc91e14291cea58c1fc9b936fe04c8afba5e))\n\n### 1.2.6 (2024-02-25)\n\n\n### Bug Fixes\n\n* image props ([316891e](https://github.com/birdwingo/react-native-instagram-stories/commit/316891e6036bc05f3f5b53137c0383b309bce4d4))\n\n### 1.2.5 (2024-02-23)\n\n### 1.2.4 (2024-02-07)\n\n### 1.2.3 (2024-01-31)\n\n\n### Bug Fixes\n\n* listContainerStyle -> avatarListContainerProps ([ef195cb](https://github.com/birdwingo/react-native-instagram-stories/commit/ef195cb635e066410876f5e945cf415c7644e971))\n\n### 1.2.2 (2024-01-30)\n\n\n### Bug Fixes\n\n* non worklet function ([bfe6016](https://github.com/birdwingo/react-native-instagram-stories/commit/bfe601605de019c4801eb98b528fea53621cd6d4))\n\n### 1.2.1 (2024-01-29)\n\n## 1.2.0 (2024-01-29)\n\n\n### Features\n\n* image styles ([37760c4](https://github.com/birdwingo/react-native-instagram-stories/commit/37760c4ba461747cf2a29828a0cac733f76d78f8))\n\n### 1.1.1 (2024-01-09)\n\n\n### Bug Fixes\n\n* Property 'userId' doesn't exist ([57546c2](https://github.com/birdwingo/react-native-instagram-stories/commit/57546c2595689d058f8a01740e6aafd0e785978d))\n\n## 1.1.0 (2024-01-05)\n\n\n### Features\n\n* getCurrentStory ([9c1256e](https://github.com/birdwingo/react-native-instagram-stories/commit/9c1256eaa0e58c6c8c42e94056dc13474fe907cf))\n\n### 1.0.13 (2023-12-08)\n\n\n### Bug Fixes\n\n* non worklet function ([dc0f88e](https://github.com/birdwingo/react-native-instagram-stories/commit/dc0f88e26170d9129b30a7f8fee37c5beac55936))\n\n### 1.0.12 (2023-11-20)\n\n\n### Bug Fixes\n\n* opening first story ([b21a57c](https://github.com/birdwingo/react-native-instagram-stories/commit/b21a57c7b40c188405f4ad94dfe9c05d096eaf18))\n\n### 1.0.11 (2023-11-14)\n\n### 1.0.10 (2023-11-14)\n\n\n### Bug Fixes\n\n* progress bar animation bug ([fa1b936](https://github.com/birdwingo/react-native-instagram-stories/commit/fa1b9360d40e26b3be79bae099500468ee32ada0))\n\n### 1.0.9 (2023-11-08)\n\n\n### Bug Fixes\n\n* worklet error ([0de8cef](https://github.com/birdwingo/react-native-instagram-stories/commit/0de8cef208fef9203d33fc824b6d77acabec02c5))\n\n### 1.0.8 (2023-11-08)\n\n\n### Bug Fixes\n\n* misspelled worklet ([3ec270b](https://github.com/birdwingo/react-native-instagram-stories/commit/3ec270b0a5712d97c6dfc46fd783acb27d974693))\n\n### 1.0.7 (2023-10-31)\n\n### 1.0.6 (2023-10-31)\n\n### 1.0.5 (2023-10-31)\n\n\n### Bug Fixes\n\n* en gif ([542f651](https://github.com/birdwingo/react-native-instagram-stories/commit/542f651b572b204ad635f8a2f4095c9465648391))\n\n### 1.0.4 (2023-09-11)\n\n\n### Bug Fixes\n\n* intro animation ([0e99a47](https://github.com/birdwingo/react-native-instagram-stories/commit/0e99a47fead4859303f87a7af2243b03f9f54d4a))\n\n### 1.0.3 (2023-08-22)\n\n### 1.0.2 (2023-08-21)\n\n### 1.0.1 (2023-08-21)\n\n\n### Bug Fixes\n\n* run test ([c20e35e](https://github.com/birdwingo/react-native-instagram-stories/commit/c20e35eb18e9c953715798b5588341bf515d3309))\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Birdwingo\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": "# @birdwingo/react-native-instagram-stories\n\n![npm downloads](https://img.shields.io/npm/dm/%40birdwingo/react-native-instagram-stories)\n![npm version](https://img.shields.io/npm/v/%40birdwingo/react-native-instagram-stories)\n![github release](https://github.com/birdwingo/react-native-instagram-stories/actions/workflows/release.yml/badge.svg?event=pull_request)\n![npm release](https://github.com/birdwingo/react-native-instagram-stories/actions/workflows/public.yml/badge.svg?event=release)\n\n> **🚀 We are actively looking for maintainers!** If you're interested in contributing to this project and helping maintain it, feel free to create a PR or reach out.\n\n## Features 🌟\n\n📸 Capture Moments: Easily integrate Instagram-like stories in your React Native app to let users share their favorite moments.\n\n✨ Inspired by Instagram: Crafted with inspiration from the real Instagram stories feature, capturing its essence and style.\n\n📱 Mobile-Friendly: Designed with mobile users in mind, providing a smooth and responsive experience on all devices.\n\n💾 Using Async Storage: Utilize Async Storage to save the progress of users and load them whenever they want.\n\n🛠️ Developer Friendly: Well-documented and easy to set up, making the developer's life a breeze.\n\n🚀 High Performance: Optimized for speed, ensuring a lag-free experience for users.\n\n💡 Rich Features: Support for video, images, and text, plus more – all in one powerful package!\n\n🎉 Community Support: Join a growing community of developers and users, eager to help and share their experiences.\n\n## About\n\n`react-native-instagram-stories` component is a versatile React Native component designed to display a horizontal scrollable list of user stories, similar to the stories feature found in the Instagram app. It provides a visually appealing way to showcase stories with various customizable options. It is used in the [Birdwingo mobile app](https://www.birdwingo.com) for **Birdwingo Academy** which allows users to learn the basics of investing in stocks and ETFs.\n\n<img src=\"./src/assets/images/demo.gif\" width=\"300\">\n\n## Installation\n\n```bash\nnpm install react-native-svg\nnpm install react-native-reanimated\nnpm install react-native-gesture-handler\nnpm install @birdwingo/react-native-instagram-stories\n```\n\n## Integration with Storage, Flashlist and Video\n\nThe component offers an option to save and track the progress of seen stories using `saveProgress`. If you use `saveProgress`, please make sure you have `@react-native-async-storage/async-storage` installed.\n\nIf you have installed Flashlist, it will be automatically used for avatars list.\n\nIf you use video in your stories, please make sure you have `react-native-video` installed.\n\n## Usage\n\nTo use the `InstagramStories` component, you need to import it in your React Native application and include it in your JSX code. Here's an example of how to use it:\n\n```jsx\nimport React, { useRef } from 'react';\nimport { View } from 'react-native';\nimport InstagramStories, { InstagramStoriesPublicMethods } from '@birdwingo/react-native-instagram-stories';\n\nconst YourComponent = () => {\n\n  // to use public methods:\n  const ref = useRef( null ); // if using typescript - useRef<InstagramStoriesPublicMethods>( null )\n\n  const stories = [{ // if using typescript - const stories: InstagramStoriesProps['stories']\n    id: 'user1',\n    name: 'User 1',\n    avatarSource: { uri: 'user1-profile-image-url', },\n    stories: [\n      { id: 'story1', source: { uri: 'story1-image-url' } },\n      { id: 'story2', source: { uri: 'story1-video-url' }, mediaType: 'video' },\n      // ...\n    ]}, // ...\n  ];\n\n  // usage of public method\n  const setStories = () => ref.current?.setStories( stories );\n  \n  return (\n    <View>\n      <InstagramStories\n        ref={ref}\n        stories={stories}\n        // ...\n      />\n     <Pressable onPress={setStories}>{...}</Pressable>\n    </View>\n  );\n};\n\nexport default YourComponent;\n```\n\n## Props\n\n Name                       | Type                                         | Default value                              | Description       \n----------------------------|----------------------------------------------|--------------------------------------------|---------------------\n `stories`                  | [InstagramStoryProps](#instagramstoryprops)[]| **required**                               | An array of stories.\n `saveProgress`             | boolean                                      | false                                      | A boolean indicating whether to save and track the progress of seen stories.\n `avatarBorderColors`       | string[]                                     | [DEFAULT_COLORS](#default-gradient-colors) | An array of string colors representing the border colors of story avatars.\n `avatarSeenBorderColors`   | string[]                                     | [ '#2A2A2C' ]                              | An array of string colors representing the border colors of seen story avatars.\n `avatarSize`               | number                                       | 60                                         | The size of the story avatars.\n `storyAvatarSize`          | number                                       | 25                                         | The size of the avatars shown in the header of each story.\n `avatarListContainerStyle` | ScrollViewProps['contentContainerStyle'], FlashListProps |                                | Additional styles for the avatar scroll list container.\n `avatarListContainerProps` | ScrollViewProps                              |                                            | Props to be passed to the avatar list ScrollView component.\n `containerStyle`           | ViewStyle                                    |                                            | Additional styles for the story container.\n `textStyle`                | TextStyle                                    |                                            | Additional styles for text elements.\n `animationDuration`        | number                                       | 10000                                      | The duration of the story animations in ms.\n `videoAnimationMaxDuration`| number                                       |                                            | The max duration of the video story animations in ms. If is this property not provided, the whole video will be played.\n `backgroundColor`          | string                                       | '#000000'                                  | The background color of story container.\n `showName`                 | boolean                                      | false                                      | Whether you want to show user name under avatar in avatar list.\n `nameTextStyle`            | TextStyle                                    | { width: `avatarSize` + 10 }               | Additional styles for name text elements.\n `nameTextProps`            | TextProps                                    |                                            | Props to be passed to the name Text component.\n `videoProps`               | [react-native-video](https://www.npmjs.com/package/react-native-video?activeTab=readme#configurable-props)| | Additional props for video component. For more information, follow `react-native-video`.\n `closeIconColor`           | string                                       | '#00000099'                                | The color of story close icon.\n `progressColor`            | string                                       | '#00000099'                                | Background color of progress bar item in inactive state\n `progressActiveColor`      | string                                       | '#FFFFFF'                                  | Background color of progress bar item in active state\n `modalAnimationDuration`   | number                                       | 800                                        | Duration of modal animation in ms (showing/closing instagram stories)\n `storyAnimationDuration`   | number                                       | 800                                        | Duration of story animation (animation when swiping to the left/right)\n `mediaContainerStyle`      | ViewStyle                                    |                                            | Additional styles for media (video or image) container\n `imageStyles`              | ImageStyle                                   | { width: WIDTH, aspectRatio: 0.5626 }      | Additional styles image component\n `imageProps`               | ImageProps                                   |                                            | Additional props applied to image component\n `isVisible`                | boolean                                      | false                                      | A boolean indicating whether to show modal on load (modal will be show with first story item)\n `headerStyle`              | ViewStyle                                    |                                            | Additional styles for the story header\n `headerContainerStyle`     | ViewStyle                                    |                                            | Additional styles for the story header container\n `progressContainerStyle`   | ViewStyle                                    |                                            | Additional styles for the story progress container\n `hideAvatarList`           | boolean                                      | false                                      | A boolean indicating whether to hide avatar scroll list\n `hideElementsOnLongPress`  | boolean                                      | false                                      | A boolean indicating whether to hide all elements when story is paused by long press\n `hideOverlayOnLongPress`   | `boolean`                                    | The value of `hideElementOnLongPress`      | Controls whether the image overlay hides when `hideElementOnLongPress` is set to `true`. If `true`, the overlay will hide along with other elements on long press. If `false`, only the other elements (e.g., header, progress bar) will hide, and the overlay will remain visible.\n `loopingStories`           | `'none'` | `'onlyLast'` | `'all'`            | `'none'`                                   | A string indicating whether to continue stories after last story was shown. If set to `'none'` modal will be closed after all stories were played, if set to `'onlyLast'` stories will loop on last user only after all stories were played. If set to `'all'` stories will play from beginning after all stories were played.\n `statusBarTranslucent`     | boolean                                      | false                                      | A property passed to React native Modal component\n `loaderColor`              | string                                       | '#FFFFFF'                                  | The color of the loading spinner.\n `loaderBackgroundColor`    | string                                       |                                            | Background color of the loading overlay.\n `footerComponent`          | ReactNode                                    |                                            | A custom component, such as a floating element, that can be added to the modal.\n `imageOverlayView`         | ReactNode                                    |                                            | Image overlay compontent\n `onShow`                   | ( id: string ) => void                       |                                            | Callback when a story is shown.\n `onHide`                   | ( id: string ) => void                       |                                            | Callback when a story is hidden.\n `onSwipeUp`                | ( userId?: string, storyId?: string ) => void|                                            | Callback when user swipes up.\n `onStoryStart`             | ( userId?: string, storyId?: string ) => void|                                            | Callback when story started\n `onStoryEnd`               | ( userId?: string, storyId?: string ) => void|                                            | Callback when story ended\n\n## Public Methods\n\n Name                  | Type                                                                                             | Description\n---------------------- |--------------------------------------------------------------------------------------------------|---------------------------\n `spliceStories`       | ( stories: [InstagramStoryProps](#instagramstoryprops)[], index?: number ) => void               | Insert new stories at a specific index. If you don't provide `index` property, stories will be pushed to the end of array.\n `spliceUserStories`   | ( stories: [InstagramStoryProps](#instagramstoryprops)[], user: string, index?: number ) => void | Insert new stories for a specific user at a specific index. If you don't provide `index` property, stories will be pushed to the end of array\n `setStories`          | ( stories: [InstagramStoryProps](#instagramstoryprops)[] ) => void                               | Replace the current stories with a new set of stories.\n `clearProgressStorage`| () => void                                                                                       | Clear the progress storage for seen stories.\n `hide`                | () => void                                                                                       | Hide stories if currently visible\n `show`                | ( id?: string ) => void                                                                          | Show stories modal with provided story `id`. If `id` is not provided, will be shown first story\n `pause`               | () => void                                                                                       | Pause story\n `resume`              | () => void                                                                                       | Resume story\n `isPaused`            | () => boolean                                                                                    | Returns true if story is paused\n `goToPreviousStory`   | () => void                                                                                       | Goes to previous story item\n `goToNextStory`       | () => void                                                                                       | Goes to next story item\n `getCurrentStory`     | () => {userId?: string, storyId?: string}                                                        | Returns current userId and storyId\n `goToSpecificStory`   | ( userId: string, index: number ) => void                                                        | Change current playing story to defined index if index not exist then start playing first story\n\n## Types\n\n### InstagramStoryProps\n\n Parameter             | Type                                   | Required\n-----------------------|----------------------------------------|----------------\n `id`                  | string                                 | true\n `avatarSource`        | ImageProps['source']                   | false\n `renderAvatar`        | () => ReactNode                        | false\n `renderStoryHeader`   | () => ReactNode                        | false\n `onStoryHeaderPress`  | () => void                             | false\n `name`                | string                                 | false\n `stories`             | [StoryItemProps](#storyitemprops)[]    | true\n\n**Please note that id parameter must be unique for every user**\n\n### StoryItemProps\n\n Parameter             | Type                                     | Required\n-----------------------|------------------------------------------|-------------------\n `id`                  | string                                   | true\n `source`              | ImageProps['source']                     | true\n `mediaType`           | 'video' \\| 'image' (default: `'image'`)  | false\n `animationDuration`   | number                                   | false\n `renderContent`       | () => ReactNode                          | false\n `renderFooter`        | () => ReactNode                          | false\n\n**Please note that id parameter must be unique for every story**\n\n### Default Gradient Colors\nDefault colors for avatar gradient are the same as on Instagram - `[ '#F7B801', '#F18701', '#F35B04', '#F5301E', '#C81D4E', '#8F1D4E' ]`\n\n## Sponsor\n\n**react-native-instagram-stories** is sponsored by [Birdwingo](https://www.birdwingo.com).\\\nDownload Birdwingo mobile app to see react-native-instagram-stories in action!\n"
  },
  {
    "path": "babel.config.js",
    "content": "\nmodule.exports = {\n  presets: ['module:metro-react-native-babel-preset'],\n  plugins: ['react-native-reanimated/plugin'],\n};\n\n"
  },
  {
    "path": "commitlint.config.js",
    "content": "module.exports = {extends: ['@commitlint/config-conventional']}\n"
  },
  {
    "path": "jest.setup.js",
    "content": "\njest.mock('react-native-reanimated', () => {\n\n  const View = require('react-native').View;\n\n  return {\n    Value: jest.fn(),\n    event: jest.fn(),\n    add: jest.fn(),\n    eq: jest.fn(),\n    set: jest.fn(),\n    cond: jest.fn(),\n    interpolate: jest.fn(),\n    View: (props) => <View {...props} />,\n    createAnimatedComponent: (cb) => cb,\n    Extrapolate: { CLAMP: jest.fn() },\n    Transition: {\n      Together: 'Together',\n      Out: 'Out',\n      In: 'In',\n    },\n    useSharedValue: jest.fn(),\n    useDerivedValue: (a) => ({ value: a() }),\n    useAnimatedScrollHandler: () => () => {},\n    useAnimatedGestureHandler: ({onStart, onActive, onFinish}) => ({onStart, onActive, onFinish}),\n    useAnimatedStyle: (cb) => cb(),\n    useAnimatedRef: () => ({ current: null }),\n    useAnimatedReaction: jest.fn(),\n    useAnimatedProps: (cb) => cb(),\n    withTiming: (toValue, _, cb) => {\n      cb && cb(true);\n      return toValue;\n    },\n    withSpring: (toValue, _, cb) => {\n      cb && cb(true);\n      return toValue;\n    },\n    withDecay: (_, cb) => {\n      cb && cb(true);\n      return 0;\n    },\n    withDelay: (_, animationValue) => {\n      return animationValue;\n    },\n    withSequence: (..._animations) => {\n      return 0;\n    },\n    withRepeat: (animation, _, __, cb) => {\n      cb?.();\n      return animation;\n    },\n    cancelAnimation: () => {},\n    measure: () => ({\n      x: 0,\n      y: 0,\n      width: 0,\n      height: 0,\n      pageX: 0,\n      pageY: 0,\n    }),\n    Easing: {\n      linear: (cb) => cb(),\n      ease: (cb) => cb(),\n      quad: (cb) => cb(),\n      cubic: (cb) => cb(),\n      poly: (cb) => cb(),\n      sin: (cb) => cb(),\n      circle: (cb) => cb(),\n      exp: (cb) => cb(),\n      elastic: (cb) => cb(),\n      back: (cb) => cb(),\n      bounce: (cb) => cb(),\n      bezier: () => ({ factory: (cb) => cb() }),\n      bezierFn: (cb) => cb(),\n      steps: (cb) => cb(),\n      in: (cb) => cb(),\n      out: (cb) => cb(),\n      inOut: (cb) => cb(),\n    },\n    Extrapolation: {\n      EXTEND: 'extend',\n      CLAMP: 'clamp',\n      IDENTITY: 'identity',\n    },\n    runOnJS: (fn) => fn,\n    runOnUI: (fn) => fn,\n  };\n});\n\njest.mock('react-native-gesture-handler', () => {\n\n  const View = require('react-native').View;\n  const createGesture = () => {\n\n    const gesture = {\n      _onStart: undefined,\n      _onUpdate: undefined,\n      _onFinalize: undefined,\n      onStart( cb ) {\n\n        this._onStart = cb;\n        return this;\n\n      },\n      onUpdate( cb ) {\n\n        this._onUpdate = cb;\n        return this;\n\n      },\n      onFinalize( cb ) {\n\n        this._onFinalize = cb;\n        return this;\n\n      },\n    };\n\n    return gesture;\n\n  };\n\n  return {\n    PanGestureHandler: ({onGestureEvent, children}) => (\n      <View\n        onResponderStart={( ...args ) => onGestureEvent.onStart?.( ...args )} \n        onResponderEnd={( ...args ) => onGestureEvent.onFinish?.( ...args )} \n        onResponderMove={( ...args ) => onGestureEvent.onActive?.( ...args )}\n        testID=\"gestureContainer\"\n      >\n        {children}\n      </View>\n    ),\n    Gesture: {\n      Pan: () => createGesture(),\n    },\n    GestureDetector: ({ gesture, children }) => (\n      <View\n        onResponderStart={( ...args ) => gesture?._onStart?.( ...args )}\n        onResponderMove={( ...args ) => gesture?._onUpdate?.( ...args )}\n        onResponderEnd={( ...args ) => gesture?._onFinalize?.( ...args )}\n        testID=\"gestureContainer\"\n      >\n        {children}\n      </View>\n    ),\n    GestureHandlerRootView: ({ children, ...props }) => <View {...props}>{children}</View>,\n    gestureHandlerRootHOC: (Component) => Component,\n  };\n\n});\n\njest.mock('./src/core/helpers/storage', () => ({\n  clearProgressStorage: () => {},\n  getProgressStorage: jest.fn(),\n  setProgressStorage: jest.fn(),\n}));\n\njest.mock('./src/components/Image/video', () => {\n\n  const React = require('react');\n  const { View } = require('react-native');\n\n  return ( props ) => {\n  \n    const { onLoad, onLayout } = props;\n\n    onLoad?.(10000);\n    onLayout?.({ nativeEvent: { layout: { width: 100, height: 100 } } });\n\n    return <View testID=\"storyVideo\" />;\n  };\n});\n\njest.mock('@shopify/flash-list', () => {\n\n  const React = require('react');\n  const { ScrollView } = require('react-native');\n\n  return {FlashList: ({ data, renderItem, ...props }) => {\n\n    return (\n      <ScrollView {...props}>\n        {data.map(( item, index ) => renderItem({ item, index }))}\n      </ScrollView>\n    )\n\n  }};\n\n});"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@birdwingo/react-native-instagram-stories\",\n  \"version\": \"1.3.18\",\n  \"description\": \"A versatile and captivating React Native component that empowers developers to seamlessly integrate Instagram-style stories into their mobile applications, fostering an engaging and interactive user experience.\",\n  \"main\": \"src/index.tsx\",\n  \"source\": \"src/index.tsx\",\n  \"scripts\": {\n    \"prepare\": \"husky install\",\n    \"build\": \"tsc -p tsconfig.json\",\n    \"release\": \"standard-version\",\n    \"test\": \"jest --coverage\"\n  },\n  \"repository\": \"https://github.com/birdwingo/react-native-instagram-stories.git\",\n  \"keywords\": [\n    \"react-native\",\n    \"android\",\n    \"ios\",\n    \"react\",\n    \"react-native-reanimated\",\n    \"reanimated\",\n    \"animated\",\n    \"animation\",\n    \"performance\",\n    \"stories\",\n    \"instagram\",\n    \"instagram-stories\",\n    \"story\"\n  ],\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/birdwingo/react-native-instagram-stories/issues\"\n  },\n  \"homepage\": \"https://github.com/birdwingo/react-native-instagram-stories#readme\",\n  \"peerDependencies\": {\n    \"react\": \">=18.0.0\",\n    \"react-native\": \">=0.70.0\",\n    \"react-native-gesture-handler\": \">=2.10.0\",\n    \"react-native-reanimated\": \">=2.12.0\",\n    \"react-native-svg\": \">=13.6.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/preset-env\": \"^7.22.9\",\n    \"@babel/preset-typescript\": \"^7.22.5\",\n    \"@commitlint/cli\": \"^17.6.7\",\n    \"@commitlint/config-conventional\": \"^17.6.7\",\n    \"@react-native-async-storage/async-storage\": \"^1.19.2\",\n    \"@shopify/flash-list\": \"^1.7.1\",\n    \"@testing-library/jest-native\": \"^5.4.2\",\n    \"@testing-library/react-native\": \"^12.1.3\",\n    \"@tsconfig/react-native\": \"^3.0.0\",\n    \"@types/jest\": \"^29.5.3\",\n    \"@types/react\": \"^18.2.16\",\n    \"eslint\": \"^8.19.0\",\n    \"eslint-config-airbnb\": \"^19.0.2\",\n    \"eslint-config-airbnb-typescript\": \"^16.1.0\",\n    \"husky\": \"^8.0.3\",\n    \"jest\": \"^29.6.2\",\n    \"react-native-video\": \"^5.2.1\",\n    \"react-test-renderer\": \"^18.2.0\",\n    \"standard-version\": \"^9.5.0\",\n    \"typescript\": \"^5.1.6\"\n  },\n  \"jest\": {\n    \"preset\": \"react-native\",\n    \"moduleFileExtensions\": [\n      \"ts\",\n      \"tsx\",\n      \"js\",\n      \"jsx\",\n      \"json\",\n      \"node\"\n    ],\n    \"testMatch\": [\n      \"<rootDir>/tests/*.test.js\"\n    ],\n    \"setupFilesAfterEnv\": [\n      \"@testing-library/jest-native/extend-expect\",\n      \"<rootDir>/jest.setup.js\"\n    ],\n    \"setupFiles\": [\n      \"./node_modules/react-native-gesture-handler/jestSetup.js\"\n    ],\n    \"verbose\": true\n  }\n}\n"
  },
  {
    "path": "src/components/Animation/Animation.styles.ts",
    "content": "import { StyleSheet } from 'react-native';\n\nexport default StyleSheet.create( {\n  container: StyleSheet.absoluteFillObject,\n  absolute: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n  },\n  cube: {\n    justifyContent: 'center',\n  },\n} );\n"
  },
  {
    "path": "src/components/Animation/index.tsx",
    "content": "import React, { FC, memo } from 'react';\nimport { Platform } from 'react-native';\nimport Animated, { Extrapolation, interpolate, useAnimatedStyle } from 'react-native-reanimated';\nimport { HEIGHT, WIDTH } from '../../core/constants';\nimport { AnimationProps } from '../../core/dto/componentsDTO';\nimport AnimationStyles from './Animation.styles';\n\nconst StoryAnimation: FC<AnimationProps> = ( { children, x, index } ) => {\n\n  const angle = Math.PI / 3;\n  const ratio = Platform.OS === 'ios' ? 2 : 1.2;\n  const offset = WIDTH * index;\n  const inputRange = [ offset - WIDTH, offset + WIDTH ];\n  const maskInputRange = [ offset - WIDTH, offset, offset + WIDTH ];\n\n  const animatedStyle = useAnimatedStyle( () => {\n\n    const translateX = interpolate(\n      x.value,\n      inputRange,\n      [ WIDTH / ratio, -WIDTH / ratio ],\n      Extrapolation.CLAMP,\n    );\n\n    const rotateY = interpolate( x.value, inputRange, [ angle, -angle ], Extrapolation.CLAMP );\n\n    const alpha = Math.abs( rotateY );\n    const gamma = angle - alpha;\n    const beta = Math.PI - alpha - gamma;\n    const w = WIDTH / 2 - ( WIDTH / 2 * ( Math.sin( gamma ) / Math.sin( beta ) ) );\n    const translateX1 = rotateY > 0 ? w : -w;\n    const left = Platform.OS === 'android' ? interpolate(\n      rotateY,\n      [ -angle, -angle + 0.1, 0, angle - 0.1, angle ],\n      [ 0, 20, 0, -20, 0 ],\n      Extrapolation.CLAMP,\n    ) : 0;\n\n    return {\n      transform: [\n        { perspective: WIDTH },\n        { translateX },\n        { rotateY: `${rotateY}rad` },\n        { translateX: translateX1 },\n      ],\n      left,\n    };\n\n  } );\n\n  const maskAnimatedStyles = useAnimatedStyle( () => ( {\n    opacity: interpolate( x.value, maskInputRange, [ 0.5, 0, 0.5 ], Extrapolation.CLAMP ),\n  } ) );\n\n  return (\n    <Animated.View style={[ animatedStyle, AnimationStyles.container, AnimationStyles.cube ]}>\n      {children}\n      <Animated.View style={[ maskAnimatedStyles, AnimationStyles.absolute, { width: WIDTH, height: HEIGHT } ]} pointerEvents=\"none\" />\n    </Animated.View>\n  );\n\n};\n\nexport default memo( StoryAnimation );\n"
  },
  {
    "path": "src/components/Avatar/Avatar.styles.ts",
    "content": "import { StyleSheet } from 'react-native';\nimport { AVATAR_OFFSET } from '../../core/constants';\n\nexport default StyleSheet.create( {\n  container: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  avatar: {\n    left: AVATAR_OFFSET,\n    top: AVATAR_OFFSET,\n    position: 'absolute',\n  },\n  name: {\n    alignItems: 'center',\n  },\n} );\n"
  },
  {
    "path": "src/components/Avatar/index.tsx",
    "content": "import React, { FC, memo } from 'react';\nimport {\n  View, Image, Text, TouchableOpacity,\n} from 'react-native';\nimport Animated, {\n  useSharedValue, useAnimatedStyle, useDerivedValue, withTiming,\n} from 'react-native-reanimated';\nimport { StoryAvatarProps } from '../../core/dto/componentsDTO';\nimport AvatarStyles from './Avatar.styles';\nimport Loader from '../Loader';\nimport { AVATAR_OFFSET } from '../../core/constants';\n\nconst AnimatedImage = Animated.createAnimatedComponent( Image );\n\nconst StoryAvatar: FC<StoryAvatarProps> = ( {\n  id,\n  avatarSource,\n  name,\n  stories,\n  loadingStory,\n  seenStories,\n  onPress,\n  colors,\n  seenColors,\n  size,\n  showName,\n  nameTextStyle,\n  nameTextProps,\n  renderAvatar,\n  avatarBorderRadius,\n} ) => {\n\n  const loaded = useSharedValue( false );\n  const isLoading = useDerivedValue( () => loadingStory.value === id || !loaded.value );\n  const seen = useDerivedValue(\n    () => seenStories.value[id] === stories[stories.length - 1]?.id,\n  );\n  const loaderColor = useDerivedValue( () => ( seen.value ? seenColors : colors ) );\n\n  const onLoad = () => {\n\n    loaded.value = true;\n\n  };\n\n  const imageAnimatedStyles = useAnimatedStyle( () => (\n    { opacity: withTiming( isLoading.value ? 0.5 : 1 ) }\n  ) );\n\n  if ( renderAvatar ) {\n\n    return renderAvatar( seen.value );\n\n  }\n\n  if ( !avatarSource ) {\n\n    return null;\n\n  }\n\n  return (\n    <View style={AvatarStyles.name}>\n      <View style={AvatarStyles.container}>\n        <TouchableOpacity activeOpacity={0.6} onPress={onPress} testID={`${id}StoryAvatar${stories.length}Story`}>\n          <Loader loading={isLoading} color={loaderColor} size={size + AVATAR_OFFSET * 2} />\n          <AnimatedImage\n            source={avatarSource}\n            style={[\n              AvatarStyles.avatar,\n              imageAnimatedStyles,\n              { width: size, height: size, borderRadius: avatarBorderRadius ?? ( size / 2 ) },\n            ]}\n            testID=\"storyAvatarImage\"\n            onLoad={onLoad}\n          />\n        </TouchableOpacity>\n      </View>\n      {Boolean( showName ) && (\n        <Text\n          {...nameTextProps}\n          style={[ { width: size + AVATAR_OFFSET * 2 }, nameTextStyle ]}\n        >\n          {name}\n        </Text>\n      )}\n    </View>\n  );\n\n};\n\nexport default memo( StoryAvatar );\n"
  },
  {
    "path": "src/components/AvatarList/index.tsx",
    "content": "import React, { FC, memo } from 'react';\nimport { ScrollView } from 'react-native';\nimport StoryAvatar from '../Avatar';\nimport { StoryAvatarListProps } from '../../core/dto/componentsDTO';\nimport { InstagramStoryProps } from '../../core/dto/instagramStoriesDTO';\n\nlet FlashList: any;\n\ntry {\n\n  // eslint-disable-next-line global-require\n  FlashList = require( '@shopify/flash-list' ).FlashList;\n\n} catch ( error ) {\n\n  FlashList = null;\n\n}\n\nconst StoryAvatarList: FC<StoryAvatarListProps> = ( {\n  stories, loadingStory, seenStories, colors, seenColors, size,\n  showName, nameTextStyle, nameTextProps,\n  avatarListContainerProps, avatarListContainerStyle, avatarBorderRadius, onPress,\n} ) => {\n\n  const renderItem = ( story: InstagramStoryProps ) => (\n    <StoryAvatar\n      {...story}\n      loadingStory={loadingStory}\n      seenStories={seenStories}\n      onPress={() => onPress( story.id )}\n      colors={colors}\n      seenColors={seenColors}\n      size={size}\n      showName={showName}\n      nameTextStyle={nameTextStyle}\n      nameTextProps={nameTextProps}\n      avatarBorderRadius={avatarBorderRadius}\n      key={`avatar${story.id}`}\n    />\n  );\n\n  if ( FlashList ) {\n\n    return (\n      <FlashList\n        horizontal\n        {...avatarListContainerProps}\n        data={stories}\n        renderItem={( { item } : { item: InstagramStoryProps } ) => renderItem( item )}\n        keyExtractor={( item: InstagramStoryProps ) => item.id}\n        contentContainerStyle={avatarListContainerStyle}\n        testID=\"storiesList\"\n      />\n    );\n\n  }\n\n  return (\n    <ScrollView horizontal {...avatarListContainerProps} contentContainerStyle={avatarListContainerStyle} testID=\"storiesList\">\n      {stories.map( renderItem )}\n    </ScrollView>\n  );\n\n};\n\nexport default memo( StoryAvatarList );\n"
  },
  {
    "path": "src/components/Content/Content.styles.ts",
    "content": "import { StyleSheet } from 'react-native';\n\nexport default StyleSheet.create( {\n  container: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    bottom: 0,\n    right: 0,\n  },\n} );\n"
  },
  {
    "path": "src/components/Content/index.tsx",
    "content": "import React, {\n  FC, memo, useState, useMemo,\n} from 'react';\nimport { View } from 'react-native';\nimport { runOnJS, useAnimatedReaction } from 'react-native-reanimated';\nimport { StoryContentProps } from '../../core/dto/componentsDTO';\nimport ContentStyles from './Content.styles';\n\nconst StoryContent: FC<StoryContentProps> = ( { stories, active, activeStory } ) => {\n\n  const [ storyIndex, setStoryIndex ] = useState( 0 );\n\n  const onChange = async () => {\n\n    'worklet';\n\n    const index = stories.findIndex( ( item ) => item.id === activeStory.value );\n    if ( active.value && index >= 0 && index !== storyIndex ) {\n\n      runOnJS( setStoryIndex )( index );\n\n    }\n\n  };\n\n  useAnimatedReaction(\n    () => active.value,\n    ( res, prev ) => res !== prev && onChange(),\n  );\n\n  useAnimatedReaction(\n    () => activeStory.value,\n    ( res, prev ) => res !== prev && onChange(),\n  );\n\n  const content = useMemo( () => stories[storyIndex]?.renderContent?.(), [ storyIndex ] );\n\n  return content ? <View style={ContentStyles.container} pointerEvents=\"box-none\">{content}</View> : null;\n\n};\n\nexport default memo( StoryContent );\n"
  },
  {
    "path": "src/components/Footer/Footer.styles.ts",
    "content": "import { StyleSheet } from 'react-native';\n\nexport default StyleSheet.create( {\n  container: {\n    position: 'absolute',\n    bottom: 0,\n    left: 0,\n    right: 0,\n  },\n} );\n"
  },
  {
    "path": "src/components/Footer/index.tsx",
    "content": "import React, {\n  FC, memo, useState, useMemo,\n} from 'react';\nimport { View } from 'react-native';\nimport { runOnJS, useAnimatedReaction } from 'react-native-reanimated';\nimport { StoryContentProps } from '../../core/dto/componentsDTO';\nimport ContentStyles from './Footer.styles';\n\nconst StoryFooter: FC<StoryContentProps> = ( { stories, active, activeStory } ) => {\n\n  const [ storyIndex, setStoryIndex ] = useState( 0 );\n\n  const onChange = async () => {\n\n    'worklet';\n\n    const index = stories.findIndex( ( item ) => item.id === activeStory.value );\n    if ( active.value && index >= 0 && index !== storyIndex ) {\n\n      runOnJS( setStoryIndex )( index );\n\n    }\n\n  };\n\n  useAnimatedReaction(\n    () => active.value,\n    ( res, prev ) => res !== prev && onChange(),\n  );\n\n  useAnimatedReaction(\n    () => activeStory.value,\n    ( res, prev ) => res !== prev && onChange(),\n  );\n\n  const footer = useMemo( () => stories[storyIndex]?.renderFooter?.(), [ storyIndex ] );\n\n  return footer ? <View style={ContentStyles.container} pointerEvents=\"box-none\">{footer}</View> : null;\n\n};\n\nexport default memo( StoryFooter );\n"
  },
  {
    "path": "src/components/Header/Header.styles.ts",
    "content": "import { StyleSheet } from 'react-native';\n\nexport default StyleSheet.create( {\n  container: {\n    position: 'absolute',\n    left: 16,\n    top: 32,\n  },\n  containerFlex: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n    alignItems: 'center',\n  },\n  left: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    gap: 12,\n    flex: 1,\n  },\n  avatar: {\n    borderWidth: 1.5,\n    borderColor: '#FFF',\n    overflow: 'hidden',\n  },\n} );\n"
  },
  {
    "path": "src/components/Header/index.tsx",
    "content": "import React, { FC, memo } from 'react';\nimport {\n  View, Text, Image, TouchableOpacity,\n  Pressable,\n} from 'react-native';\nimport { WIDTH } from '../../core/constants';\nimport HeaderStyles from './Header.styles';\nimport { StoryHeaderProps } from '../../core/dto/componentsDTO';\nimport Close from '../Icon/close';\n\nconst StoryHeader: FC<StoryHeaderProps> = ( {\n  avatarSource, name, onClose, avatarSize, textStyle, closeColor, headerStyle,\n  headerContainerStyle, renderStoryHeader, onStoryHeaderPress,\n} ) => {\n\n  const styles = { width: avatarSize, height: avatarSize, borderRadius: avatarSize };\n  const width = WIDTH - HeaderStyles.container.left * 2;\n\n  if ( renderStoryHeader ) {\n\n    return (\n      <View\n        style={[ HeaderStyles.container, { width }, headerContainerStyle ]}\n      >\n        {renderStoryHeader()}\n      </View>\n    );\n\n  }\n\n  return (\n    <View style={[\n      HeaderStyles.container, HeaderStyles.containerFlex,\n      { width }, headerContainerStyle,\n    ]}\n    >\n      <Pressable style={[ HeaderStyles.left, headerStyle ]} onPress={() => onStoryHeaderPress?.()}>\n        {( Boolean( avatarSource ) ) && (\n          <View style={[ HeaderStyles.avatar, { borderRadius: styles.borderRadius } ]}>\n            <Image source={avatarSource!} style={styles} />\n          </View>\n        )}\n        {Boolean( name ) && <Text style={textStyle}>{name}</Text>}\n      </Pressable>\n      <TouchableOpacity\n        onPress={onClose}\n        hitSlop={16}\n        testID=\"storyCloseButton\"\n      >\n        <Close color={closeColor} />\n      </TouchableOpacity>\n    </View>\n  );\n\n};\n\nexport default memo( StoryHeader );\n"
  },
  {
    "path": "src/components/Icon/close.tsx",
    "content": "import React, { FC, memo } from 'react';\nimport { Path, Svg } from 'react-native-svg';\nimport { IconProps } from '../../core/dto/componentsDTO';\n\nconst Close: FC<IconProps> = ( { color } ) => (\n  <Svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\">\n    <Path fill={color} d=\"M17.3422 15.7964C17.7651 16.2193 17.7651 16.9193 17.3422 17.3422C17.1234 17.5609 16.8464 17.663 16.5693 17.663C16.2922 17.663 16.0151 17.5609 15.7964 17.3422L11.8297 13.3755L7.86302 17.3422C7.64427 17.5609 7.36719 17.663 7.0901 17.663C6.81302 17.663 6.53594 17.5609 6.31719 17.3422C5.89427 16.9193 5.89427 16.2193 6.31719 15.7964L10.2839 11.8297L6.31719 7.86302C5.89427 7.4401 5.89427 6.7401 6.31719 6.31719C6.7401 5.89427 7.4401 5.89427 7.86302 6.31719L11.8297 10.2839L15.7964 6.31719C16.2193 5.89427 16.9193 5.89427 17.3422 6.31719C17.7651 6.7401 17.7651 7.4401 17.3422 7.86302L13.3755 11.8297L17.3422 15.7964Z\" />\n  </Svg>\n);\n\nexport default memo( Close );\n"
  },
  {
    "path": "src/components/Icon/index.tsx",
    "content": "import Close from './close';\n\nexport { Close };\n"
  },
  {
    "path": "src/components/Image/Image.styles.ts",
    "content": "import { StyleSheet } from 'react-native';\n\nexport default StyleSheet.create( {\n  container: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    bottom: 0,\n    right: 0,\n    alignItems: 'center',\n    justifyContent: 'center',\n    zIndex: 2,\n  },\n  image: {\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n} );\n"
  },
  {
    "path": "src/components/Image/index.tsx",
    "content": "import { Image, View } from 'react-native';\nimport React, { FC, memo, useState } from 'react';\nimport Animated, {\n  runOnJS, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useSharedValue,\n} from 'react-native-reanimated';\nimport { StoryImageProps } from '../../core/dto/componentsDTO';\nimport Loader from '../Loader';\nimport { HEIGHT, LOADER_COLORS, WIDTH } from '../../core/constants';\nimport ImageStyles from './Image.styles';\nimport StoryVideo from './video';\nimport { StoryItemProps } from '../../core/dto/instagramStoriesDTO';\n\nconst StoryImage: FC<StoryImageProps> = ( {\n  stories, activeStory, defaultStory, isDefaultVideo, paused, videoProps, isActive,\n  mediaContainerStyle, imageStyles, imageProps, videoDuration, loaderColor,\n  loaderBackgroundColor, onImageLayout, onLoad,\n} ) => {\n\n  const [ data, setData ] = useState<{ data?: StoryItemProps, isVideo?: boolean }>(\n    { data: defaultStory, isVideo: isDefaultVideo },\n  );\n\n  const loading = useSharedValue( true );\n  const color = useSharedValue( loaderColor ? [ loaderColor ] : LOADER_COLORS );\n  const duration = useSharedValue<number | undefined>( undefined );\n  const isPaused = useDerivedValue( () => paused.value || !isActive.value );\n\n  const loaderHideStyle = useAnimatedStyle( () => ( {\n    opacity: loading.value ? 1 : 0,\n  } ) );\n\n  const loaderBackgroundStyle = useAnimatedStyle( () => ( {\n    backgroundColor: loaderBackgroundColor || 'transparent',\n  } ) );\n\n  const onImageChange = async () => {\n\n    if ( !activeStory.value ) {\n\n      return;\n\n    }\n\n    const story = stories.find( ( item ) => item.id === activeStory.value );\n\n    if ( !story ) {\n\n      return;\n\n    }\n\n    if ( data.data?.id === story.id ) {\n\n      if ( !loading.value ) {\n\n        onLoad( duration.value );\n\n      }\n\n    } else {\n\n      loading.value = true;\n      setData( { data: story, isVideo: story.mediaType === 'video' } );\n\n    }\n\n    const nextStory = stories[stories.indexOf( story ) + 1];\n\n    if ( nextStory && nextStory.mediaType !== 'video' && ( nextStory.source as any )?.uri ) {\n\n      Image.prefetch( ( nextStory.source as any )?.uri );\n\n    }\n\n  };\n\n  useAnimatedReaction(\n    () => isActive.value,\n    ( res, prev ) => res !== prev && res && runOnJS( onImageChange )(),\n  );\n\n  useAnimatedReaction(\n    () => activeStory.value,\n    ( res, prev ) => res !== prev && runOnJS( onImageChange )(),\n  );\n\n  const onContentLoad = ( newDuration?: number ) => {\n\n    const animationDuration = ( data?.data?.mediaType === 'video' ? videoDuration : undefined ) ?? data.data?.animationDuration ?? newDuration;\n    duration.value = animationDuration;\n\n    loading.value = false;\n\n    if ( isActive.value ) {\n\n      onLoad( animationDuration );\n\n    }\n\n  };\n\n  return (\n    <>\n      <Animated.View style={[ ImageStyles.container, loaderHideStyle, loaderBackgroundStyle ]}>\n        <Loader loading={loading} color={color} size={50} />\n      </Animated.View>\n      <View style={[ ImageStyles.image, mediaContainerStyle ]}>\n        {data.data?.source && (\n          data.isVideo ? (\n            <StoryVideo\n              onLoad={onContentLoad}\n              onLayout={onImageLayout}\n              source={data.data.source}\n              paused={isPaused}\n              isActive={isActive}\n              {...videoProps}\n            />\n          ) : (\n            <Image\n              source={data.data.source}\n              style={[ { width: WIDTH, aspectRatio: 0.5626 }, imageStyles ]}\n              resizeMode=\"contain\"\n              testID=\"storyImageComponent\"\n              onLayout={( e ) => onImageLayout( Math.min( HEIGHT, e.nativeEvent.layout.height ) )}\n              onLoad={() => onContentLoad()}\n              {...imageProps}\n            />\n          )\n        )}\n      </View>\n    </>\n  );\n\n};\n\nexport default memo( StoryImage );\n"
  },
  {
    "path": "src/components/Image/video.tsx",
    "content": "import React, {\n  FC, memo, useRef, useState,\n} from 'react';\nimport { LayoutChangeEvent } from 'react-native';\nimport { runOnJS, useAnimatedReaction } from 'react-native-reanimated';\nimport { StoryVideoProps } from '../../core/dto/componentsDTO';\nimport { WIDTH } from '../../core/constants';\n\nconst StoryVideo: FC<StoryVideoProps> = ( {\n  source, paused, isActive, onLoad, onLayout, ...props\n} ) => {\n\n  try {\n\n    // eslint-disable-next-line global-require\n    const Video = require( 'react-native-video' ).default;\n\n    const ref = useRef<any>( null );\n\n    const [ pausedValue, setPausedValue ] = useState( true );\n\n    const start = () => {\n\n      ref.current?.seek( 0 );\n      ref.current?.resume?.();\n\n    };\n\n    useAnimatedReaction(\n      () => paused.value,\n      ( res, prev ) => res !== prev && runOnJS( setPausedValue )( res ),\n    );\n\n    useAnimatedReaction(\n      () => isActive.value,\n      ( res ) => res && runOnJS( start )(),\n    );\n\n    return (\n      <Video\n        ref={ref}\n        style={{ width: WIDTH, aspectRatio: 0.5626 }}\n        {...props}\n        source={source}\n        paused={pausedValue}\n        controls={false}\n        repeat={false}\n        onLoad={( { duration }: { duration: number } ) => onLoad( duration * 1000 )}\n        onLayout={( e: LayoutChangeEvent ) => onLayout( e.nativeEvent.layout.height )}\n      />\n    );\n\n  } catch ( error ) {\n\n    return null;\n\n  }\n\n};\n\nexport default memo( StoryVideo );\n"
  },
  {
    "path": "src/components/InstagramStories/InstagramStories.styles.ts",
    "content": ""
  },
  {
    "path": "src/components/InstagramStories/index.tsx",
    "content": "import React, {\n  forwardRef, useImperativeHandle, useState, useEffect, useRef, memo,\n} from 'react';\nimport { useSharedValue } from 'react-native-reanimated';\nimport { Image } from 'react-native';\nimport { clearProgressStorage, getProgressStorage, setProgressStorage } from '../../core/helpers/storage';\nimport { InstagramStoriesProps, InstagramStoriesPublicMethods } from '../../core/dto/instagramStoriesDTO';\nimport { ProgressStorageProps } from '../../core/dto/helpersDTO';\nimport {\n  ANIMATION_DURATION, DEFAULT_COLORS, SEEN_LOADER_COLORS,\n  STORY_AVATAR_SIZE, AVATAR_SIZE, BACKGROUND_COLOR, CLOSE_COLOR,\n} from '../../core/constants';\nimport StoryModal from '../Modal';\nimport { StoryModalPublicMethods } from '../../core/dto/componentsDTO';\nimport StoryAvatarList from '../AvatarList';\n\nconst InstagramStories = forwardRef<InstagramStoriesPublicMethods, InstagramStoriesProps>( ( {\n  stories,\n  saveProgress = false,\n  avatarBorderColors = DEFAULT_COLORS,\n  avatarSeenBorderColors = SEEN_LOADER_COLORS,\n  avatarSize = AVATAR_SIZE,\n  storyAvatarSize = STORY_AVATAR_SIZE,\n  avatarListContainerStyle,\n  avatarListContainerProps,\n  animationDuration = ANIMATION_DURATION,\n  backgroundColor = BACKGROUND_COLOR,\n  showName = false,\n  nameTextStyle,\n  nameTextProps,\n  videoAnimationMaxDuration,\n  videoProps,\n  closeIconColor = CLOSE_COLOR,\n  isVisible = false,\n  hideAvatarList = false,\n  avatarBorderRadius,\n  loaderColor,\n  loaderBackgroundColor,\n  ...props\n}, ref ) => {\n\n  const [ data, setData ] = useState( stories );\n\n  const seenStories = useSharedValue<ProgressStorageProps>( {} );\n  const loadedStories = useSharedValue( false );\n  const loadingStory = useSharedValue<string | undefined>( undefined );\n\n  const modalRef = useRef<StoryModalPublicMethods>( null );\n\n  const onPress = ( id: string ) => {\n\n    loadingStory.value = id;\n\n    if ( loadedStories.value ) {\n\n      modalRef.current?.show( id );\n\n    }\n\n  };\n\n  const onLoad = () => {\n\n    loadingStory.value = undefined;\n\n  };\n\n  const onStoriesChange = async () => {\n\n    seenStories.value = await ( saveProgress ? getProgressStorage() : {} );\n\n    const promises = stories.map( ( story ) => {\n\n      const seenStoryIndex = story.stories.findIndex(\n        ( item ) => item.id === seenStories.value[story.id],\n      );\n      const seenStory = story.stories[seenStoryIndex + 1] || story.stories[0];\n\n      if ( !seenStory ) {\n\n        return true;\n\n      }\n\n      return seenStory.mediaType !== 'video' && ( seenStory.source as any )?.uri ? Image.prefetch( ( seenStory.source as any )?.uri ) : true;\n\n    } );\n\n    await Promise.all( promises );\n\n    loadedStories.value = true;\n\n    if ( loadingStory.value ) {\n\n      onPress( loadingStory.value );\n\n    }\n\n  };\n\n  const onSeenStoriesChange = async ( user: string, value: string ) => {\n\n    if ( !saveProgress ) {\n\n      return;\n\n    }\n\n    if ( seenStories.value[user] ) {\n\n      const userData = data.find( ( story ) => story.id === user );\n      const oldIndex = userData?.stories.findIndex(\n        ( story ) => story.id === seenStories.value[user],\n      );\n      const newIndex = userData?.stories.findIndex( ( story ) => story.id === value );\n\n      if ( oldIndex! > newIndex! ) {\n\n        return;\n\n      }\n\n    }\n\n    seenStories.value = await setProgressStorage( user, value );\n\n  };\n\n  useImperativeHandle(\n    ref,\n    () => ( {\n      spliceStories: ( newStories, index ) => {\n\n        if ( index === undefined ) {\n\n          setData( [ ...data, ...newStories ] );\n\n        } else {\n\n          const newData = [ ...data ];\n          newData.splice( index, 0, ...newStories );\n          setData( newData );\n\n        }\n\n      },\n      spliceUserStories: ( newStories, user, index ) => {\n\n        const userData = data.find( ( story ) => story.id === user );\n\n        if ( !userData ) {\n\n          return;\n\n        }\n\n        const newData = index === undefined\n          ? [ ...userData.stories, ...newStories ]\n          : [ ...userData.stories ];\n\n        if ( index !== undefined ) {\n\n          newData.splice( index, 0, ...newStories );\n\n        }\n\n        setData( data.map( ( value ) => ( value.id === user ? {\n          ...value,\n          stories: newData,\n        } : value ) ) );\n\n      },\n      setStories: ( newStories ) => {\n\n        setData( newStories );\n\n      },\n      clearProgressStorage,\n      goToSpecificStory: ( userId, index ) => modalRef.current?.goToSpecificStory( userId, index ),\n      hide: () => modalRef.current?.hide(),\n      show: ( id ) => {\n\n        if ( id ) {\n\n          onPress( id );\n\n        } else if ( data[0]?.id ) {\n\n          onPress( data[0]?.id );\n\n        }\n\n      },\n      pause: () => modalRef.current?.pause()!,\n      resume: () => modalRef.current?.resume()!,\n      isPaused: () => modalRef.current?.isPaused()!,\n      goToPreviousStory: () => modalRef.current?.goToPreviousStory()!,\n      goToNextStory: () => modalRef.current?.goToNextStory()!,\n      getCurrentStory: () => modalRef.current?.getCurrentStory()!,\n    } ),\n    [ data ],\n  );\n\n  useEffect( () => {\n\n    onStoriesChange();\n\n  }, [ data ] );\n\n  useEffect( () => {\n\n    setData( stories );\n\n  }, [ stories ] );\n\n  useEffect( () => {\n\n    if ( isVisible && data[0]?.id ) {\n\n      modalRef.current?.show( data[0]?.id );\n\n    } else {\n\n      modalRef.current?.hide();\n\n    }\n\n  }, [ isVisible ] );\n\n  return (\n    <>\n      {!hideAvatarList && (\n        <StoryAvatarList\n          stories={data}\n          loadingStory={loadingStory}\n          seenStories={seenStories}\n          colors={avatarBorderColors}\n          seenColors={avatarSeenBorderColors}\n          size={avatarSize}\n          showName={showName}\n          nameTextStyle={nameTextStyle}\n          nameTextProps={nameTextProps}\n          avatarListContainerProps={avatarListContainerProps}\n          avatarListContainerStyle={avatarListContainerStyle}\n          avatarBorderRadius={avatarBorderRadius}\n          onPress={onPress}\n        />\n      )}\n      {/* @ts-expect-error: imageProps type mismatch is intentionally ignored */}\n      <StoryModal\n        ref={modalRef}\n        stories={data}\n        seenStories={seenStories}\n        duration={animationDuration}\n        storyAvatarSize={storyAvatarSize}\n        onLoad={onLoad}\n        onSeenStoriesChange={onSeenStoriesChange}\n        backgroundColor={backgroundColor}\n        videoDuration={videoAnimationMaxDuration}\n        videoProps={videoProps}\n        closeIconColor={closeIconColor}\n        loaderColor={loaderColor}\n        loaderBackgroundColor={loaderBackgroundColor}\n        {...props}\n      />\n    </>\n  );\n\n} );\n\nexport default memo( InstagramStories );\n"
  },
  {
    "path": "src/components/List/List.styles.ts",
    "content": "import { StyleSheet } from 'react-native';\nimport { WIDTH } from '../../core/constants';\n\nexport default StyleSheet.create( {\n  container: {\n    borderRadius: 8,\n    overflow: 'hidden',\n    width: WIDTH,\n  },\n  content: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    bottom: 0,\n    right: 0,\n    zIndex: 3,\n  },\n} );\n"
  },
  {
    "path": "src/components/List/index.tsx",
    "content": "import React, { FC, memo, useState } from 'react';\nimport Animated, {\n  runOnJS, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming,\n} from 'react-native-reanimated';\nimport StoryAnimation from '../Animation';\nimport ListStyles from './List.styles';\nimport StoryImage from '../Image';\nimport Progress from '../Progress';\nimport StoryHeader from '../Header';\nimport { StoryListProps } from '../../core/dto/componentsDTO';\nimport { HEIGHT } from '../../core/constants';\nimport StoryContent from '../Content';\nimport StoryFooter from '../Footer';\n\nconst StoryList: FC<StoryListProps> = ( {\n  id, stories, index, x, activeUser, activeStory, progress, seenStories, paused,\n  onLoad, videoProps, progressColor, progressActiveColor, mediaContainerStyle, imageStyles,\n  imageProps, progressContainerStyle, imageOverlayView, hideElements, hideOverlayViewOnLongPress,\n  videoDuration, loaderColor, loaderBackgroundColor, ...props\n} ) => {\n\n  const imageHeight = useSharedValue( HEIGHT );\n  const isActive = useDerivedValue( () => activeUser.value === id );\n\n  const activeStoryIndex = useDerivedValue(\n    () => stories.findIndex( ( item ) => item.id === activeStory.value ),\n  );\n\n  const animatedStyles = useAnimatedStyle( () => ( { height: imageHeight.value } ) );\n  const contentStyles = useAnimatedStyle( () => ( {\n    opacity: withTiming( hideElements.value ? 0 : 1 ),\n  } ) );\n\n  const onImageLayout = ( height: number ) => {\n\n    imageHeight.value = height;\n\n  };\n\n  const [ lastSeenId, setLastSeenId ] = useState<string | undefined>( undefined );\n\n  useAnimatedReaction(\n    () => seenStories.value[id],\n    ( res, prev ) => {\n      if ( res !== prev ) {\n        runOnJS( setLastSeenId )( res );\n      }\n    },\n  );\n\n  const lastSeenIndex = lastSeenId !== undefined\n    ? stories.findIndex( ( item ) => item.id === lastSeenId )\n    : -1;\n\n  return (\n    <StoryAnimation x={x} index={index}>\n      <Animated.View style={[ animatedStyles, ListStyles.container ]}>\n        <StoryImage\n          stories={stories}\n          activeStory={activeStory}\n          defaultStory={stories[lastSeenIndex + 1] ?? stories[0]}\n          isDefaultVideo={( stories[lastSeenIndex + 1]?.mediaType ?? stories[0]?.mediaType ) === 'video'}\n          onImageLayout={onImageLayout}\n          onLoad={onLoad}\n          paused={paused}\n          isActive={isActive}\n          videoProps={videoProps}\n          mediaContainerStyle={mediaContainerStyle}\n          imageStyles={imageStyles}\n          imageProps={imageProps}\n          videoDuration={videoDuration}\n          loaderColor={loaderColor}\n          loaderBackgroundColor={loaderBackgroundColor}\n        />\n        <Animated.View\n          style={[\n            hideOverlayViewOnLongPress ? contentStyles : {},\n            ListStyles.content,\n          ]}\n          pointerEvents=\"auto\"\n        >\n          {imageOverlayView}\n          <Animated.View style={[ contentStyles, ListStyles.content ]} pointerEvents=\"box-none\">\n            <Progress\n              active={isActive}\n              activeStory={activeStoryIndex}\n              progress={progress}\n              length={stories.length}\n              progressColor={progressColor}\n              progressActiveColor={progressActiveColor}\n              progressContainerStyle={progressContainerStyle}\n            />\n            <StoryHeader {...props} />\n            <StoryContent stories={stories} active={isActive} activeStory={activeStory} />\n          </Animated.View>\n        </Animated.View>\n      </Animated.View>\n      <StoryFooter stories={stories} active={isActive} activeStory={activeStory} />\n    </StoryAnimation>\n  );\n\n};\n\nexport default memo( StoryList );\n"
  },
  {
    "path": "src/components/Loader/index.tsx",
    "content": "import React, {\n  FC, memo, useMemo, useState,\n} from 'react';\nimport Animated, {\n  cancelAnimation, interpolate, runOnJS, useAnimatedProps, useAnimatedReaction, useAnimatedStyle,\n  useSharedValue, withRepeat, withTiming,\n} from 'react-native-reanimated';\nimport {\n  Circle, Defs, LinearGradient, Stop, Svg,\n} from 'react-native-svg';\nimport {\n  AVATAR_SIZE, LOADER_COLORS, LOADER_ID, LOADER_URL, STROKE_WIDTH,\n} from '../../core/constants';\nimport { StoryLoaderProps } from '../../core/dto/componentsDTO';\n\nconst AnimatedCircle = Animated.createAnimatedComponent( Circle );\nconst AnimatedSvg = Animated.createAnimatedComponent( Svg );\n\nconst Loader: FC<StoryLoaderProps> = ( {\n  loading, color, size = AVATAR_SIZE + 10,\n} ) => {\n\n  const RADIUS = useMemo( () => ( size - STROKE_WIDTH ) / 2, [ size ] );\n  const CIRCUMFERENCE = useMemo( () => RADIUS * 2 * Math.PI, [ RADIUS ] );\n\n  const [ colors, setColors ] = useState<string[]>( LOADER_COLORS );\n\n  const rotation = useSharedValue( 0 );\n  const progress = useSharedValue( 0 );\n\n  const animatedProps = useAnimatedProps( () => ( {\n    strokeDashoffset: interpolate( progress.value, [ 0, 1 ], [ 0, CIRCUMFERENCE * 2 / 3 ] ),\n  } ) );\n  const animatedStyles = useAnimatedStyle( () => ( {\n    transform: [ { rotate: `${rotation.value}deg` } ],\n  } ) );\n\n  const startAnimation = () => {\n\n    'worklet';\n\n    progress.value = withRepeat( withTiming( 1, { duration: 3000 } ), -1, true );\n    rotation.value = withRepeat( withTiming( 720, { duration: 3000 } ), -1, false, () => {\n\n      rotation.value = 0;\n\n    } );\n\n  };\n\n  const stopAnimation = () => {\n\n    'worklet';\n\n    cancelAnimation( progress );\n    progress.value = withTiming( 0 );\n\n    cancelAnimation( rotation );\n    rotation.value = withTiming( 0 );\n\n  };\n\n  const onColorChange = ( newColors: string[] ) => {\n\n    'worklet';\n\n    if ( JSON.stringify( colors ) === JSON.stringify( newColors ) ) {\n\n      return;\n\n    }\n\n    runOnJS( setColors )( newColors );\n\n  };\n\n  useAnimatedReaction(\n    () => loading.value,\n    ( res ) => ( res ? startAnimation() : stopAnimation() ),\n  );\n  useAnimatedReaction(\n    () => color.value,\n    ( res ) => onColorChange( res ),\n  );\n\n  return (\n    <AnimatedSvg width={size} height={size} style={animatedStyles}>\n      <Defs>\n        <LinearGradient id={LOADER_ID} x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n          {colors?.map( ( item, i ) => (\n            <Stop key={item} offset={i / colors.length} stopColor={item} />\n          ) )}\n        </LinearGradient>\n      </Defs>\n      <AnimatedCircle\n        cx={size / 2}\n        cy={size / 2}\n        r={RADIUS}\n        fill=\"none\"\n        stroke={LOADER_URL}\n        strokeWidth={STROKE_WIDTH}\n        strokeLinecap=\"round\"\n        strokeDasharray={[ CIRCUMFERENCE ]}\n        animatedProps={animatedProps}\n      />\n    </AnimatedSvg>\n  );\n\n};\n\nexport default memo( Loader );\n"
  },
  {
    "path": "src/components/Modal/Modal.styles.ts",
    "content": "import { StyleSheet } from 'react-native';\nimport { HEIGHT, WIDTH } from '../../core/constants';\n\nexport default StyleSheet.create( {\n  container: {\n    flex: 1,\n  },\n  absolute: {\n    position: 'absolute',\n    top: 0,\n    left: 0,\n    width: WIDTH,\n    height: HEIGHT,\n  },\n  bgAnimation: StyleSheet.absoluteFillObject,\n} );\n"
  },
  {
    "path": "src/components/Modal/gesture.tsx",
    "content": "import React, { memo } from 'react';\nimport { PanGestureHandler, PanGestureHandlerProps, gestureHandlerRootHOC } from 'react-native-gesture-handler';\n\nconst GestureHandler = gestureHandlerRootHOC(\n  ( { children, onGestureEvent } : PanGestureHandlerProps ) => (\n    <PanGestureHandler onGestureEvent={onGestureEvent}>{children}</PanGestureHandler>\n  ),\n);\n\nexport default memo( GestureHandler );\n"
  },
  {
    "path": "src/components/Modal/index.tsx",
    "content": "import React, {\n  forwardRef, memo, useEffect, useImperativeHandle, useState,\n} from 'react';\nimport { GestureResponderEvent, Modal, Pressable } from 'react-native';\nimport { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';\nimport Animated, {\n  cancelAnimation, interpolate, runOnJS, useAnimatedReaction,\n  useAnimatedStyle,\n  useDerivedValue, useSharedValue, withTiming,\n} from 'react-native-reanimated';\nimport {\n  HEIGHT, LONG_PRESS_DURATION, STORY_ANIMATION_DURATION, WIDTH,\n} from '../../core/constants';\nimport { GestureContext, StoryModalProps, StoryModalPublicMethods } from '../../core/dto/componentsDTO';\nimport StoryList from '../List';\nimport ModalStyles from './Modal.styles';\n\nconst StoryModal = forwardRef<StoryModalPublicMethods, StoryModalProps>( ( {\n  stories, seenStories, duration, videoDuration, storyAvatarSize, textStyle, containerStyle,\n  backgroundColor, videoProps, closeIconColor, modalAnimationDuration = STORY_ANIMATION_DURATION,\n  storyAnimationDuration = STORY_ANIMATION_DURATION, hideElementsOnLongPress, loopingStories = 'none',\n  statusBarTranslucent, loaderColor, loaderBackgroundColor, onLoad, onShow, onHide,\n  onSeenStoriesChange, onSwipeUp, onStoryStart, onStoryEnd, footerComponent, ...props\n}, ref ) => {\n\n  const [ visible, setVisible ] = useState( false );\n\n  const x = useSharedValue( 0 );\n  const y = useSharedValue( HEIGHT );\n  const animation = useSharedValue( 0 );\n  const currentStory = useSharedValue( stories[0]?.stories[0]?.id );\n  const paused = useSharedValue( false );\n  const durationValue = useSharedValue( duration );\n  const isLongPress = useSharedValue( false );\n  const hideElements = useSharedValue( false );\n  const lastViewed = useSharedValue<{ [key: string]:number }>( {} );\n  const firstRender = useSharedValue( true );\n\n  const userIndex = useDerivedValue( () => Math.round( x.value / WIDTH ) );\n  const storyIndex = useDerivedValue( () => stories[userIndex.value]?.stories.findIndex(\n    ( story ) => story.id === currentStory.value,\n  ) );\n  const userId = useDerivedValue( () => stories[userIndex.value]?.id );\n  const previousUserId = useDerivedValue( () => stories[userIndex.value - 1]?.id );\n  const nextUserId = useDerivedValue( () => stories[userIndex.value + 1]?.id );\n  const previousStory = useDerivedValue( () => ( storyIndex.value !== undefined\n    ? stories[userIndex.value]?.stories[storyIndex.value - 1]?.id\n    : undefined ) );\n  const nextStory = useDerivedValue( () => ( storyIndex.value !== undefined\n    ? stories[userIndex.value]?.stories[storyIndex.value + 1]?.id\n    : undefined ) );\n\n  const animatedStyles = useAnimatedStyle( () => ( { top: y.value } ) );\n  const backgroundAnimatedStyles = useAnimatedStyle( () => ( {\n    opacity: interpolate( y.value, [ 0, HEIGHT ], [ 1, 0 ] ),\n    backgroundColor,\n  } ) );\n\n  const gestureContext = useSharedValue<GestureContext>( {\n    x: 0,\n    pressedX: 0,\n    pressedAt: 0,\n    moving: false,\n    vertical: false,\n    userId: undefined,\n  } );\n\n  const onClose = () => {\n\n    'worklet';\n\n    y.value = withTiming(\n      HEIGHT,\n      { duration: modalAnimationDuration },\n      () => runOnJS( setVisible )( false ),\n    );\n    lastViewed.value = {};\n    cancelAnimation( animation );\n\n  };\n\n  const stopAnimation = () => {\n\n    'worklet';\n\n    cancelAnimation( animation );\n\n  };\n\n  const startAnimation = ( resume = false, newDuration?: number ) => {\n\n    'worklet';\n\n    if ( newDuration ) {\n\n      durationValue.value = newDuration;\n\n    } else {\n\n      newDuration = durationValue.value;\n\n    }\n\n    if ( resume ) {\n\n      newDuration -= animation.value * newDuration;\n\n    } else {\n\n      animation.value = 0;\n\n      if ( userId.value !== undefined && currentStory.value !== undefined ) {\n\n        runOnJS( onSeenStoriesChange )( userId.value, currentStory.value );\n\n      }\n\n      if ( userId.value !== undefined && storyIndex.value! >= 0 ) {\n\n        lastViewed.value = { ...lastViewed.value, [userId.value]: storyIndex.value ?? 0 };\n\n      }\n\n    }\n\n    animation.value = withTiming( 1, { duration: newDuration } );\n\n  };\n\n  const scrollTo = (\n    id: string,\n    animated = true,\n    sameUser = false,\n    previousUser?: string,\n    index?: number,\n  ) => {\n\n    'worklet';\n\n    const newUserIndex = stories.findIndex( ( story ) => story.id === id );\n    const newX = newUserIndex * WIDTH;\n\n    x.value = animated ? withTiming( newX, { duration: storyAnimationDuration } ) : newX;\n\n    if ( sameUser ) {\n\n      startAnimation( true );\n\n      return;\n\n    }\n\n    if ( onStoryEnd && animated ) {\n\n      runOnJS( onStoryEnd )( previousUser ?? userId.value, currentStory.value );\n\n    }\n\n    const newStoryIndex = lastViewed.value[id] !== undefined\n      ? lastViewed.value[id]!\n      : ( ( stories[newUserIndex]?.stories.findIndex(\n        ( story ) => story.id === seenStories.value[id],\n      ) ?? 0 ) + 1 );\n    const userStories = stories[newUserIndex]?.stories;\n    const newStory = userStories?.[index ?? newStoryIndex]?.id ?? userStories?.[0]?.id;\n    currentStory.value = newStory;\n\n    if ( onStoryStart ) {\n\n      runOnJS( onStoryStart )( id, newStory );\n\n    }\n\n  };\n\n  const toNextStory = ( value = true ) => {\n\n    'worklet';\n\n    if ( !value ) {\n\n      return;\n\n    }\n\n    if ( !nextStory.value ) {\n\n      if ( nextUserId.value ) {\n\n        scrollTo( nextUserId.value );\n\n      } else if ( stories[0]?.id && loopingStories === 'all' ) {\n\n        scrollTo( stories[0].id, false );\n\n      } else if ( userId.value && loopingStories === 'onlyLast' ) {\n\n        scrollTo( userId.value, false, undefined, undefined, 0 );\n\n      } else {\n\n        onClose();\n\n      }\n\n    } else {\n\n      if ( onStoryEnd ) {\n\n        runOnJS( onStoryEnd )( userId.value, currentStory.value );\n\n      }\n\n      if ( onStoryStart ) {\n\n        runOnJS( onStoryStart )( userId.value, nextStory.value );\n\n      }\n\n      animation.value = 0;\n      currentStory.value = nextStory.value;\n\n    }\n\n  };\n\n  const toPreviousStory = () => {\n\n    'worklet';\n\n    if ( !previousStory.value ) {\n\n      if ( previousUserId.value ) {\n\n        scrollTo( previousUserId.value );\n\n      } else {\n\n        return false;\n\n      }\n\n    } else {\n\n      if ( onStoryEnd ) {\n\n        runOnJS( onStoryEnd )( userId.value, currentStory.value );\n\n      }\n\n      if ( onStoryStart ) {\n\n        runOnJS( onStoryStart )( userId.value, previousStory.value );\n\n      }\n\n      animation.value = 0;\n      currentStory.value = previousStory.value;\n\n    }\n\n    return true;\n\n  };\n\n  const show = ( id: string ) => {\n\n    setVisible( true );\n    scrollTo( id, false );\n\n  };\n\n  const onGestureEvent = Gesture.Pan()\n    .onStart ( _ => {\n\n      gestureContext.value.x = x.value;\n      gestureContext.value.userId = userId.value;\n      paused.value = true;\n\n    } )\n    .onUpdate ( e => {\n\n      if ( gestureContext.value.x === x.value\n        && ( gestureContext.value.vertical || ( Math.abs( e.velocityX ) < Math.abs( e.velocityY ) ) ) ) {\n\n        gestureContext.value.vertical = true;\n        y.value = e.translationY / 2;\n\n      } else {\n\n        gestureContext.value.moving = true;\n        x.value = Math.max(\n          0,\n          Math.min( gestureContext.value.x + -e.translationX, WIDTH * ( stories.length - 1 ) ),\n        );\n\n      }\n\n    } )\n    .onFinalize ( e => {\n\n      if ( gestureContext.value.vertical ) {\n\n        if ( e.translationY > 100 ) {\n\n          onClose();\n\n        } else {\n\n          if ( e.translationY < -100 && onSwipeUp ) {\n\n            runOnJS( onSwipeUp )(\n              stories[userIndex.value]?.id,\n              stories[userIndex.value]?.stories[storyIndex.value ?? 0]?.id,\n            );\n\n          }\n\n          y.value = withTiming( 0 );\n          startAnimation( true );\n\n        }\n\n      } else if ( gestureContext.value.moving ) {\n\n        const diff = x.value - gestureContext.value.x;\n        let newX;\n\n        if ( Math.abs( diff ) < WIDTH / 4 ) {\n\n          newX = gestureContext.value.x;\n\n        } else {\n\n          newX = diff > 0\n            ? Math.ceil( x.value / WIDTH ) * WIDTH\n            : Math.floor( x.value / WIDTH ) * WIDTH;\n\n        }\n\n        const newUserId = stories[Math.round( newX / WIDTH )]?.id;\n        if ( newUserId !== undefined ) {\n\n          scrollTo(\n            newUserId,\n            true,\n            newUserId === gestureContext.value.userId,\n            gestureContext.value.userId,\n          );\n\n        }\n\n      }\n\n      gestureContext.value.moving = false;\n      gestureContext.value.vertical = false;\n      gestureContext.value.userId = undefined;\n      hideElements.value = false;\n      paused.value = false;\n\n    } );\n\n  const onPressIn = () => {\n\n    stopAnimation();\n    paused.value = true;\n\n  };\n\n  const onLongPress = () => {\n\n    isLongPress.value = true;\n    hideElements.value = hideElementsOnLongPress ?? false;\n\n  };\n\n  const onPressOut = () => {\n\n    if ( !isLongPress.value ) {\n\n      return;\n\n    }\n\n    hideElements.value = false;\n    isLongPress.value = false;\n    paused.value = false;\n    startAnimation( true );\n\n  };\n\n  const onPress = ( { nativeEvent: { locationX } }: GestureResponderEvent ) => {\n\n    hideElements.value = false;\n\n    if ( isLongPress.value ) {\n\n      onPressOut();\n\n      return;\n\n    }\n\n    if ( locationX < WIDTH / 2 ) {\n\n      const success = toPreviousStory();\n\n      if ( !success ) {\n\n        startAnimation( true );\n\n      }\n\n    } else {\n\n      toNextStory();\n\n    }\n\n    paused.value = false;\n\n  };\n\n  useImperativeHandle( ref, () => ( {\n    show,\n    hide: onClose,\n    pause: () => {\n\n      stopAnimation();\n      paused.value = true;\n\n    },\n    resume: () => {\n\n      startAnimation( true );\n      paused.value = false;\n\n    },\n    isPaused: () => paused.value,\n    getCurrentStory: () => ( { userId: userId.value, storyId: currentStory.value } ),\n    goToPreviousStory: toPreviousStory,\n    goToNextStory: toNextStory,\n    goToSpecificStory: ( newUserId, index ) => scrollTo( newUserId, true, false, undefined, index ),\n  } ) );\n\n  useEffect( () => {\n\n    if ( visible ) {\n\n      if ( currentStory.value !== undefined ) {\n\n        onShow?.( currentStory.value );\n\n      }\n      onLoad?.();\n\n      y.value = withTiming( 0, { duration: modalAnimationDuration } );\n\n    } else if ( currentStory.value !== undefined && !firstRender.value ) {\n\n      onHide?.( currentStory.value );\n\n    }\n\n    firstRender.value = false;\n\n  }, [ visible ] );\n\n  useAnimatedReaction(\n    () => animation.value,\n    ( res, prev ) => res !== prev && toNextStory( res === 1 ),\n  );\n\n  return (\n    <Modal statusBarTranslucent={statusBarTranslucent} visible={visible} transparent animationType=\"none\" testID=\"storyRNModal\" onRequestClose={onClose}>\n      <GestureHandlerRootView style={{ flex: 1 }}>\n        <GestureDetector gesture={onGestureEvent}>\n          <Animated.View style={ModalStyles.container} testID=\"storyModal\">\n            <Pressable\n              onPressIn={onPressIn}\n              onPress={onPress}\n              onLongPress={onLongPress}\n              onPressOut={onPressOut}\n              delayLongPress={LONG_PRESS_DURATION}\n              style={ModalStyles.container}\n            >\n              <Animated.View style={[ ModalStyles.bgAnimation, backgroundAnimatedStyles ]} />\n              <Animated.View style={[ ModalStyles.absolute, animatedStyles, containerStyle ]}>\n                {stories?.map( ( story, index ) => (\n                  <StoryList\n                    {...story}\n                    index={index}\n                    x={x}\n                    activeUser={userId}\n                    activeStory={currentStory}\n                    progress={animation}\n                    seenStories={seenStories}\n                    onClose={onClose}\n                    onLoad={( value ) => {\n\n                      onLoad?.();\n                      startAnimation(\n                        undefined,\n                        value !== undefined ? value : duration,\n                      );\n\n                    }}\n                    avatarSize={storyAvatarSize}\n                    textStyle={textStyle}\n                    paused={paused}\n                    videoProps={videoProps}\n                    closeColor={closeIconColor}\n                    hideElements={hideElements}\n                    videoDuration={videoDuration}\n                    loaderColor={loaderColor}\n                    loaderBackgroundColor={loaderBackgroundColor}\n                    key={story.id}\n                    {...props}\n                  />\n                ) )}\n              </Animated.View>\n            </Pressable>\n            {footerComponent && footerComponent}\n          </Animated.View>\n        </GestureDetector>\n      </GestureHandlerRootView>\n    </Modal>\n  );\n\n} );\n\nexport default memo( StoryModal );\n"
  },
  {
    "path": "src/components/Progress/Progress.styles.ts",
    "content": "import { StyleSheet } from 'react-native';\n\nexport default StyleSheet.create( {\n  container: {\n    position: 'absolute',\n    top: 16,\n    left: 16,\n    height: 2,\n    flexDirection: 'row',\n    gap: 4,\n  },\n  item: {\n    height: 3,\n    borderRadius: 8,\n    overflow: 'hidden',\n  },\n} );\n"
  },
  {
    "path": "src/components/Progress/index.tsx",
    "content": "import React, { FC, memo } from 'react';\nimport { View } from 'react-native';\nimport ProgressItem from './item';\nimport { WIDTH } from '../../core/constants';\nimport ProgressStyles from './Progress.styles';\nimport { StoryProgressProps } from '../../core/dto/componentsDTO';\n\nconst Progress: FC<StoryProgressProps> = ( {\n  progress, active, activeStory, length,\n  progressActiveColor, progressColor, progressContainerStyle,\n} ) => {\n\n  const width = ( (\n    WIDTH - ProgressStyles.container.left * 2 ) - ( length - 1 )\n    * ProgressStyles.container.gap ) / length;\n\n  return (\n    <View style={[ ProgressStyles.container, progressContainerStyle, { width: WIDTH } ]}>\n      {[ ...Array( length ).keys() ].map( ( val ) => (\n        <ProgressItem\n          active={active}\n          activeStory={activeStory}\n          progress={progress}\n          index={val}\n          width={width}\n          key={val}\n          progressActiveColor={progressActiveColor}\n          progressColor={progressColor}\n        />\n      ) )}\n    </View>\n  );\n\n};\n\nexport default memo( Progress );\n"
  },
  {
    "path": "src/components/Progress/item.tsx",
    "content": "import React, { FC, memo } from 'react';\nimport { View } from 'react-native';\nimport Animated, { useAnimatedStyle } from 'react-native-reanimated';\nimport { StoryProgressItemProps } from '../../core/dto/componentsDTO';\nimport ProgressStyles from './Progress.styles';\nimport { PROGRESS_ACTIVE_COLOR, PROGRESS_COLOR } from '../../core/constants';\n\nconst AnimatedView = Animated.createAnimatedComponent( View );\n\nconst ProgressItem: FC<StoryProgressItemProps> = ( {\n  progress, active, activeStory, index, width,\n  progressActiveColor = PROGRESS_ACTIVE_COLOR, progressColor = PROGRESS_COLOR,\n} ) => {\n\n  const animatedStyle = useAnimatedStyle( () => {\n\n    if ( !active.value || activeStory.value < index ) {\n\n      return { width: 0 };\n\n    }\n\n    if ( activeStory.value > index ) {\n\n      return { width };\n\n    }\n\n    return { width: width * progress.value };\n\n  } );\n\n  return (\n    <View style={[ ProgressStyles.item, { backgroundColor: progressColor }, { width } ]}>\n      <AnimatedView\n        style={[ ProgressStyles.item, { backgroundColor: progressActiveColor }, animatedStyle ]}\n      />\n    </View>\n  );\n\n};\n\nexport default memo( ProgressItem );\n"
  },
  {
    "path": "src/core/constants/index.ts",
    "content": "import { Dimensions } from 'react-native';\n\nexport const { width: WIDTH, height: HEIGHT } = Dimensions.get( 'screen' );\n\nexport const STORAGE_KEY = '@birdwingo/react-native-instagram-stories';\n\nexport const DEFAULT_COLORS = [ '#F7B801', '#F18701', '#F35B04', '#F5301E', '#C81D4E', '#8F1D4E' ];\nexport const LOADER_COLORS = [ '#FFF' ];\nexport const SEEN_LOADER_COLORS = [ '#2A2A2C' ];\nexport const PROGRESS_COLOR = '#00000099';\nexport const PROGRESS_ACTIVE_COLOR = '#FFFFFF';\nexport const BACKGROUND_COLOR = '#000000';\nexport const CLOSE_COLOR = '#00000099';\n\nexport const LOADER_ID = 'gradient';\nexport const LOADER_URL = `url(#${LOADER_ID})`;\n\nexport const STROKE_WIDTH = 2;\n\nexport const AVATAR_SIZE = 60;\nexport const AVATAR_OFFSET = 5;\nexport const STORY_AVATAR_SIZE = 26;\n\nexport const STORY_ANIMATION_DURATION = 800;\nexport const ANIMATION_DURATION = 10000;\nexport const LONG_PRESS_DURATION = 500;\n"
  },
  {
    "path": "src/core/dto/componentsDTO.ts",
    "content": "import { SharedValue } from 'react-native-reanimated';\nimport {\n  ImageProps, ImageStyle, TextProps, TextStyle, ViewStyle,\n} from 'react-native';\nimport { ReactNode } from 'react';\nimport { InstagramStoriesProps, InstagramStoryProps, StoryItemProps } from './instagramStoriesDTO';\nimport { ProgressStorageProps } from './helpersDTO';\n\nexport interface StoryAvatarListProps {\n  stories: InstagramStoryProps[];\n  loadingStory: StoryAvatarProps['loadingStory'];\n  seenStories: StoryAvatarProps['seenStories'];\n  colors: StoryAvatarProps['colors'];\n  seenColors: StoryAvatarProps['seenColors'];\n  size: StoryAvatarProps['size'];\n  showName: InstagramStoriesProps['showName'];\n  nameTextStyle: InstagramStoriesProps['nameTextStyle'];\n  nameTextProps: InstagramStoriesProps['nameTextProps'];\n  avatarListContainerStyle: InstagramStoriesProps['avatarListContainerStyle'];\n  avatarListContainerProps: InstagramStoriesProps['avatarListContainerProps'];\n  avatarBorderRadius?: number;\n  onPress: ( id: string ) => void;\n}\n\nexport interface StoryAvatarProps extends InstagramStoryProps {\n  loadingStory: SharedValue<string | undefined>;\n  seenStories: SharedValue<ProgressStorageProps>;\n  onPress: () => void;\n  colors: string[];\n  seenColors: string[];\n  size: number;\n  showName?: boolean;\n  nameTextStyle?: TextStyle;\n  nameTextProps?: TextProps;\n  avatarBorderRadius?: number;\n}\n\nexport interface StoryLoaderProps {\n  loading: SharedValue<boolean>;\n  color: SharedValue<string[]>;\n  size?: number;\n}\n\nexport interface StoryModalProps {\n  stories: InstagramStoryProps[];\n  seenStories: SharedValue<ProgressStorageProps>;\n  duration: number;\n  videoDuration?: number;\n  storyAvatarSize: number;\n  textStyle?: TextStyle;\n  containerStyle?: ViewStyle;\n  backgroundColor?: string;\n  videoProps?: any;\n  closeIconColor: string;\n  progressActiveColor?: string;\n  progressColor?: string;\n  modalAnimationDuration?: number;\n  storyAnimationDuration?: number;\n  mediaContainerStyle?: ViewStyle;\n  imageStyles?: ImageStyle;\n  imageProps?: ImageProps;\n  footerComponent?: ReactNode;\n  hideElementsOnLongPress?: boolean;\n  hideOverlayViewOnLongPress?: boolean;\n  loopingStories?: 'none' | 'all' | 'onlyLast';\n  statusBarTranslucent?: boolean;\n  loaderColor?: string;\n  loaderBackgroundColor?: string;\n  onLoad: () => void;\n  onShow?: ( id: string ) => void;\n  onHide?: ( id: string ) => void;\n  onSeenStoriesChange: ( user: string, value: string ) => void;\n  onSwipeUp?: ( userId?: string, storyId?: string ) => void;\n  onStoryStart?: ( userId?: string, storyId?: string ) => void;\n  onStoryEnd?: ( userId?: string, storyId?: string ) => void;\n}\n\nexport type StoryModalPublicMethods = {\n  show: ( id: string ) => void;\n  hide: () => void;\n  pause: () => void;\n  resume: () => void;\n  isPaused: () => boolean;\n  goToPreviousStory: () => void;\n  goToNextStory: () => void;\n  getCurrentStory: () => { userId?: string, storyId?: string };\n  goToSpecificStory: ( userId: string, index?: number ) => void;\n};\n\nexport type GestureContext = {\n  x: number,\n  pressedX: number,\n  pressedAt: number,\n  moving: boolean,\n  vertical: boolean,\n  userId?: string,\n};\n\nexport interface AnimationProps {\n  children: React.ReactNode;\n  x: SharedValue<number>;\n  index: number;\n}\n\nexport interface StoryImageProps {\n  stories: InstagramStoryProps['stories'];\n  activeStory: SharedValue<string | undefined>;\n  defaultStory?: StoryItemProps;\n  isDefaultVideo: boolean;\n  paused: SharedValue<boolean>;\n  videoProps?: any;\n  mediaContainerStyle?: ViewStyle;\n  isActive: SharedValue<boolean>;\n  imageStyles?: ImageStyle;\n  imageProps?: ImageProps;\n  videoDuration?: number;\n  loaderColor?: string;\n  loaderBackgroundColor?: string;\n  onImageLayout: ( height: number ) => void;\n  onLoad: ( duration?: number ) => void;\n}\n\nexport interface StoryProgressProps {\n  progress: SharedValue<number>;\n  active: SharedValue<boolean>;\n  activeStory: SharedValue<number>;\n  length: number;\n  progressActiveColor?: string;\n  progressColor?: string;\n  progressContainerStyle?: ViewStyle;\n}\n\nexport interface StoryProgressItemProps extends Omit<StoryProgressProps, 'length'> {\n  index: number;\n  width: number;\n}\n\nexport interface StoryHeaderProps {\n  avatarSource: ImageProps['source'];\n  name?: string;\n  avatarSize: number;\n  textStyle?: TextStyle;\n  closeColor: string;\n  headerStyle?: ViewStyle;\n  headerContainerStyle?: ViewStyle;\n  onClose: () => void;\n  renderStoryHeader?: () => ReactNode;\n  onStoryHeaderPress?: () => void;\n}\n\nexport interface IconProps {\n  color: string;\n}\n\nexport interface StoryContentProps {\n  stories: InstagramStoryProps['stories'];\n  active: SharedValue<boolean>;\n  activeStory: SharedValue<string | undefined>;\n}\n\nexport interface StoryListProps extends InstagramStoryProps, StoryHeaderProps {\n  index: number;\n  x: SharedValue<number>;\n  activeUser: SharedValue<string | undefined>;\n  activeStory: SharedValue<string | undefined>;\n  progress: SharedValue<number>;\n  seenStories: SharedValue<ProgressStorageProps>;\n  paused: SharedValue<boolean>;\n  videoProps?: any;\n  progressActiveColor?: string;\n  progressColor?: string;\n  mediaContainerStyle?: ViewStyle;\n  imageStyles?: ImageStyle;\n  imageProps?: ImageProps;\n  progressContainerStyle?: ViewStyle;\n  imageOverlayView?: ReactNode;\n  hideElements: SharedValue<boolean>;\n  hideOverlayViewOnLongPress?: boolean;\n  videoDuration?: number;\n  loaderColor?: string;\n  loaderBackgroundColor?: string;\n  onLoad: ( duration?: number ) => void;\n}\n\nexport interface StoryVideoProps {\n  source: ImageProps['source'];\n  paused: SharedValue<boolean>;\n  isActive: SharedValue<boolean>;\n  onLoad: ( duration: number ) => void;\n  onLayout: ( height: number ) => void;\n}\n"
  },
  {
    "path": "src/core/dto/helpersDTO.ts",
    "content": "export interface ProgressStorageProps {\n  [key: string]: string;\n}\n"
  },
  {
    "path": "src/core/dto/instagramStoriesDTO.ts",
    "content": "import { ReactNode } from 'react';\nimport {\n  ImageProps,\n  ImageStyle,\n  ScrollViewProps, TextStyle, ViewStyle, TextProps,\n} from 'react-native';\nimport { FlashListProps } from '@shopify/flash-list';\n\nexport interface StoryItemProps {\n  id: string;\n  source: ImageProps['source'];\n  mediaType?: 'image' | 'video';\n  animationDuration?: number;\n  renderContent?: () => ReactNode;\n  renderFooter?: () => ReactNode;\n}\n\nexport interface InstagramStoryProps {\n  id: string;\n  avatarSource: ImageProps['source'];\n  renderAvatar?: ( seen: boolean ) => ReactNode;\n  renderStoryHeader?: () => ReactNode;\n  onStoryHeaderPress?: () => void;\n  name?: string;\n  stories: StoryItemProps[];\n}\n\nexport interface InstagramStoriesProps {\n  stories: InstagramStoryProps[];\n  saveProgress?: boolean;\n  avatarBorderColors?: string[];\n  avatarSeenBorderColors?: string[];\n  avatarSize?: number;\n  storyAvatarSize?: number;\n  avatarListContainerStyle?: ScrollViewProps['contentContainerStyle'];\n  avatarListContainerProps?: ScrollViewProps | Partial<FlashListProps<InstagramStoryProps>>;\n  containerStyle?: ViewStyle;\n  textStyle?: TextStyle;\n  animationDuration?: number;\n  videoAnimationMaxDuration?: number;\n  backgroundColor?: string;\n  showName?: boolean;\n  nameTextStyle?: TextStyle;\n  nameTextProps?: TextProps;\n  videoProps?: any;\n  closeIconColor?: string;\n  progressActiveColor?: string;\n  progressColor?: string;\n  modalAnimationDuration?: number;\n  storyAnimationDuration?: number;\n  mediaContainerStyle?: ViewStyle;\n  imageStyles?: ImageStyle;\n  imageProps?: Omit<ImageProps, 'source'>;\n  isVisible?: boolean;\n  headerStyle?: ViewStyle;\n  headerContainerStyle?: ViewStyle;\n  progressContainerStyle?: ViewStyle;\n  hideAvatarList?: boolean;\n  imageOverlayView?: ReactNode;\n  hideElementsOnLongPress?: boolean;\n  hideOverlayViewOnLongPress?: boolean;\n  loopingStories?: 'none' | 'all' | 'onlyLast';\n  statusBarTranslucent?: boolean;\n  footerComponent?: ReactNode;\n  avatarBorderRadius?: number;\n  loaderColor?: string;\n  loaderBackgroundColor?: string;\n  onShow?: ( id: string ) => void;\n  onHide?: ( id: string ) => void;\n  onSwipeUp?: ( userId?: string, storyId?: string ) => void;\n  onStoryStart?: ( userId?: string, storyId?: string ) => void;\n  onStoryEnd?: ( userId?: string, storyId?: string ) => void;\n}\n\nexport type InstagramStoriesPublicMethods = {\n  spliceStories: ( stories: InstagramStoryProps[], index?: number ) => void;\n  spliceUserStories: ( stories: StoryItemProps[], user: string, index?: number ) => void;\n  setStories: ( stories: InstagramStoryProps[] ) => void;\n  clearProgressStorage: () => void;\n  hide: () => void;\n  show: ( id?: string ) => void;\n  pause: () => void;\n  resume: () => void;\n  goToPreviousStory: () => void;\n  goToNextStory: () => void;\n  getCurrentStory: () => { userId?: string, storyId?: string };\n  goToSpecificStory: ( userId: string, index?: number ) => void;\n};\n"
  },
  {
    "path": "src/core/helpers/storage.ts",
    "content": "/* eslint-disable global-require */\nimport { STORAGE_KEY } from '../constants';\nimport { ProgressStorageProps } from '../dto/helpersDTO';\n\nexport const clearProgressStorage = async () => {\n\n  try {\n\n    const AsyncStorage = require( '@react-native-async-storage/async-storage' ).default;\n\n    return AsyncStorage.removeItem( STORAGE_KEY );\n\n  } catch ( error ) {\n\n    return null;\n\n  }\n\n};\n\nexport const getProgressStorage = async (): Promise<ProgressStorageProps> => {\n\n  try {\n\n    const AsyncStorage = require( '@react-native-async-storage/async-storage' ).default;\n\n    const progress = await AsyncStorage.getItem( STORAGE_KEY );\n\n    return progress ? JSON.parse( progress ) : {};\n\n  } catch ( error ) {\n\n    return {};\n\n  }\n\n};\n\nexport const setProgressStorage = async ( user: string, lastSeen: string ) => {\n\n  const progress = await getProgressStorage();\n  progress[user] = lastSeen;\n\n  try {\n\n    const AsyncStorage = require( '@react-native-async-storage/async-storage' ).default;\n\n    await AsyncStorage.setItem( STORAGE_KEY, JSON.stringify( progress ) );\n\n    return progress;\n\n  } catch ( error ) {\n\n    return {};\n\n  }\n\n};\n"
  },
  {
    "path": "src/declarations.d.ts",
    "content": "declare module '*.png';\ndeclare module '*.gif';\n\ndeclare interface Keyframe {\n  composite?: 'accumulate' | 'add' | 'auto' | 'replace';\n  easing?: string;\n  offset?: number | null;\n  [property: string]: string | number | null | undefined;\n}\n"
  },
  {
    "path": "src/index.tsx",
    "content": "import InstagramStories from './components/InstagramStories';\nimport { InstagramStoriesProps, InstagramStoriesPublicMethods } from './core/dto/instagramStoriesDTO';\n\nexport type { InstagramStoriesProps, InstagramStoriesPublicMethods };\nexport default InstagramStories;\n"
  },
  {
    "path": "tests/index.test.js",
    "content": "import { createRef } from 'react';\nimport { render, fireEvent, act } from '@testing-library/react-native';\nimport * as Reanimated from 'react-native-reanimated';\nimport { Platform, View } from 'react-native';\nimport InstagramStories from '../src';\nimport { WIDTH } from '../src/core/constants';\nimport StoryAvatar from '../src/components/Avatar';\nimport StoryImage from '../src/components/Image';\nimport Loader from '../src/components/Loader';\nimport * as Storage from '../src/core/helpers/storage';\n\nconst reactions = new Map();\n\nconst sleep = async ( ms ) => new Promise( ( resolve ) => setTimeout( resolve, ms ?? 250 ) );\njest.spyOn( Reanimated, 'useAnimatedReaction' ).mockImplementation( ( value, cb ) => {\n\n  if ( reactions.has( `${cb}` ) && reactions.get( `${cb}` ) === value() ) {\n\n    return;\n\n  }\n\n  reactions.set( `${cb}`, value() );\n  cb( value(), '' );\n\n} );\njest.spyOn( Reanimated, 'useSharedValue' ).mockImplementation( ( value ) => ( { value } ) );\njest.spyOn( Storage, 'getProgressStorage' ).mockImplementation( () => ( {} ) );\njest.spyOn( Storage, 'setProgressStorage' ).mockImplementation( () => ( {} ) );\n\nconst stories = [ {\n  id: '1',\n  name: 'John Doe',\n  avatarSource: { uri: 'https://picsum.photos/200/300' },\n  stories: [ {\n    id: '1',\n    source: { uri: 'https://picsum.photos/200/300' },\n    renderContent: () => <View />,\n  } ],\n} ];\n\nconst stories2 = [ {\n  id: '1',\n  name: 'John Doe',\n  avatarSource: { uri: 'https://picsum.photos/200/300' },\n  stories: [ {\n    id: '1',\n    source: { uri: 'https://picsum.photos/200/300' },\n  } ],\n}, {\n  id: '2',\n  name: 'John Doe 2',\n  avatarSource: { uri: 'https://picsum.photos/200/300' },\n  stories: [ {\n    id: '1',\n    source: { uri: 'https://picsum.photos/200/300' },\n  } ],\n} ];\n\nconst stories3 = [ {\n  id: '1',\n  name: 'John Doe',\n  avatarSource: { uri: 'https://picsum.photos/200/300' },\n  stories: [ {\n    id: '1',\n    source: { uri: 'https://picsum.photos/200/300' },\n  }, {\n    id: '2',\n    source: { uri: 'https://picsum.photos/200/300' },\n  } ],\n} ];\n\nconst stories4 = [ {\n  id: '1',\n  name: 'John Doe',\n  avatarSource: { uri: 'https://picsum.photos/200/300' },\n  stories: [ {\n    id: '1',\n    source: { uri: 'https://picsum.photos/200/300' },\n    mediaType: 'video',\n  } ],\n}, {\n  id: '2',\n  name: 'John Doe 2',\n  avatarSource: { uri: 'https://picsum.photos/200/300' },\n  stories: [ {\n    id: '1',\n    source: { uri: 'https://picsum.photos/200/300' },\n    mediaType: 'video',\n  } ],\n} ];\n\ndescribe( 'Instagram Stories test', () => {\n\n  beforeEach( () => {\n\n    reactions.clear();\n\n  } );\n\n  it( 'Should render the stories list', () => {\n\n    const { getByTestId } = render( <InstagramStories stories={stories} /> );\n\n    expect( getByTestId( 'storiesList' ) ).toBeTruthy();\n\n  } );\n\n  it( 'Should open story on press', async () => {\n\n    const { getByTestId } = render( <InstagramStories stories={stories} showName /> );\n\n    const story = getByTestId( '1StoryAvatar1Story' );\n    expect( story ).toBeTruthy();\n\n    await act( async () => {\n\n      fireEvent( story, 'click' );\n      await sleep();\n      expect( getByTestId( 'storyModal' ) ).toBeTruthy();\n\n    } );\n\n  } );\n\n  it( 'Should work with saveProgress', async () => {\n\n    jest.spyOn( Storage, 'getProgressStorage' ).mockImplementation( () => ( { 1: '1' } ) );\n    const { getByTestId, getAllByTestId } = render(\n      <InstagramStories\n        stories={stories2}\n        saveProgress\n      />,\n    );\n\n    await act( async () => {\n\n      fireEvent( getByTestId( '1StoryAvatar1Story' ), 'click' );\n      await sleep();\n\n      getAllByTestId( 'storyAvatarImage' ).forEach( ( element ) => {\n\n        element.props.onLoad();\n\n      } );\n      getAllByTestId( 'storyImageComponent' ).forEach( ( element ) => {\n\n        element.props.onLoad();\n\n      } );\n      getAllByTestId( 'storyImageComponent' ).forEach( ( element ) => {\n\n        element.props.onLayout( { nativeEvent: { layout: { height: 100 } } } );\n\n      } );\n      getAllByTestId( 'storyCloseButton' ).forEach( ( element ) => {\n\n        fireEvent( element.parent, 'onPressIn', { nativeEvent: { locationX: 0, locationY: 0 } } );\n\n      } );\n\n    } );\n\n  } );\n\n  it( 'Should work if new seen story is older than saved', async () => {\n\n    jest.spyOn( Storage, 'getProgressStorage' ).mockImplementation( () => ( { 1: '2' } ) );\n    const { getByTestId } = render(\n      <InstagramStories\n        stories={stories3}\n        saveProgress\n      />,\n    );\n\n    await act( async () => {\n\n      fireEvent( getByTestId( '1StoryAvatar2Story' ), 'click' );\n      await sleep();\n\n    } );\n\n  } );\n\n  it( 'Should work with public methods', async () => {\n\n    const ref = createRef();\n\n    const { getByTestId, queryByTestId } = render(\n      <InstagramStories\n        stories={stories}\n        ref={ref}\n        saveProgress\n      />,\n    );\n\n    await act( async () => {\n\n      fireEvent( getByTestId( '1StoryAvatar1Story' ), 'click' );\n      await sleep();\n      expect( getByTestId( 'storyModal' ) ).toBeTruthy();\n\n    } );\n\n    await act( async () => {\n\n      ref.current.clearProgressStorage();\n\n      ref.current.hide();\n\n      await sleep();\n      expect( queryByTestId( 'storyModal' ) ).toBeFalsy();\n\n      ref.current.spliceStories( [ {\n        id: '2',\n        name: 'John Doe 2',\n        avatarSource: { uri: 'https://picsum.photos/200/300' },\n        stories: [ {\n          id: '1',\n          source: { uri: 'https://picsum.photos/200/300' },\n        } ],\n      } ] );\n\n      await sleep();\n\n      ref.current.spliceStories( [ {\n        id: '3',\n        name: 'John Doe 3',\n        avatarSource: { uri: 'https://picsum.photos/200/300' },\n        stories: [ {\n          id: '1',\n          source: { uri: 'https://picsum.photos/200/300' },\n        } ],\n      } ], -1 );\n\n      await sleep();\n\n      ref.current.spliceUserStories( [ {\n        id: '2',\n        source: { uri: 'https://picsum.photos/200/300' },\n      } ], '1' );\n\n      await sleep();\n\n      ref.current.spliceUserStories( [ {\n        id: '2',\n        source: { uri: 'https://picsum.photos/200/300' },\n      } ], '2', 2 );\n\n      ref.current.spliceUserStories( [ {\n        id: '2',\n        source: { uri: 'https://picsum.photos/200/300' },\n      } ], '20', 2 );\n\n      await sleep();\n\n      expect( getByTestId( '1StoryAvatar2Story' ) ).toBeTruthy();\n      expect( getByTestId( '2StoryAvatar2Story' ) ).toBeTruthy();\n      expect( getByTestId( '3StoryAvatar1Story' ) ).toBeTruthy();\n\n      ref.current.setStories( stories );\n\n      await sleep();\n\n      expect( getByTestId( '1StoryAvatar1Story' ) ).toBeTruthy();\n\n      ref.current.show();\n      ref.current.show('1');\n\n    } );\n\n  } );\n\n  it( 'Should not open if empty array', async () => {\n\n    const ref = createRef();\n\n    const { queryByTestId } = render(\n      <InstagramStories\n        stories={[]}\n        ref={ref}\n        saveProgress\n      />,\n    );\n\n    await act( async () => {\n\n      ref.current.show();\n\n      await sleep();\n\n      expect( queryByTestId( 'storyModal' ) ).toBeFalsy();\n\n    } );\n\n  } );\n\n  it( 'Should work animations', async () => {\n\n    const { getByTestId, queryByTestId } = render( <InstagramStories stories={stories} /> );\n\n    await act( async () => {\n\n      fireEvent( getByTestId( '1StoryAvatar1Story' ), 'click' );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderStart', { x: 0 }, {} );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderMove', {\n        x: 0, velocityX: 0, velocityY: 10, translationY: 10,\n      }, { x: 0 } );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderMove', {\n        x: 0, velocityX: 10, velocityY: 0, translationX: 10,\n      }, { x: 0 } );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', { translationY: 10 }, { vertical: true } );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { moving: true, x: 10 } );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { moving: true, x: 300 } );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { moving: true, x: -300 } );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderStart', { x: 0 } );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderMove', {\n        x: 0, velocityX: 0, velocityY: 10, translationY: 200,\n      } );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', { translationY: 200 } );\n      await sleep();\n\n      expect( queryByTestId( 'gestureContainer' ) ).toBeFalsy();\n\n    } );\n\n  } );\n\n  it( 'Should not continue if button pressed', async () => {\n\n    jest.spyOn( Reanimated, 'useSharedValue' ).mockImplementation( ( value ) => ( { value: value === false ? true : value } ) );\n    const { getByTestId } = render( <InstagramStories stories={stories} /> );\n\n    await act( async () => {\n\n      fireEvent( getByTestId( '1StoryAvatar1Story' ), 'click' );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, {} );\n      await sleep();\n\n    } );\n\n  } );\n\n  it( 'Should close with swipe down', async () => {\n\n    jest.spyOn( Reanimated, 'useSharedValue' ).mockImplementation( ( value ) => ( { value } ) );\n    const { getByTestId, queryByTestId } = render( <InstagramStories stories={stories} /> );\n\n    await act( async () => {\n\n      fireEvent( getByTestId( '1StoryAvatar1Story' ), 'click' );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderStart', { x: 0 } );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderMove', {\n        x: 0, velocityX: 0, velocityY: 10, translationY: 200,\n      } );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', { translationY: 200 } );\n      await sleep();\n\n      expect( queryByTestId( 'gestureContainer' ) ).toBeFalsy();\n\n    } );\n\n  } );\n\n  it( 'Should go to next story', async () => {\n\n    const { getByTestId } = render( <InstagramStories stories={stories3} /> );\n\n    await act( async () => {\n\n      fireEvent( getByTestId( '1StoryAvatar2Story' ), 'click' );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, {} );\n      await sleep();\n\n    } );\n\n  } );\n\n  it( 'Should go to next user', async () => {\n\n    const { getByTestId } = render( <InstagramStories stories={stories2} /> );\n\n    await act( async () => {\n\n      fireEvent( getByTestId( '1StoryAvatar1Story' ), 'click' );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, {} );\n      await sleep();\n\n    } );\n\n  } );\n\n  it( 'Should go to previous story', async () => {\n\n    jest.spyOn( Reanimated, 'useSharedValue' ).mockImplementation( ( value ) => ( { value: value === '1' ? '2' : value } ) );\n\n    const { getByTestId } = render( <InstagramStories stories={stories3} /> );\n\n    await act( async () => {\n\n      fireEvent( getByTestId( '1StoryAvatar2Story' ), 'click' );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { pressedX: 0, pressedAt: Date.now() } );\n      await sleep();\n\n    } );\n\n  } );\n\n  it( 'Should go to previous user + work for android', async () => {\n\n    Platform.OS = 'android';\n    jest.spyOn( Reanimated, 'useSharedValue' ).mockImplementation( ( value ) => ( { value: value === 0 ? 500 : value } ) );\n    jest.spyOn( Reanimated, 'interpolate' ).mockImplementation( () => WIDTH );\n\n    const { getByTestId } = render( <InstagramStories stories={stories2} /> );\n\n    await act( async () => {\n\n      fireEvent( getByTestId( '2StoryAvatar1Story' ), 'click' );\n      await sleep();\n\n      fireEvent( getByTestId( 'gestureContainer' ), 'responderEnd', {}, { pressedX: 0, pressedAt: Date.now() } );\n      await sleep();\n\n    } );\n\n  } );\n\n  it( 'Should work with video', async () => {\n\n    const { getByTestId } = render( <InstagramStories stories={stories4} /> );\n\n    await act( async () => {\n\n      fireEvent( getByTestId( '2StoryAvatar1Story' ), 'click' );\n      await sleep();\n\n    } );\n\n  } );\n\n  it( 'Should work with empty array', async () => {\n\n    render( <InstagramStories stories={[ {\n      id: '1',\n      name: 'John Doe',\n      avatarSource: { uri: 'https://picsum.photos/200/300' },\n      stories: [],\n    } ]} /> );\n\n  } );\n\n  it( 'Should work with video & default duration', async () => {\n\n    const { getByTestId } = render( <InstagramStories stories={stories4} videoAnimationMaxDuration={1000} /> );\n\n    await act( async () => {\n\n      fireEvent( getByTestId( '2StoryAvatar1Story' ), 'click' );\n      await sleep();\n\n    } );\n\n  } );\n\n} );\n\ndescribe( 'StoryAvatar test', () => {\n\n  it( 'Should work with seenStories & call onPress', () => {\n\n    jest.spyOn( Reanimated, 'useSharedValue' ).mockImplementation( ( value ) => ( { value: value === false ? true : value } ) );\n    const callback = jest.fn();\n    const { getByTestId } = render( <StoryAvatar {...stories[0]} seenStories={{ value: { 1: '1' } }} loadingStory={{ value: '2' }} onPress={callback} colors={[ '#FFF' ]} seenColors={[ '#FFF' ]} size={50} /> );\n\n    expect( getByTestId( '1StoryAvatar1Story' ) ).toBeTruthy();\n\n    act( () => {\n\n      fireEvent.press( getByTestId( '1StoryAvatar1Story' ) );\n      expect( callback ).toHaveBeenCalled();\n\n    } );\n\n  } );\n\n} );\n\ndescribe( 'Loader test', () => {\n\n  it( 'Should work with empty stories', () => {\n\n    jest.spyOn( Reanimated, 'useAnimatedReaction' ).mockImplementation( ( value, cb ) => cb( typeof value() !== 'boolean' ? [ '#AAA' ] : value() ) );\n    render( <Loader loading={{ value: false }} color={{ value: [ '#FFF' ] }} /> );\n\n  } );\n\n} );\n\ndescribe( 'Story Image test', () => {\n\n  it( 'Should work with wrong story', () => {\n\n    render( <StoryImage stories={stories[0].stories} active={{ value: true }} activeStory={{ value: '2' }} defaultImage=\"url\" paused={{ value: true }} isActive={{ value: true }} /> );\n\n  } );\n\n  it( 'Should work if story already loaded', async () => {\n\n    jest.spyOn(Reanimated, 'useSharedValue').mockImplementation((value) => ({ value: value === true ? false : value }));\n\n    const onLoad = jest.fn();\n\n    render( <StoryImage\n      stories={[ { id: '1', source: { uri: '' } } ]}\n      active={{ value: true }}\n      activeStory={{ value: '1' }}\n      defaultImage=\"url\"\n      onLoad={onLoad}\n      paused={{ value: true }}\n      isActive={{ value: true }}\n    /> );\n\n    expect( onLoad ).toHaveBeenCalled();\n\n  } );\n\n} );\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"commonjs\",\n    \"lib\": [\n      \"ES2021\"\n    ],\n    \"allowJs\": true,\n    \"jsx\": \"react-native\",\n    \"declaration\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"./dist\",\n    \"declarationDir\": \"./dist\",\n    \"noEmit\": true,\n    \"incremental\": true,\n    \"isolatedModules\": true,\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"moduleResolution\": \"node\",\n    \"baseUrl\": \"./src\",\n    \"paths\": {\n      \"~/*\": [\n        \"*\"\n      ]\n    },\n    \"types\": [\"react\"],\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": false,\n    \"resolveJsonModule\": true,\n    \"noUncheckedIndexedAccess\": true\n  },\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.tsx\"\n  ],\n  \"exclude\": [\n    \"node_modules\",\n    \"modules\",\n    \"babel.config.js\",\n    \"metro.config.js\",\n    \"jest.config.js\",\n    \"commitlint.config.js\",\n  ]\n}\n"
  }
]