├── .eslintignore ├── .eslintrc.cjs ├── .expo-shared └── assets.json ├── .github ├── FUNDING.yml ├── stale.yml └── workflows │ └── main.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── babel.config.cjs ├── codecov.yml ├── example ├── .expo-shared │ └── assets.json ├── .gitignore ├── .yarn │ └── releases │ │ └── yarn-classic.cjs ├── .yarnrc ├── .yarnrc.yml ├── App.tsx ├── app.json ├── assets │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ └── splash.png ├── babel.config.js ├── components │ └── navbar.tsx ├── example-expo │ ├── AccessoryBar.tsx │ ├── CustomActions.tsx │ ├── CustomView.tsx │ ├── data │ │ ├── earlierMessages.js │ │ └── messages.js │ └── mediaUtils.ts ├── example-gifted-chat │ ├── README.md │ ├── example-gifted-chat.png │ └── src │ │ ├── Chats.js │ │ ├── InputToolbar.js │ │ ├── MessageContainer.js │ │ └── messages.js ├── example-slack-message │ ├── README.md │ ├── example-default-style.png │ ├── example-slack-style.png │ └── src │ │ ├── SlackBubble.tsx │ │ └── SlackMessage.tsx ├── index.js ├── ios │ ├── .gitignore │ ├── .xcode.env │ ├── Podfile │ ├── Podfile.lock │ ├── Podfile.properties.json │ ├── example.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── example.xcscheme │ ├── example.xcworkspace │ │ └── contents.xcworkspacedata │ └── example │ │ ├── AppDelegate.h │ │ ├── AppDelegate.mm │ │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── PrivacyInfo.xcprivacy │ │ ├── SplashScreen.storyboard │ │ ├── Supporting │ │ └── Expo.plist │ │ └── main.m ├── metro.config.js ├── package.json ├── tsconfig.json └── yarn.lock ├── jest.config.cjs ├── media ├── logo_sponsor.png └── stream-logo.png ├── package.json ├── screenshots ├── gifted-chat-1.png ├── gifted-chat-2.png ├── iPhone-6s-gifted-chat-1.png ├── iPhone-6s-gifted-chat-2.png └── iPhone-6s-gifted-chat-3.png ├── src ├── Actions.tsx ├── Avatar.tsx ├── Bubble │ ├── index.tsx │ ├── styles.ts │ └── types.ts ├── Color.ts ├── Composer.tsx ├── Constant.ts ├── Day │ ├── index.tsx │ ├── styles.ts │ └── types.ts ├── GiftedAvatar.tsx ├── GiftedChat │ ├── index.tsx │ ├── styles.ts │ └── types.ts ├── GiftedChatContext.ts ├── InputToolbar.tsx ├── LoadEarlier.tsx ├── Message │ ├── index.tsx │ ├── styles.ts │ └── types.ts ├── MessageAudio.tsx ├── MessageContainer │ ├── components │ │ ├── DayAnimated │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ └── Item │ │ │ ├── index.tsx │ │ │ └── types.ts │ ├── index.tsx │ ├── styles.ts │ └── types.ts ├── MessageImage.tsx ├── MessageText.tsx ├── MessageVideo.tsx ├── QuickReplies.tsx ├── Send.tsx ├── SystemMessage.tsx ├── Time.tsx ├── TypingIndicator │ ├── index.tsx │ ├── styles.ts │ └── types.ts ├── __tests__ │ ├── Actions.test.tsx │ ├── Avatar.test.tsx │ ├── Bubble.test.tsx │ ├── Color.test.tsx │ ├── Composer.test.tsx │ ├── Constant.test.tsx │ ├── Day.test.tsx │ ├── GiftedAvatar.test.tsx │ ├── GiftedChat.test.tsx │ ├── InputToolbar.test.tsx │ ├── LoadEarlier.test.tsx │ ├── Message.test.tsx │ ├── MessageContainer.test.tsx │ ├── MessageImage.test.tsx │ ├── MessageText.test.tsx │ ├── Send.test.tsx │ ├── SystemMessage.test.tsx │ ├── Time.test.tsx │ ├── __snapshots__ │ │ ├── Actions.test.tsx.snap │ │ ├── Avatar.test.tsx.snap │ │ ├── Bubble.test.tsx.snap │ │ ├── Color.test.tsx.snap │ │ ├── Composer.test.tsx.snap │ │ ├── Constant.test.tsx.snap │ │ ├── Day.test.tsx.snap │ │ ├── GiftedAvatar.test.tsx.snap │ │ ├── GiftedChat.test.tsx.snap │ │ ├── InputToolbar.test.tsx.snap │ │ ├── LoadEarlier.test.tsx.snap │ │ ├── Message.test.tsx.snap │ │ ├── MessageContainer.test.tsx.snap │ │ ├── MessageImage.test.tsx.snap │ │ ├── MessageText.test.tsx.snap │ │ ├── Send.test.tsx.snap │ │ ├── SystemMessage.test.tsx.snap │ │ └── Time.test.tsx.snap │ ├── data.ts │ └── utils.test.ts ├── hooks │ └── useUpdateLayoutEffect.ts ├── index.ts ├── logging.ts ├── styles.ts ├── types.ts └── utils.ts ├── tests └── setup.js ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | /lib 2 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es2021: true, 4 | // jest: true, 5 | browser: true, 6 | node: true, 7 | }, 8 | parser: '@typescript-eslint/parser', 9 | extends: [ 10 | 'standard', 11 | 'eslint:recommended', 12 | 'plugin:react/recommended', 13 | 'plugin:react-hooks/recommended', 14 | "plugin:@typescript-eslint/eslint-recommended", 15 | 'plugin:@typescript-eslint/recommended', 16 | 'plugin:json/recommended-legacy', 17 | 'plugin:jest/recommended', 18 | ], 19 | overrides: [ 20 | { 21 | env: { 22 | node: true, 23 | }, 24 | files: ['.eslintrc.{js,cjs}'], 25 | parserOptions: { 26 | sourceType: 'script', 27 | project: './tsconfig.json', 28 | }, 29 | }, 30 | { 31 | files: ["tests/**/*"], 32 | plugins: ["jest"], 33 | env: { 34 | 'jest/globals': true, 35 | }, 36 | }, 37 | ], 38 | parserOptions: { 39 | ecmaFeatures: { 40 | jsx: true, 41 | }, 42 | ecmaVersion: 'latest', 43 | sourceType: 'module', 44 | }, 45 | plugins: [ 46 | '@stylistic', 47 | 'react', 48 | 'react-hooks', 49 | ], 50 | settings: { 51 | react: { 52 | version: 'detect', 53 | }, 54 | }, 55 | rules: { 56 | 'react/react-in-jsx-scope': 0, 57 | '@stylistic/no-explicit-any': 'off', 58 | 'react/no-unknown-property': 0, 59 | 'indent': [ 60 | 'error', 61 | 2, 62 | { 63 | SwitchCase: 1, 64 | VariableDeclarator: 'first', 65 | ignoredNodes: ['TemplateLiteral'], 66 | }, 67 | ], 68 | 'template-curly-spacing': 'off', 69 | 'linebreak-style': ['off', 'unix'], 70 | 'quotes': ['error', 'single'], 71 | 'jsx-quotes': ['error', 'prefer-single'], 72 | '@stylistic/semi': ['error', 'never'], 73 | '@stylistic/member-delimiter-style': [ 74 | 'error', 75 | { 76 | multiline: { 77 | delimiter: 'none', // No semicolon for multiline 78 | requireLast: true, 79 | }, 80 | singleline: { 81 | delimiter: 'comma', // Use comma for single line 82 | requireLast: false, 83 | }, 84 | }, 85 | ], 86 | 'comma-dangle': [ 87 | 'error', 88 | { 89 | arrays: 'always-multiline', 90 | objects: 'always-multiline', 91 | imports: 'always-multiline', 92 | exports: 'never', 93 | functions: 'never', 94 | }, 95 | ], 96 | 'arrow-parens': ['error', 'as-needed'], 97 | 'no-func-assign': 'off', 98 | 'no-class-assign': 'off', 99 | 'no-useless-escape': 'off', 100 | 'curly': [2, 'multi', 'consistent'], 101 | 'react/display-name': 'off', 102 | 'react-hooks/exhaustive-deps': [ 103 | 'warn', 104 | { 105 | additionalHooks: 106 | '(useAnimatedStyle|useSharedValue|useAnimatedGestureHandler|useAnimatedScrollHandler|useAnimatedProps|useDerivedValue|useAnimatedRef|useAnimatedReact|useAnimatedReaction)', 107 | // useAnimatedReaction 108 | // USE RULE FUNC/FUNC/DEPS 109 | }, 110 | ], 111 | 'no-unused-vars': ['error'], 112 | 'brace-style': ['error', '1tbs', { allowSingleLine: false }], 113 | 'nonblock-statement-body-position': ['error', 'below'], 114 | '@stylistic/jsx-closing-bracket-location': ['error', 'line-aligned'], 115 | 'no-unreachable': 'error', 116 | 'react/prop-types': 'off', 117 | }, 118 | globals: { 119 | describe: 'readonly', 120 | test: 'readonly', 121 | jest: 'readonly', 122 | expect: 'readonly', 123 | fetch: 'readonly', 124 | navigator: 'readonly', 125 | __DEV__: 'readonly', 126 | XMLHttpRequest: 'readonly', 127 | FormData: 'readonly', 128 | React$Element: 'readonly', 129 | requestAnimationFrame: 'readonly', 130 | }, 131 | } 132 | -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "5c6d215cbde93d15ae63d2ea43dfe8bf8a79a53146382cf7f3f0089bec2fc5d6": true, 3 | "d0e86e9f72936ac85597d9cd6415cf22a3c208505d5586ac04de09d4dd305707": true, 4 | "b884dbf3daca9d0a4de2f6552aa4dbdda9cbff26e2677bd76a514d71af2e7e2c": true, 5 | "30bf2d1edfc90d2841794660cf30a94bb134b89d4808bd4b05cac2304cf6fad7": true, 6 | "01d8b00b4e3d1dfab70e1ef3354b373f13bdcc3ad29122b3afacf34afd043960": true, 7 | "36cb6cfb9a281169f9ba1eb7c345fba1d56a0c7115fbf8c4b3753427aad8edaa": true, 8 | "3232d6cbd4824ece99982787e431ff1425df1d22288961602506b046c50bc516": true, 9 | "5c8d230c038116f9327c1a38157e7b5d25e1d6bbfbb0ba4e86310f097c3d0f9f": true, 10 | "250d0d32ab3051aee4b8f9d26a3299e6b3d8e6ee137dffe8a7e183e5478a2040": true 11 | } 12 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [faridsafi, xcarpentier, johan-dutoit, kesha-antonov] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with a custom sponsorship URL 13 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 15 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | Sorry, but this issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. BTW Thank you 15 | for your contributions 😀 !!! 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | checks: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [18, 20] 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | 24 | - name: Node modules 25 | run: | 26 | yarn install 27 | 28 | - name: Lint 29 | run: | 30 | yarn build 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | .expo/ 4 | npm-debug.log 5 | TODO.md 6 | .idea 7 | .vscode 8 | Exponent-*.app 9 | *.log 10 | lib/ 11 | coverage/ 12 | web-build/ 13 | .eslintcache 14 | 15 | # Yarn 16 | .yarn/* 17 | !.yarn/patches 18 | !.yarn/plugins 19 | !.yarn/releases 20 | !.yarn/sdks 21 | !.yarn/versions 22 | yarn-error.log 23 | 24 | example_bare/vendor 25 | example_bare/**/build 26 | example_bare/ios/Pods 27 | example_bare/android/.gradle 28 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | yarn lint-staged 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .expo/ 2 | .expo-shared/ 3 | .circleci/ 4 | .github/ 5 | .vscode/ 6 | example/ 7 | example-expo/ 8 | example-slack-message/ 9 | example-gifted-chat/ 10 | screenshots/ 11 | babel.config.js 12 | tests/ 13 | README.md 14 | ISSUE_TEMPLATE.md 15 | codecov.yml 16 | media/ 17 | App.tsx 18 | app.json 19 | metro.config.js 20 | src/ 21 | tsconfig.json 22 | tslint.json 23 | yarn.lock 24 | flow-typedefs/ 25 | .flowconfig 26 | yarn-error.log 27 | web-build/ 28 | types.d.ts 29 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### Issue Description 2 | 3 | [FILL THIS OUT] 4 | 5 | #### Steps to Reproduce / Code Snippets 6 | 7 | [FILL THIS OUT] 8 | 9 | #### Expected Results 10 | 11 | [FILL THIS OUT] 12 | 13 | #### Additional Information 14 | 15 | * Nodejs version: [FILL THIS OUT] 16 | * React version: [FILL THIS OUT] 17 | * React Native version: [FILL THIS OUT] 18 | * react-native-gifted-chat version: [FILL THIS OUT] 19 | * Platform(s) (iOS, Android, or both?): [FILL THIS OUT] 20 | * TypeScript version: [FILL THIS OUT] 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Farid from Safi 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 | -------------------------------------------------------------------------------- /babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true) 3 | 4 | return { 5 | presets: [ 6 | '@babel/preset-env', 7 | 'module:@react-native/babel-preset', 8 | '@babel/preset-typescript', 9 | ], 10 | plugins: [ 11 | '@babel/plugin-transform-unicode-property-regex', 12 | '@babel/plugin-transform-react-jsx', 13 | 'react-native-reanimated/plugin', 14 | ], 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: 4 | default: off 5 | -------------------------------------------------------------------------------- /example/.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | 13 | # macOS 14 | .DS_Store 15 | 16 | ios/.xcode.env.local 17 | *.hprof 18 | .cxx/ 19 | 20 | /vendor/bundle/ 21 | 22 | # Yarn 23 | .yarn/* 24 | !.yarn/patches 25 | !.yarn/plugins 26 | !.yarn/releases 27 | !.yarn/sdks 28 | !.yarn/versions 29 | yarn-error.log 30 | 31 | # @generated expo-cli sync-8d4afeec25ea8a192358fae2f8e2fc766bdce4ec 32 | # The following patterns were generated by expo-cli 33 | 34 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 35 | 36 | # dependencies 37 | node_modules/ 38 | 39 | # Expo 40 | .expo/ 41 | dist/ 42 | web-build/ 43 | expo-env.d.ts 44 | 45 | # Native 46 | *.orig.* 47 | *.jks 48 | *.p8 49 | *.p12 50 | *.key 51 | *.mobileprovision 52 | 53 | # Metro 54 | .metro-health-check* 55 | 56 | # debug 57 | npm-debug.* 58 | yarn-debug.* 59 | yarn-error.* 60 | 61 | # macOS 62 | .DS_Store 63 | *.pem 64 | 65 | # local env files 66 | .env*.local 67 | 68 | # typescript 69 | *.tsbuildinfo 70 | 71 | # @end expo-cli -------------------------------------------------------------------------------- /example/.yarnrc: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | yarn-path ".yarn/releases/yarn-1.22.22.cjs" 6 | -------------------------------------------------------------------------------- /example/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-classic.cjs 4 | -------------------------------------------------------------------------------- /example/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useReducer } from 'react' 2 | import { Alert, Linking, Platform, StyleSheet, Text, View } from 'react-native' 3 | import { MaterialIcons } from '@expo/vector-icons' 4 | import { 5 | GiftedChat, 6 | IMessage, 7 | Send, 8 | SendProps, 9 | SystemMessage, 10 | } from 'react-native-gifted-chat' 11 | import { SafeAreaProvider, SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context' 12 | import { NavBar } from './components/navbar' 13 | import AccessoryBar from './example-expo/AccessoryBar' 14 | import CustomActions from './example-expo/CustomActions' 15 | import CustomView from './example-expo/CustomView' 16 | import earlierMessages from './example-expo/data/earlierMessages' 17 | import messagesData from './example-expo/data/messages' 18 | import * as Clipboard from 'expo-clipboard' 19 | 20 | const user = { 21 | _id: 1, 22 | name: 'Developer', 23 | } 24 | 25 | // const otherUser = { 26 | // _id: 2, 27 | // name: 'React Native', 28 | // avatar: 'https://facebook.github.io/react/img/logo_og.png', 29 | // } 30 | 31 | interface IState { 32 | messages: any[] 33 | step: number 34 | loadEarlier?: boolean 35 | isLoadingEarlier?: boolean 36 | isTyping: boolean 37 | } 38 | 39 | enum ActionKind { 40 | SEND_MESSAGE = 'SEND_MESSAGE', 41 | LOAD_EARLIER_MESSAGES = 'LOAD_EARLIER_MESSAGES', 42 | LOAD_EARLIER_START = 'LOAD_EARLIER_START', 43 | SET_IS_TYPING = 'SET_IS_TYPING', 44 | // LOAD_EARLIER_END = 'LOAD_EARLIER_END', 45 | } 46 | 47 | // An interface for our actions 48 | interface StateAction { 49 | type: ActionKind 50 | payload?: any 51 | } 52 | 53 | function reducer (state: IState, action: StateAction) { 54 | switch (action.type) { 55 | case ActionKind.SEND_MESSAGE: { 56 | return { 57 | ...state, 58 | step: state.step + 1, 59 | messages: action.payload, 60 | } 61 | } 62 | case ActionKind.LOAD_EARLIER_MESSAGES: { 63 | return { 64 | ...state, 65 | loadEarlier: true, 66 | isLoadingEarlier: false, 67 | messages: action.payload, 68 | } 69 | } 70 | case ActionKind.LOAD_EARLIER_START: { 71 | return { 72 | ...state, 73 | isLoadingEarlier: true, 74 | } 75 | } 76 | case ActionKind.SET_IS_TYPING: { 77 | return { 78 | ...state, 79 | isTyping: action.payload, 80 | } 81 | } 82 | } 83 | } 84 | 85 | const App = () => { 86 | const [state, dispatch] = useReducer(reducer, { 87 | messages: messagesData, 88 | step: 0, 89 | loadEarlier: true, 90 | isLoadingEarlier: false, 91 | isTyping: false, 92 | }) 93 | 94 | const onSend = useCallback( 95 | (messages: any[]) => { 96 | const sentMessages = [{ ...messages[0], sent: true, received: true }] 97 | const newMessages = GiftedChat.append( 98 | state.messages, 99 | sentMessages, 100 | Platform.OS !== 'web' 101 | ) 102 | 103 | dispatch({ type: ActionKind.SEND_MESSAGE, payload: newMessages }) 104 | }, 105 | [dispatch, state.messages] 106 | ) 107 | 108 | const onLoadEarlier = useCallback(() => { 109 | dispatch({ type: ActionKind.LOAD_EARLIER_START }) 110 | setTimeout(() => { 111 | const newMessages = GiftedChat.prepend( 112 | state.messages, 113 | earlierMessages() as IMessage[], 114 | Platform.OS !== 'web' 115 | ) 116 | 117 | dispatch({ type: ActionKind.LOAD_EARLIER_MESSAGES, payload: newMessages }) 118 | }, 1500) // simulating network 119 | // }, 15000) // for debug with long loading 120 | }, [dispatch, state.messages]) 121 | 122 | const parsePatterns = useCallback(() => { 123 | return [ 124 | { 125 | pattern: /#(\w+)/, 126 | style: { textDecorationLine: 'underline', color: 'darkorange' }, 127 | onPress: () => Linking.openURL('http://gifted.chat'), 128 | }, 129 | ] 130 | }, []) 131 | 132 | const onLongPressAvatar = useCallback((pressedUser: any) => { 133 | Alert.alert(JSON.stringify(pressedUser)) 134 | }, []) 135 | 136 | const onPressAvatar = useCallback(() => { 137 | Alert.alert('On avatar press') 138 | }, []) 139 | 140 | const handleLongPress = useCallback((context: unknown, currentMessage: object) => { 141 | if (!currentMessage.text) 142 | return 143 | 144 | const options = [ 145 | 'Copy text', 146 | 'Cancel', 147 | ] 148 | 149 | const cancelButtonIndex = options.length - 1 150 | 151 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 152 | ;(context as any).actionSheet().showActionSheetWithOptions( 153 | { 154 | options, 155 | cancelButtonIndex, 156 | }, 157 | (buttonIndex: number) => { 158 | switch (buttonIndex) { 159 | case 0: 160 | Clipboard.setStringAsync(currentMessage.text) 161 | break 162 | default: 163 | break 164 | } 165 | } 166 | ) 167 | }, []) 168 | 169 | const onQuickReply = useCallback((replies: any[]) => { 170 | const createdAt = new Date() 171 | if (replies.length === 1) 172 | onSend([ 173 | { 174 | createdAt, 175 | _id: Math.round(Math.random() * 1000000), 176 | text: replies[0].title, 177 | user, 178 | }, 179 | ]) 180 | else if (replies.length > 1) 181 | onSend([ 182 | { 183 | createdAt, 184 | _id: Math.round(Math.random() * 1000000), 185 | text: replies.map(reply => reply.title).join(', '), 186 | user, 187 | }, 188 | ]) 189 | else 190 | console.warn('replies param is not set correctly') 191 | }, []) 192 | 193 | const renderQuickReplySend = useCallback(() => { 194 | return {' custom send =>'} 195 | }, []) 196 | 197 | const setIsTyping = useCallback( 198 | (isTyping: boolean) => { 199 | dispatch({ type: ActionKind.SET_IS_TYPING, payload: isTyping }) 200 | }, 201 | [dispatch] 202 | ) 203 | 204 | const onSendFromUser = useCallback( 205 | (messages: IMessage[] = []) => { 206 | const createdAt = new Date() 207 | const messagesToUpload = messages.map(message => ({ 208 | ...message, 209 | user, 210 | createdAt, 211 | _id: Math.round(Math.random() * 1000000), 212 | })) 213 | 214 | onSend(messagesToUpload) 215 | }, 216 | [onSend] 217 | ) 218 | 219 | const renderAccessory = useCallback(() => { 220 | return ( 221 | setIsTyping(!state.isTyping)} 224 | /> 225 | ) 226 | }, [onSendFromUser, setIsTyping, state.isTyping]) 227 | 228 | const renderCustomActions = useCallback( 229 | props => 230 | Platform.OS === 'web' 231 | ? null 232 | : ( 233 | 234 | ), 235 | [onSendFromUser] 236 | ) 237 | 238 | const renderSystemMessage = useCallback(props => { 239 | return ( 240 | 249 | ) 250 | }, []) 251 | 252 | const renderCustomView = useCallback(props => { 253 | return 254 | }, []) 255 | 256 | const renderSend = useCallback((props: SendProps) => { 257 | return ( 258 | 259 | 260 | 261 | ) 262 | }, []) 263 | 264 | const insets = useSafeAreaInsets() 265 | 266 | return ( 267 | 268 | 269 | 270 | 303 | 304 | 305 | ) 306 | } 307 | 308 | const AppWrapper = () => { 309 | return ( 310 | 311 | 312 | 313 | ) 314 | } 315 | 316 | const styles = StyleSheet.create({ 317 | fill: { 318 | flex: 1, 319 | }, 320 | container: { 321 | backgroundColor: '#f5f5f5', 322 | }, 323 | content: { 324 | backgroundColor: '#ffffff', 325 | }, 326 | }) 327 | 328 | export default AppWrapper 329 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "example", 4 | "slug": "example", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "splash": { 9 | "image": "./assets/splash.png", 10 | "resizeMode": "contain", 11 | "backgroundColor": "#ffffff" 12 | }, 13 | "updates": { 14 | "fallbackToCacheTimeout": 0 15 | }, 16 | "assetBundlePatterns": [ 17 | "**/*" 18 | ], 19 | "ios": { 20 | "supportsTablet": true, 21 | "bundleIdentifier": "org.name.example" 22 | }, 23 | "android": { 24 | "adaptiveIcon": { 25 | "foregroundImage": "./assets/adaptive-icon.png", 26 | "backgroundColor": "#FFFFFF" 27 | } 28 | }, 29 | "web": { 30 | "favicon": "./assets/favicon.png" 31 | }, 32 | "plugins": [ 33 | [ 34 | "expo-build-properties", 35 | { 36 | "android": { 37 | "kotlinVersion": "1.6.21" 38 | } 39 | } 40 | ] 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaridSafi/react-native-gifted-chat/7766a68fc753bac74b7b7d715772783b47fb8b46/example/assets/adaptive-icon.png -------------------------------------------------------------------------------- /example/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaridSafi/react-native-gifted-chat/7766a68fc753bac74b7b7d715772783b47fb8b46/example/assets/favicon.png -------------------------------------------------------------------------------- /example/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaridSafi/react-native-gifted-chat/7766a68fc753bac74b7b7d715772783b47fb8b46/example/assets/icon.png -------------------------------------------------------------------------------- /example/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaridSafi/react-native-gifted-chat/7766a68fc753bac74b7b7d715772783b47fb8b46/example/assets/splash.png -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = function (api) { 4 | api.cache(true) 5 | 6 | return { 7 | presets: ['babel-preset-expo'], 8 | plugins: [ 9 | [ 10 | 'module-resolver', 11 | { 12 | resolvePath: (sourcePath, currentFile, opts) => { 13 | if (/react\-native\-gifted\-chat/ig.test(sourcePath)) { 14 | let relativePath = new Array(currentFile.replace(path.join(__dirname, '../'), '').split('/').length - 1).fill('..').join('/') 15 | relativePath = path.join(relativePath, 'src', sourcePath.replace(/react\-native\-gifted\-chat(?:\/src)?/ig, '')) 16 | return relativePath 17 | } 18 | 19 | return sourcePath 20 | }, 21 | }, 22 | ], 23 | 'react-native-reanimated/plugin', 24 | ], 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/components/navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, Text, Platform } from 'react-native' 3 | 4 | export function NavBar () { 5 | if (Platform.OS === 'web') 6 | return null 7 | 8 | return ( 9 | 15 | 💬 Gifted Chat{'\n'} 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /example/example-expo/AccessoryBar.tsx: -------------------------------------------------------------------------------- 1 | import { MaterialIcons } from '@expo/vector-icons' 2 | import React from 'react' 3 | import { StyleSheet, TouchableOpacity, View } from 'react-native' 4 | 5 | import { 6 | getLocationAsync, 7 | pickImageAsync, 8 | takePictureAsync, 9 | } from './mediaUtils' 10 | 11 | export default class AccessoryBar extends React.Component { 12 | render () { 13 | const { onSend, isTyping } = this.props 14 | 15 | return ( 16 | 17 |