├── .gitignore
├── src
├── components
│ ├── InstagramStories
│ │ ├── InstagramStories.styles.ts
│ │ └── index.tsx
│ ├── Icon
│ │ ├── index.tsx
│ │ └── close.tsx
│ ├── Footer
│ │ ├── Footer.styles.ts
│ │ └── index.tsx
│ ├── Content
│ │ ├── Content.styles.ts
│ │ └── index.tsx
│ ├── Animation
│ │ ├── Animation.styles.ts
│ │ └── index.tsx
│ ├── Progress
│ │ ├── Progress.styles.ts
│ │ ├── index.tsx
│ │ └── item.tsx
│ ├── Modal
│ │ ├── Modal.styles.ts
│ │ ├── gesture.tsx
│ │ └── index.tsx
│ ├── List
│ │ ├── List.styles.ts
│ │ └── index.tsx
│ ├── Image
│ │ ├── Image.styles.ts
│ │ ├── video.tsx
│ │ └── index.tsx
│ ├── Avatar
│ │ ├── Avatar.styles.ts
│ │ └── index.tsx
│ ├── Header
│ │ ├── Header.styles.ts
│ │ └── index.tsx
│ ├── AvatarList
│ │ └── index.tsx
│ └── Loader
│ │ └── index.tsx
├── core
│ ├── dto
│ │ ├── helpersDTO.ts
│ │ ├── instagramStoriesDTO.ts
│ │ └── componentsDTO.ts
│ ├── constants
│ │ └── index.ts
│ └── helpers
│ │ └── storage.ts
├── assets
│ └── images
│ │ ├── demo.gif
│ │ └── logo.png
├── index.tsx
└── declarations.d.ts
├── commitlint.config.js
├── .husky
└── commit-msg
├── babel.config.js
├── tsconfig.json
├── LICENSE
├── .github
└── workflows
│ ├── public.yml
│ └── release.yml
├── .eslintrc.js
├── package.json
├── jest.setup.js
├── CHANGELOG.md
├── tests
└── index.test.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /coverage
3 | /dist
--------------------------------------------------------------------------------
/src/components/InstagramStories/InstagramStories.styles.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {extends: ['@commitlint/config-conventional']}
2 |
--------------------------------------------------------------------------------
/src/components/Icon/index.tsx:
--------------------------------------------------------------------------------
1 | import Close from './close';
2 |
3 | export { Close };
4 |
--------------------------------------------------------------------------------
/src/core/dto/helpersDTO.ts:
--------------------------------------------------------------------------------
1 | export interface ProgressStorageProps {
2 | [key: string]: string;
3 | }
4 |
--------------------------------------------------------------------------------
/src/assets/images/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/birdwingo/react-native-instagram-stories/HEAD/src/assets/images/demo.gif
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/birdwingo/react-native-instagram-stories/HEAD/src/assets/images/logo.png
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx --no -- commitlint --edit ${1}
5 | npm run test
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | presets: ['module:metro-react-native-babel-preset'],
4 | plugins: ['react-native-reanimated/plugin'],
5 | };
6 |
7 |
--------------------------------------------------------------------------------
/src/components/Footer/Footer.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create( {
4 | container: {
5 | position: 'absolute',
6 | bottom: 0,
7 | left: 0,
8 | right: 0,
9 | },
10 | } );
11 |
--------------------------------------------------------------------------------
/src/components/Content/Content.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create( {
4 | container: {
5 | position: 'absolute',
6 | top: 0,
7 | left: 0,
8 | bottom: 0,
9 | right: 0,
10 | },
11 | } );
12 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import InstagramStories from './components/InstagramStories';
2 | import { InstagramStoriesProps, InstagramStoriesPublicMethods } from './core/dto/instagramStoriesDTO';
3 |
4 | export type { InstagramStoriesProps, InstagramStoriesPublicMethods };
5 | export default InstagramStories;
6 |
--------------------------------------------------------------------------------
/src/declarations.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.png';
2 | declare module '*.gif';
3 |
4 | declare interface Keyframe {
5 | composite?: 'accumulate' | 'add' | 'auto' | 'replace';
6 | easing?: string;
7 | offset?: number | null;
8 | [property: string]: string | number | null | undefined;
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/Animation/Animation.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create( {
4 | container: StyleSheet.absoluteFillObject,
5 | absolute: {
6 | position: 'absolute',
7 | top: 0,
8 | left: 0,
9 | },
10 | cube: {
11 | justifyContent: 'center',
12 | },
13 | } );
14 |
--------------------------------------------------------------------------------
/src/components/Progress/Progress.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create( {
4 | container: {
5 | position: 'absolute',
6 | top: 16,
7 | left: 16,
8 | height: 2,
9 | flexDirection: 'row',
10 | gap: 4,
11 | },
12 | item: {
13 | height: 3,
14 | borderRadius: 8,
15 | overflow: 'hidden',
16 | },
17 | } );
18 |
--------------------------------------------------------------------------------
/src/components/Modal/Modal.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import { HEIGHT, WIDTH } from '../../core/constants';
3 |
4 | export default StyleSheet.create( {
5 | container: {
6 | flex: 1,
7 | },
8 | absolute: {
9 | position: 'absolute',
10 | top: 0,
11 | left: 0,
12 | width: WIDTH,
13 | height: HEIGHT,
14 | },
15 | bgAnimation: StyleSheet.absoluteFillObject,
16 | } );
17 |
--------------------------------------------------------------------------------
/src/components/List/List.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import { WIDTH } from '../../core/constants';
3 |
4 | export default StyleSheet.create( {
5 | container: {
6 | borderRadius: 8,
7 | overflow: 'hidden',
8 | width: WIDTH,
9 | },
10 | content: {
11 | position: 'absolute',
12 | top: 0,
13 | left: 0,
14 | bottom: 0,
15 | right: 0,
16 | zIndex: 3,
17 | },
18 | } );
19 |
--------------------------------------------------------------------------------
/src/components/Image/Image.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create( {
4 | container: {
5 | position: 'absolute',
6 | top: 0,
7 | left: 0,
8 | bottom: 0,
9 | right: 0,
10 | alignItems: 'center',
11 | justifyContent: 'center',
12 | zIndex: 2,
13 | },
14 | image: {
15 | alignItems: 'center',
16 | justifyContent: 'center',
17 | },
18 | } );
19 |
--------------------------------------------------------------------------------
/src/components/Avatar/Avatar.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 | import { AVATAR_OFFSET } from '../../core/constants';
3 |
4 | export default StyleSheet.create( {
5 | container: {
6 | flexDirection: 'row',
7 | alignItems: 'center',
8 | },
9 | avatar: {
10 | left: AVATAR_OFFSET,
11 | top: AVATAR_OFFSET,
12 | position: 'absolute',
13 | },
14 | name: {
15 | alignItems: 'center',
16 | },
17 | } );
18 |
--------------------------------------------------------------------------------
/src/components/Modal/gesture.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { PanGestureHandler, PanGestureHandlerProps, gestureHandlerRootHOC } from 'react-native-gesture-handler';
3 |
4 | const GestureHandler = gestureHandlerRootHOC(
5 | ( { children, onGestureEvent } : PanGestureHandlerProps ) => (
6 | {children}
7 | ),
8 | );
9 |
10 | export default memo( GestureHandler );
11 |
--------------------------------------------------------------------------------
/src/components/Header/Header.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from 'react-native';
2 |
3 | export default StyleSheet.create( {
4 | container: {
5 | position: 'absolute',
6 | left: 16,
7 | top: 32,
8 | },
9 | containerFlex: {
10 | flexDirection: 'row',
11 | justifyContent: 'space-between',
12 | alignItems: 'center',
13 | },
14 | left: {
15 | flexDirection: 'row',
16 | alignItems: 'center',
17 | gap: 12,
18 | flex: 1,
19 | },
20 | avatar: {
21 | borderWidth: 1.5,
22 | borderColor: '#FFF',
23 | overflow: 'hidden',
24 | },
25 | } );
26 |
--------------------------------------------------------------------------------
/src/components/Icon/close.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, memo } from 'react';
2 | import { Path, Svg } from 'react-native-svg';
3 | import { IconProps } from '../../core/dto/componentsDTO';
4 |
5 | const Close: FC = ( { color } ) => (
6 |
9 | );
10 |
11 | export default memo( Close );
12 |
--------------------------------------------------------------------------------
/src/core/constants/index.ts:
--------------------------------------------------------------------------------
1 | import { Dimensions } from 'react-native';
2 |
3 | export const { width: WIDTH, height: HEIGHT } = Dimensions.get( 'screen' );
4 |
5 | export const STORAGE_KEY = '@birdwingo/react-native-instagram-stories';
6 |
7 | export const DEFAULT_COLORS = [ '#F7B801', '#F18701', '#F35B04', '#F5301E', '#C81D4E', '#8F1D4E' ];
8 | export const LOADER_COLORS = [ '#FFF' ];
9 | export const SEEN_LOADER_COLORS = [ '#2A2A2C' ];
10 | export const PROGRESS_COLOR = '#00000099';
11 | export const PROGRESS_ACTIVE_COLOR = '#FFFFFF';
12 | export const BACKGROUND_COLOR = '#000000';
13 | export const CLOSE_COLOR = '#00000099';
14 |
15 | export const LOADER_ID = 'gradient';
16 | export const LOADER_URL = `url(#${LOADER_ID})`;
17 |
18 | export const STROKE_WIDTH = 2;
19 |
20 | export const AVATAR_SIZE = 60;
21 | export const AVATAR_OFFSET = 5;
22 | export const STORY_AVATAR_SIZE = 26;
23 |
24 | export const STORY_ANIMATION_DURATION = 800;
25 | export const ANIMATION_DURATION = 10000;
26 | export const LONG_PRESS_DURATION = 500;
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "commonjs",
5 | "lib": [
6 | "ES2021"
7 | ],
8 | "allowJs": true,
9 | "jsx": "react-native",
10 | "declaration": true,
11 | "sourceMap": true,
12 | "outDir": "./dist",
13 | "declarationDir": "./dist",
14 | "noEmit": true,
15 | "incremental": true,
16 | "isolatedModules": true,
17 | "strict": true,
18 | "noImplicitAny": true,
19 | "moduleResolution": "node",
20 | "baseUrl": "./src",
21 | "paths": {
22 | "~/*": [
23 | "*"
24 | ]
25 | },
26 | "types": ["react"],
27 | "allowSyntheticDefaultImports": true,
28 | "esModuleInterop": true,
29 | "skipLibCheck": false,
30 | "resolveJsonModule": true,
31 | "noUncheckedIndexedAccess": true
32 | },
33 | "include": [
34 | "src/**/*.ts",
35 | "src/**/*.tsx"
36 | ],
37 | "exclude": [
38 | "node_modules",
39 | "modules",
40 | "babel.config.js",
41 | "metro.config.js",
42 | "jest.config.js",
43 | "commitlint.config.js",
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Birdwingo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/components/Progress/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, memo } from 'react';
2 | import { View } from 'react-native';
3 | import ProgressItem from './item';
4 | import { WIDTH } from '../../core/constants';
5 | import ProgressStyles from './Progress.styles';
6 | import { StoryProgressProps } from '../../core/dto/componentsDTO';
7 |
8 | const Progress: FC = ( {
9 | progress, active, activeStory, length,
10 | progressActiveColor, progressColor, progressContainerStyle,
11 | } ) => {
12 |
13 | const width = ( (
14 | WIDTH - ProgressStyles.container.left * 2 ) - ( length - 1 )
15 | * ProgressStyles.container.gap ) / length;
16 |
17 | return (
18 |
19 | {[ ...Array( length ).keys() ].map( ( val ) => (
20 |
30 | ) )}
31 |
32 | );
33 |
34 | };
35 |
36 | export default memo( Progress );
37 |
--------------------------------------------------------------------------------
/src/components/Progress/item.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, memo } from 'react';
2 | import { View } from 'react-native';
3 | import Animated, { useAnimatedStyle } from 'react-native-reanimated';
4 | import { StoryProgressItemProps } from '../../core/dto/componentsDTO';
5 | import ProgressStyles from './Progress.styles';
6 | import { PROGRESS_ACTIVE_COLOR, PROGRESS_COLOR } from '../../core/constants';
7 |
8 | const AnimatedView = Animated.createAnimatedComponent( View );
9 |
10 | const ProgressItem: FC = ( {
11 | progress, active, activeStory, index, width,
12 | progressActiveColor = PROGRESS_ACTIVE_COLOR, progressColor = PROGRESS_COLOR,
13 | } ) => {
14 |
15 | const animatedStyle = useAnimatedStyle( () => {
16 |
17 | if ( !active.value || activeStory.value < index ) {
18 |
19 | return { width: 0 };
20 |
21 | }
22 |
23 | if ( activeStory.value > index ) {
24 |
25 | return { width };
26 |
27 | }
28 |
29 | return { width: width * progress.value };
30 |
31 | } );
32 |
33 | return (
34 |
35 |
38 |
39 | );
40 |
41 | };
42 |
43 | export default memo( ProgressItem );
44 |
--------------------------------------------------------------------------------
/src/components/Footer/index.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | FC, memo, useState, useMemo,
3 | } from 'react';
4 | import { View } from 'react-native';
5 | import { runOnJS, useAnimatedReaction } from 'react-native-reanimated';
6 | import { StoryContentProps } from '../../core/dto/componentsDTO';
7 | import ContentStyles from './Footer.styles';
8 |
9 | const StoryFooter: FC = ( { stories, active, activeStory } ) => {
10 |
11 | const [ storyIndex, setStoryIndex ] = useState( 0 );
12 |
13 | const onChange = async () => {
14 |
15 | 'worklet';
16 |
17 | const index = stories.findIndex( ( item ) => item.id === activeStory.value );
18 | if ( active.value && index >= 0 && index !== storyIndex ) {
19 |
20 | runOnJS( setStoryIndex )( index );
21 |
22 | }
23 |
24 | };
25 |
26 | useAnimatedReaction(
27 | () => active.value,
28 | ( res, prev ) => res !== prev && onChange(),
29 | [ active.value, onChange ],
30 | );
31 |
32 | useAnimatedReaction(
33 | () => activeStory.value,
34 | ( res, prev ) => res !== prev && onChange(),
35 | [ activeStory.value, onChange ],
36 | );
37 |
38 | const footer = useMemo( () => stories[storyIndex]?.renderFooter?.(), [ storyIndex ] );
39 |
40 | return footer ? {footer} : null;
41 |
42 | };
43 |
44 | export default memo( StoryFooter );
45 |
--------------------------------------------------------------------------------
/src/components/Content/index.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | FC, memo, useState, useMemo,
3 | } from 'react';
4 | import { View } from 'react-native';
5 | import { runOnJS, useAnimatedReaction } from 'react-native-reanimated';
6 | import { StoryContentProps } from '../../core/dto/componentsDTO';
7 | import ContentStyles from './Content.styles';
8 |
9 | const StoryContent: FC = ( { stories, active, activeStory } ) => {
10 |
11 | const [ storyIndex, setStoryIndex ] = useState( 0 );
12 |
13 | const onChange = async () => {
14 |
15 | 'worklet';
16 |
17 | const index = stories.findIndex( ( item ) => item.id === activeStory.value );
18 | if ( active.value && index >= 0 && index !== storyIndex ) {
19 |
20 | runOnJS( setStoryIndex )( index );
21 |
22 | }
23 |
24 | };
25 |
26 | useAnimatedReaction(
27 | () => active.value,
28 | ( res, prev ) => res !== prev && onChange(),
29 | [ active.value, onChange ],
30 | );
31 |
32 | useAnimatedReaction(
33 | () => activeStory.value,
34 | ( res, prev ) => res !== prev && onChange(),
35 | [ activeStory.value, onChange ],
36 | );
37 |
38 | const content = useMemo( () => stories[storyIndex]?.renderContent?.(), [ storyIndex ] );
39 |
40 | return content ? {content} : null;
41 |
42 | };
43 |
44 | export default memo( StoryContent );
45 |
--------------------------------------------------------------------------------
/src/core/helpers/storage.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | import { STORAGE_KEY } from '../constants';
3 | import { ProgressStorageProps } from '../dto/helpersDTO';
4 |
5 | export const clearProgressStorage = async () => {
6 |
7 | try {
8 |
9 | const AsyncStorage = require( '@react-native-async-storage/async-storage' ).default;
10 |
11 | return AsyncStorage.removeItem( STORAGE_KEY );
12 |
13 | } catch ( error ) {
14 |
15 | return null;
16 |
17 | }
18 |
19 | };
20 |
21 | export const getProgressStorage = async (): Promise => {
22 |
23 | try {
24 |
25 | const AsyncStorage = require( '@react-native-async-storage/async-storage' ).default;
26 |
27 | const progress = await AsyncStorage.getItem( STORAGE_KEY );
28 |
29 | return progress ? JSON.parse( progress ) : {};
30 |
31 | } catch ( error ) {
32 |
33 | return {};
34 |
35 | }
36 |
37 | };
38 |
39 | export const setProgressStorage = async ( user: string, lastSeen: string ) => {
40 |
41 | const progress = await getProgressStorage();
42 | progress[user] = lastSeen;
43 |
44 | try {
45 |
46 | const AsyncStorage = require( '@react-native-async-storage/async-storage' ).default;
47 |
48 | await AsyncStorage.setItem( STORAGE_KEY, JSON.stringify( progress ) );
49 |
50 | return progress;
51 |
52 | } catch ( error ) {
53 |
54 | return {};
55 |
56 | }
57 |
58 | };
59 |
--------------------------------------------------------------------------------
/.github/workflows/public.yml:
--------------------------------------------------------------------------------
1 | name: NPM Package Publish
2 | on:
3 | release:
4 | types: [created]
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: actions/setup-node@v3
12 | with:
13 | node-version: '16.x'
14 | registry-url: 'https://registry.npmjs.org'
15 | - run: npm ci
16 | - run: npm run build
17 | - run: |
18 | npm version ${{ github.event.release.tag_name }} --no-git-tag-version --allow-same-version && \
19 | npm publish --access public
20 | env:
21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
22 | slackNotification:
23 | name: Slack Notification
24 | runs-on: ubuntu-latest
25 | steps:
26 | - uses: actions/checkout@v2
27 | - name: Slack Notification
28 | uses: rtCamp/action-slack-notify@v2
29 | env:
30 | SLACK_CHANNEL: frontend
31 | SLACK_COLOR: ${{ job.status }}
32 | SLACK_ICON: https://raw.githubusercontent.com/birdwingo/react-native-instagram-stories/main/src/assets/images/logo.png
33 | SLACK_MESSAGE: Publish Release ${{ github.event.release.tag_name }} ${{ job.status == 'success' && 'has been successful' || 'has been failed' }}
34 | SLACK_TITLE: 'Instagram stories publish release :rocket:'
35 | SLACK_USERNAME: NPM
36 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Github Release
2 | on:
3 | pull_request:
4 | types: [closed]
5 | branch: main
6 | jobs:
7 | release:
8 | if: github.event.pull_request.merged == true
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v3
12 | with:
13 | token: ${{ secrets.AUTH_TOKEN }}
14 | - uses: actions/setup-node@v2
15 | with:
16 | node-version: '16.x'
17 | registry-url: 'https://registry.npmjs.org'
18 | - run: npm ci
19 | - name: Set up git user for release
20 | run: |
21 | git config --global user.email "actions@github.com"
22 | git config --global user.name "GitHub Actions"
23 | - run: npm run release
24 | - name: Push changes
25 | run: git push --follow-tags origin main
26 | - run: npm run build
27 | - run: npm run test
28 | - name: Get version from package-lock.json
29 | id: get_version
30 | run: echo "::set-output name=version::$(node -p "require('./package-lock.json').version")"
31 | - name: Get changelog
32 | id: get_changelog
33 | run: |
34 | CHANGELOG=$(awk '/^### \[[0-9]+\.[0-9]+\.[0-9]+\]/{if (version!="") {exit}; version=$2} version!="" {print}' CHANGELOG.md)
35 | echo "::set-output name=changelog::${CHANGELOG}"
36 | - name: Create Release
37 | uses: actions/create-release@master
38 | env:
39 | GITHUB_TOKEN: ${{ secrets.AUTH_TOKEN }}
40 | with:
41 | tag_name: "v${{ steps.get_version.outputs.version }}"
42 | release_name: "v${{ steps.get_version.outputs.version }}"
--------------------------------------------------------------------------------
/src/components/Image/video.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | FC, memo, useRef, useState,
3 | } from 'react';
4 | import { LayoutChangeEvent } from 'react-native';
5 | import { runOnJS, useAnimatedReaction } from 'react-native-reanimated';
6 | import { StoryVideoProps } from '../../core/dto/componentsDTO';
7 | import { WIDTH } from '../../core/constants';
8 |
9 | const StoryVideo: FC = ( {
10 | source, paused, isActive, onLoad, onLayout, ...props
11 | } ) => {
12 |
13 | try {
14 |
15 | // eslint-disable-next-line global-require
16 | const Video = require( 'react-native-video' ).default;
17 |
18 | const ref = useRef( null );
19 |
20 | const [ pausedValue, setPausedValue ] = useState( paused.value );
21 |
22 | const start = () => {
23 |
24 | ref.current?.seek( 0 );
25 | ref.current?.resume?.();
26 |
27 | };
28 |
29 | useAnimatedReaction(
30 | () => paused.value,
31 | ( res, prev ) => res !== prev && runOnJS( setPausedValue )( res ),
32 | [ paused.value ],
33 | );
34 |
35 | useAnimatedReaction(
36 | () => isActive.value,
37 | ( res ) => res && runOnJS( start )(),
38 | [ isActive.value ],
39 | );
40 |
41 | return (
42 |