Repository: cometchat/cometchat-uikit-react-native
Branch: v5
Commit: 33dac7ee0a1f
Files: 1301
Total size: 5.4 MB
Directory structure:
gitextract_hs1fkjg0/
├── .gitignore
├── README.md
├── examples/
│ ├── SampleApp/
│ │ ├── .bundle/
│ │ │ └── config
│ │ ├── .eslintrc.js
│ │ ├── .gitignore
│ │ ├── .prettierrc.js
│ │ ├── .vscode/
│ │ │ └── settings.json
│ │ ├── .watchmanconfig
│ │ ├── App.tsx
│ │ ├── AppErrorBoundary.tsx
│ │ ├── Gemfile
│ │ ├── README.md
│ │ ├── android/
│ │ │ ├── app/
│ │ │ │ ├── build.gradle
│ │ │ │ ├── debug.keystore
│ │ │ │ ├── proguard-rules.pro
│ │ │ │ └── src/
│ │ │ │ └── main/
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── java/
│ │ │ │ │ └── com/
│ │ │ │ │ └── sampleapp/
│ │ │ │ │ ├── MainActivity.kt
│ │ │ │ │ └── MainApplication.kt
│ │ │ │ └── res/
│ │ │ │ ├── drawable/
│ │ │ │ │ └── rn_edit_text_material.xml
│ │ │ │ └── values/
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── build.gradle
│ │ │ ├── gradle/
│ │ │ │ └── wrapper/
│ │ │ │ ├── gradle-wrapper.jar
│ │ │ │ └── gradle-wrapper.properties
│ │ │ ├── gradle.properties
│ │ │ ├── gradlew
│ │ │ ├── gradlew.bat
│ │ │ └── settings.gradle
│ │ ├── app.json
│ │ ├── babel.config.js
│ │ ├── gesture-handler.js
│ │ ├── gesture-handler.native.js
│ │ ├── index.js
│ │ ├── ios/
│ │ │ ├── .xcode.env
│ │ │ ├── Podfile
│ │ │ ├── SampleApp/
│ │ │ │ ├── AppDelegate.swift
│ │ │ │ ├── Images.xcassets/
│ │ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Info.plist
│ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ └── PrivacyInfo.xcprivacy
│ │ │ ├── SampleApp.xcodeproj/
│ │ │ │ ├── project.pbxproj
│ │ │ │ └── xcshareddata/
│ │ │ │ └── xcschemes/
│ │ │ │ └── SampleApp.xcscheme
│ │ │ └── SampleApp.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ └── WorkspaceSettings.xcsettings
│ │ ├── jest.config.js
│ │ ├── metro.config.js
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── assets/
│ │ │ │ └── icons/
│ │ │ │ ├── AccountCircle.tsx
│ │ │ │ ├── AddComment.tsx
│ │ │ │ ├── AiIcon.tsx
│ │ │ │ ├── ArrowBack.tsx
│ │ │ │ ├── Block.tsx
│ │ │ │ ├── Builder.tsx
│ │ │ │ ├── Call.tsx
│ │ │ │ ├── CallFill.tsx
│ │ │ │ ├── Chat.tsx
│ │ │ │ ├── Chatfill.tsx
│ │ │ │ ├── CheckFill.tsx
│ │ │ │ ├── Close.tsx
│ │ │ │ ├── Delete.tsx
│ │ │ │ ├── Flash.tsx
│ │ │ │ ├── Group.tsx
│ │ │ │ ├── GroupAdd.tsx
│ │ │ │ ├── GroupFill.tsx
│ │ │ │ ├── Info.tsx
│ │ │ │ ├── InfoIcon.tsx
│ │ │ │ ├── Logout.tsx
│ │ │ │ ├── Person.tsx
│ │ │ │ ├── PersonAdd.tsx
│ │ │ │ ├── PersonFill.tsx
│ │ │ │ ├── PersonOff.tsx
│ │ │ │ ├── Reset.tsx
│ │ │ │ ├── UserEmptyIcon.tsx
│ │ │ │ └── VideoCam.tsx
│ │ │ ├── components/
│ │ │ │ ├── AIAgent/
│ │ │ │ │ └── AIAgents.tsx
│ │ │ │ ├── calls/
│ │ │ │ │ ├── CallDetailHelper.tsx
│ │ │ │ │ ├── CallDetails.tsx
│ │ │ │ │ ├── CallHistory.tsx
│ │ │ │ │ ├── CallLogDetailHeader.tsx
│ │ │ │ │ ├── CallParticipants.tsx
│ │ │ │ │ ├── CallRecordings.tsx
│ │ │ │ │ ├── Calls.tsx
│ │ │ │ │ └── icons/
│ │ │ │ │ ├── call-end-fill.tsx
│ │ │ │ │ ├── call-end.tsx
│ │ │ │ │ ├── call-fill.tsx
│ │ │ │ │ ├── call-log-fill.tsx
│ │ │ │ │ ├── call-log.tsx
│ │ │ │ │ ├── call-made-fill.tsx
│ │ │ │ │ ├── call-made.tsx
│ │ │ │ │ ├── call-missed-fill.tsx
│ │ │ │ │ ├── call-missed-outgoing-fill.tsx
│ │ │ │ │ ├── call-missed-outgoing.tsx
│ │ │ │ │ ├── call-missed.tsx
│ │ │ │ │ ├── call-received-fill.tsx
│ │ │ │ │ ├── call-received.tsx
│ │ │ │ │ ├── call.tsx
│ │ │ │ │ ├── cancel-fill.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── play-arrow.tsx
│ │ │ │ ├── conversations/
│ │ │ │ │ ├── helper/
│ │ │ │ │ │ └── GroupListeners.ts
│ │ │ │ │ └── screens/
│ │ │ │ │ ├── AddMember.tsx
│ │ │ │ │ ├── AddMemberStyles.tsx
│ │ │ │ │ ├── BannedMember.tsx
│ │ │ │ │ ├── BannedMemberStyles.tsx
│ │ │ │ │ ├── Conversations.tsx
│ │ │ │ │ ├── CreateConversation.tsx
│ │ │ │ │ ├── GroupInfo.tsx
│ │ │ │ │ ├── GroupInfoStyles.tsx
│ │ │ │ │ ├── Messages.tsx
│ │ │ │ │ ├── OngoingCallScreen.tsx
│ │ │ │ │ ├── SearchMessages.tsx
│ │ │ │ │ ├── ThreadView.tsx
│ │ │ │ │ ├── TransferOwnership.tsx
│ │ │ │ │ ├── TransferOwnershipStyles.tsx
│ │ │ │ │ ├── UserInfo.tsx
│ │ │ │ │ ├── UserInfoStyles.tsx
│ │ │ │ │ ├── ViewMembers.tsx
│ │ │ │ │ └── qr_screen.tsx
│ │ │ │ ├── groups/
│ │ │ │ │ ├── GroupHelper.tsx
│ │ │ │ │ ├── Groups.tsx
│ │ │ │ │ └── styles.ts
│ │ │ │ ├── login/
│ │ │ │ │ ├── AppCredentials.tsx
│ │ │ │ │ ├── SampleUser.tsx
│ │ │ │ │ └── Skeleton.tsx
│ │ │ │ └── users/
│ │ │ │ └── Users.tsx
│ │ │ ├── config/
│ │ │ │ ├── config.json
│ │ │ │ └── store.ts
│ │ │ ├── declarations.d.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useGroupMemberStatus.ts
│ │ │ │ └── useIsKeyboardVisible.ts
│ │ │ ├── navigation/
│ │ │ │ ├── AuthContext.tsx
│ │ │ │ ├── BottomTabNavigator.tsx
│ │ │ │ ├── NavigationService.ts
│ │ │ │ ├── RootStackNavigator.tsx
│ │ │ │ └── types.ts
│ │ │ └── utils/
│ │ │ ├── ActiveChatContext.tsx
│ │ │ ├── AppConstants.tsx
│ │ │ ├── CommonUtils.ts
│ │ │ ├── TooltipMenu.tsx
│ │ │ ├── helper.ts
│ │ │ └── themeTypography.ts
│ │ └── tsconfig.json
│ ├── SampleAppAI/
│ │ ├── .bundle/
│ │ │ └── config
│ │ ├── .eslintrc.js
│ │ ├── .gitignore
│ │ ├── .prettierrc.js
│ │ ├── .watchmanconfig
│ │ ├── App.tsx
│ │ ├── AppErrorBoundary.tsx
│ │ ├── Gemfile
│ │ ├── README.md
│ │ ├── android/
│ │ │ ├── app/
│ │ │ │ ├── build.gradle
│ │ │ │ ├── debug.keystore
│ │ │ │ ├── proguard-rules.pro
│ │ │ │ └── src/
│ │ │ │ └── main/
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── java/
│ │ │ │ │ └── com/
│ │ │ │ │ └── cometchat/
│ │ │ │ │ └── ai/
│ │ │ │ │ └── sampleapp/
│ │ │ │ │ ├── MainActivity.kt
│ │ │ │ │ └── MainApplication.kt
│ │ │ │ └── res/
│ │ │ │ ├── drawable/
│ │ │ │ │ └── rn_edit_text_material.xml
│ │ │ │ └── values/
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── build.gradle
│ │ │ ├── gradle/
│ │ │ │ └── wrapper/
│ │ │ │ ├── gradle-wrapper.jar
│ │ │ │ └── gradle-wrapper.properties
│ │ │ ├── gradle.properties
│ │ │ ├── gradlew
│ │ │ ├── gradlew.bat
│ │ │ └── settings.gradle
│ │ ├── app.json
│ │ ├── babel.config.js
│ │ ├── gesture-handler.js
│ │ ├── gesture-handler.native.js
│ │ ├── index.js
│ │ ├── ios/
│ │ │ ├── .xcode.env
│ │ │ ├── Podfile
│ │ │ ├── SampleApp/
│ │ │ │ ├── AppDelegate.swift
│ │ │ │ ├── Images.xcassets/
│ │ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Info.plist
│ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ └── PrivacyInfo.xcprivacy
│ │ │ ├── SampleApp.xcodeproj/
│ │ │ │ ├── project.pbxproj
│ │ │ │ └── xcshareddata/
│ │ │ │ └── xcschemes/
│ │ │ │ └── SampleApp.xcscheme
│ │ │ └── SampleApp.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ └── WorkspaceSettings.xcsettings
│ │ ├── jest.config.js
│ │ ├── metro.config.js
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── assets/
│ │ │ │ └── icons/
│ │ │ │ ├── AccountCircle.tsx
│ │ │ │ ├── AddComment.tsx
│ │ │ │ ├── ArrowBack.tsx
│ │ │ │ ├── Block.tsx
│ │ │ │ ├── Call.tsx
│ │ │ │ ├── CallFill.tsx
│ │ │ │ ├── Chat.tsx
│ │ │ │ ├── Chatfill.tsx
│ │ │ │ ├── CheckFill.tsx
│ │ │ │ ├── Close.tsx
│ │ │ │ ├── Delete.tsx
│ │ │ │ ├── Group.tsx
│ │ │ │ ├── GroupAdd.tsx
│ │ │ │ ├── GroupFill.tsx
│ │ │ │ ├── Info.tsx
│ │ │ │ ├── InfoIcon.tsx
│ │ │ │ ├── Logout.tsx
│ │ │ │ ├── Person.tsx
│ │ │ │ ├── PersonAdd.tsx
│ │ │ │ ├── PersonFill.tsx
│ │ │ │ ├── PersonOff.tsx
│ │ │ │ ├── UserEmptyIcon.tsx
│ │ │ │ └── VideoCam.tsx
│ │ │ ├── components/
│ │ │ │ ├── AIAgents.tsx
│ │ │ │ ├── Messages.tsx
│ │ │ │ └── login/
│ │ │ │ ├── AppCredentials.tsx
│ │ │ │ ├── SampleUser.tsx
│ │ │ │ └── Skeleton.tsx
│ │ │ ├── declarations.d.ts
│ │ │ ├── navigation/
│ │ │ │ ├── AuthContext.tsx
│ │ │ │ ├── BottomTabNavigator.tsx
│ │ │ │ ├── NavigationService.ts
│ │ │ │ ├── RootStackNavigator.tsx
│ │ │ │ └── types.ts
│ │ │ └── utils/
│ │ │ ├── ActiveChatContext.tsx
│ │ │ ├── AppConstants.tsx
│ │ │ ├── CommonUtils.ts
│ │ │ ├── TooltipMenu.tsx
│ │ │ └── helper.ts
│ │ └── tsconfig.json
│ ├── SampleAppExpo/
│ │ ├── .gitattributes
│ │ ├── .gitignore
│ │ ├── App.tsx
│ │ ├── README.md
│ │ ├── app.json
│ │ ├── gesture-handler.js
│ │ ├── gesture-handler.native.js
│ │ ├── index.js
│ │ ├── metro.config.js
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── assets/
│ │ │ │ └── icons/
│ │ │ │ ├── AccountCircle.tsx
│ │ │ │ ├── AddComment.tsx
│ │ │ │ ├── AiIcon.tsx
│ │ │ │ ├── ArrowBack.tsx
│ │ │ │ ├── Block.tsx
│ │ │ │ ├── Builder.tsx
│ │ │ │ ├── Call.tsx
│ │ │ │ ├── CallFill.tsx
│ │ │ │ ├── Chat.tsx
│ │ │ │ ├── Chatfill.tsx
│ │ │ │ ├── CheckFill.tsx
│ │ │ │ ├── Close.tsx
│ │ │ │ ├── Delete.tsx
│ │ │ │ ├── Flash.tsx
│ │ │ │ ├── Group.tsx
│ │ │ │ ├── GroupAdd.tsx
│ │ │ │ ├── GroupFill.tsx
│ │ │ │ ├── Info.tsx
│ │ │ │ ├── InfoIcon.tsx
│ │ │ │ ├── Logout.tsx
│ │ │ │ ├── Person.tsx
│ │ │ │ ├── PersonAdd.tsx
│ │ │ │ ├── PersonFill.tsx
│ │ │ │ ├── PersonOff.tsx
│ │ │ │ ├── Reset.tsx
│ │ │ │ ├── UserEmptyIcon.tsx
│ │ │ │ └── VideoCam.tsx
│ │ │ ├── components/
│ │ │ │ ├── AIAgent/
│ │ │ │ │ └── AIAgents.tsx
│ │ │ │ ├── calls/
│ │ │ │ │ ├── CallDetailHelper.tsx
│ │ │ │ │ ├── CallDetails.tsx
│ │ │ │ │ ├── CallHistory.tsx
│ │ │ │ │ ├── CallLogDetailHeader.tsx
│ │ │ │ │ ├── CallParticipants.tsx
│ │ │ │ │ ├── CallRecordings.tsx
│ │ │ │ │ ├── Calls.tsx
│ │ │ │ │ └── icons/
│ │ │ │ │ ├── call-end-fill.tsx
│ │ │ │ │ ├── call-end.tsx
│ │ │ │ │ ├── call-fill.tsx
│ │ │ │ │ ├── call-log-fill.tsx
│ │ │ │ │ ├── call-log.tsx
│ │ │ │ │ ├── call-made-fill.tsx
│ │ │ │ │ ├── call-made.tsx
│ │ │ │ │ ├── call-missed-fill.tsx
│ │ │ │ │ ├── call-missed-outgoing-fill.tsx
│ │ │ │ │ ├── call-missed-outgoing.tsx
│ │ │ │ │ ├── call-missed.tsx
│ │ │ │ │ ├── call-received-fill.tsx
│ │ │ │ │ ├── call-received.tsx
│ │ │ │ │ ├── call.tsx
│ │ │ │ │ ├── cancel-fill.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── play-arrow.tsx
│ │ │ │ ├── conversations/
│ │ │ │ │ ├── helper/
│ │ │ │ │ │ └── GroupListeners.ts
│ │ │ │ │ └── screens/
│ │ │ │ │ ├── AddMember.tsx
│ │ │ │ │ ├── AddMemberStyles.tsx
│ │ │ │ │ ├── BannedMember.tsx
│ │ │ │ │ ├── BannedMemberStyles.tsx
│ │ │ │ │ ├── Conversations.tsx
│ │ │ │ │ ├── CreateConversation.tsx
│ │ │ │ │ ├── GroupInfo.tsx
│ │ │ │ │ ├── GroupInfoStyles.tsx
│ │ │ │ │ ├── Messages.tsx
│ │ │ │ │ ├── OngoingCallScreen.tsx
│ │ │ │ │ ├── SearchMessages.tsx
│ │ │ │ │ ├── ThreadView.tsx
│ │ │ │ │ ├── TransferOwnership.tsx
│ │ │ │ │ ├── TransferOwnershipStyles.tsx
│ │ │ │ │ ├── UserInfo.tsx
│ │ │ │ │ ├── UserInfoStyles.tsx
│ │ │ │ │ ├── ViewMembers.tsx
│ │ │ │ │ └── qr_screen.tsx
│ │ │ │ ├── groups/
│ │ │ │ │ ├── GroupHelper.tsx
│ │ │ │ │ ├── Groups.tsx
│ │ │ │ │ └── styles.ts
│ │ │ │ ├── login/
│ │ │ │ │ ├── AppCredentials.tsx
│ │ │ │ │ ├── SampleUser.tsx
│ │ │ │ │ └── Skeleton.tsx
│ │ │ │ └── users/
│ │ │ │ └── Users.tsx
│ │ │ ├── config/
│ │ │ │ ├── config.json
│ │ │ │ └── store.ts
│ │ │ ├── declarations.d.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useGroupMemberStatus.ts
│ │ │ │ └── useIsKeyboardVisible.ts
│ │ │ ├── navigation/
│ │ │ │ ├── AuthContext.tsx
│ │ │ │ ├── BottomTabNavigator.tsx
│ │ │ │ ├── NavigationService.ts
│ │ │ │ ├── RootStackNavigator.tsx
│ │ │ │ └── types.ts
│ │ │ └── utils/
│ │ │ ├── ActiveChatContext.tsx
│ │ │ ├── AppConstants.tsx
│ │ │ ├── CommonUtils.ts
│ │ │ ├── TooltipMenu.tsx
│ │ │ ├── helper.ts
│ │ │ └── themeTypography.ts
│ │ └── tsconfig.json
│ └── SampleAppWithPushNotifications/
│ ├── .bundle/
│ │ └── config
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── .prettierrc.js
│ ├── .vscode/
│ │ └── settings.json
│ ├── .watchmanconfig
│ ├── App.tsx
│ ├── AppErrorBoundary.tsx
│ ├── Gemfile
│ ├── README.md
│ ├── __tests__/
│ │ └── App.test.tsx
│ ├── android/
│ │ ├── app/
│ │ │ ├── build.gradle
│ │ │ ├── cometchat_team_messanger.jks
│ │ │ ├── debug.keystore
│ │ │ ├── google-services.json
│ │ │ ├── proguard-rules.pro
│ │ │ └── src/
│ │ │ └── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java/
│ │ │ │ └── com/
│ │ │ │ └── sampleappwithpushnotifications/
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── MainApplication.kt
│ │ │ └── res/
│ │ │ ├── drawable/
│ │ │ │ └── rn_edit_text_material.xml
│ │ │ └── values/
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ ├── build.gradle
│ │ ├── gradle/
│ │ │ └── wrapper/
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ │ ├── gradle.properties
│ │ ├── gradlew
│ │ ├── gradlew.bat
│ │ └── settings.gradle
│ ├── app.json
│ ├── babel.config.js
│ ├── cometchat_team_messanger.jks
│ ├── gesture-handler.js
│ ├── gesture-handler.native.js
│ ├── google-services.json
│ ├── index.js
│ ├── ios/
│ │ ├── .xcode.env
│ │ ├── GoogleService-Info.plist
│ │ ├── Podfile
│ │ ├── SampleAppWithPushNotifications/
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Images.xcassets/
│ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Info.plist
│ │ │ ├── LaunchScreen.storyboard
│ │ │ ├── PrivacyInfo.xcprivacy
│ │ │ ├── SampleAppWithPushNotifications-Bridging-Header.h
│ │ │ ├── SampleAppWithPushNotifications.entitlements
│ │ │ └── SampleAppWithPushNotificationsRelease.entitlements
│ │ ├── SampleAppWithPushNotifications-Bridging-Header.h
│ │ ├── SampleAppWithPushNotifications.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ └── xcshareddata/
│ │ │ └── xcschemes/
│ │ │ └── SampleAppWithPushNotifications.xcscheme
│ │ └── SampleAppWithPushNotifications.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ └── WorkspaceSettings.xcsettings
│ ├── jest.config.js
│ ├── metro.config.js
│ ├── package.json
│ ├── src/
│ │ ├── assets/
│ │ │ └── icons/
│ │ │ ├── AccountCircle.tsx
│ │ │ ├── AddComment.tsx
│ │ │ ├── AiIcon.tsx
│ │ │ ├── ArrowBack.tsx
│ │ │ ├── Block.tsx
│ │ │ ├── Builder.tsx
│ │ │ ├── Call.tsx
│ │ │ ├── CallFill.tsx
│ │ │ ├── Chat.tsx
│ │ │ ├── Chatfill.tsx
│ │ │ ├── CheckFill.tsx
│ │ │ ├── Close.tsx
│ │ │ ├── Delete.tsx
│ │ │ ├── Flash.tsx
│ │ │ ├── Group.tsx
│ │ │ ├── GroupAdd.tsx
│ │ │ ├── GroupFill.tsx
│ │ │ ├── Info.tsx
│ │ │ ├── InfoIcon.tsx
│ │ │ ├── Logout.tsx
│ │ │ ├── Person.tsx
│ │ │ ├── PersonAdd.tsx
│ │ │ ├── PersonFill.tsx
│ │ │ ├── PersonOff.tsx
│ │ │ ├── Reset.tsx
│ │ │ ├── UserEmptyIcon.tsx
│ │ │ └── VideoCam.tsx
│ │ ├── components/
│ │ │ ├── AIAgent/
│ │ │ │ └── AIAgents.tsx
│ │ │ ├── calls/
│ │ │ │ ├── CallDetailHelper.tsx
│ │ │ │ ├── CallDetails.tsx
│ │ │ │ ├── CallHistory.tsx
│ │ │ │ ├── CallLogDetailHeader.tsx
│ │ │ │ ├── CallParticipants.tsx
│ │ │ │ ├── CallRecordings.tsx
│ │ │ │ ├── Calls.tsx
│ │ │ │ └── icons/
│ │ │ │ ├── call-end-fill.tsx
│ │ │ │ ├── call-end.tsx
│ │ │ │ ├── call-fill.tsx
│ │ │ │ ├── call-log-fill.tsx
│ │ │ │ ├── call-log.tsx
│ │ │ │ ├── call-made-fill.tsx
│ │ │ │ ├── call-made.tsx
│ │ │ │ ├── call-missed-fill.tsx
│ │ │ │ ├── call-missed-outgoing-fill.tsx
│ │ │ │ ├── call-missed-outgoing.tsx
│ │ │ │ ├── call-missed.tsx
│ │ │ │ ├── call-received-fill.tsx
│ │ │ │ ├── call-received.tsx
│ │ │ │ ├── call.tsx
│ │ │ │ ├── cancel-fill.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── play-arrow.tsx
│ │ │ ├── conversations/
│ │ │ │ ├── helper/
│ │ │ │ │ └── GroupListeners.ts
│ │ │ │ └── screens/
│ │ │ │ ├── AddMember.tsx
│ │ │ │ ├── AddMemberStyles.tsx
│ │ │ │ ├── BannedMember.tsx
│ │ │ │ ├── BannedMemberStyles.tsx
│ │ │ │ ├── Conversations.tsx
│ │ │ │ ├── CreateConversation.tsx
│ │ │ │ ├── GroupInfo.tsx
│ │ │ │ ├── GroupInfoStyles.tsx
│ │ │ │ ├── Messages.tsx
│ │ │ │ ├── OngoingCallScreen.tsx
│ │ │ │ ├── SearchMessages.tsx
│ │ │ │ ├── ThreadView.tsx
│ │ │ │ ├── TransferOwnership.tsx
│ │ │ │ ├── TransferOwnershipStyles.tsx
│ │ │ │ ├── UserInfo.tsx
│ │ │ │ ├── UserInfoStyles.tsx
│ │ │ │ ├── ViewMembers.tsx
│ │ │ │ └── qr_screen.tsx
│ │ │ ├── groups/
│ │ │ │ ├── GroupHelper.tsx
│ │ │ │ ├── Groups.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── login/
│ │ │ │ ├── AppCredentials.tsx
│ │ │ │ ├── SampleUser.tsx
│ │ │ │ └── Skeleton.tsx
│ │ │ └── users/
│ │ │ └── Users.tsx
│ │ ├── config/
│ │ │ ├── config.json
│ │ │ └── store.ts
│ │ ├── declarations.d.ts
│ │ ├── hooks/
│ │ │ ├── useGroupMemberStatus.ts
│ │ │ └── useIsKeyboardVisible.ts
│ │ ├── navigation/
│ │ │ ├── AuthContext.tsx
│ │ │ ├── BottomTabNavigator.tsx
│ │ │ ├── NavigationService.ts
│ │ │ ├── RootStackNavigator.tsx
│ │ │ └── types.ts
│ │ └── utils/
│ │ ├── ActiveChatContext.tsx
│ │ ├── AppConstants.tsx
│ │ ├── CommonUtils.ts
│ │ ├── PendingCallManager.ts
│ │ ├── PushNotification.tsx
│ │ ├── TooltipMenu.tsx
│ │ ├── VoipNotificationHandler.ts
│ │ ├── helper.ts
│ │ └── themeTypography.ts
│ └── tsconfig.json
└── packages/
└── ChatUiKit/
├── .gitignore
├── .prettierrc.json
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── SECURITY.md
├── SUPPORT.md
├── android/
│ ├── .settings/
│ │ └── org.eclipse.buildship.core.prefs
│ ├── build.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── settings.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ ├── reactnativecometchatuikit/
│ │ │ ├── CometChatImageViewer.java
│ │ │ ├── CometChatSoundModule.java
│ │ │ ├── CometChatVideoPlayer.java
│ │ │ ├── CometChatWebView.java
│ │ │ ├── CometchatUiKitModule.java
│ │ │ ├── CometchatUiKitPackage.java
│ │ │ ├── FileManager.java
│ │ │ ├── ImageManager.java
│ │ │ ├── ProximityModule.java
│ │ │ ├── ReactVideoView.java
│ │ │ ├── ReactVideoViewManager.java
│ │ │ ├── RichTextEditorPackage.kt
│ │ │ ├── RichTextEditorView.kt
│ │ │ ├── RichTextEditorViewManager.kt
│ │ │ ├── SoundPlayer.java
│ │ │ ├── TimeZoneCodeManager.java
│ │ │ └── WebViewManager.java
│ │ ├── scalablevideoview/
│ │ │ ├── BuildConfig.java
│ │ │ ├── PivotPoint.java
│ │ │ ├── ScalableType.java
│ │ │ ├── ScalableVideoView.java
│ │ │ ├── ScaleManager.java
│ │ │ └── Size.java
│ │ └── zipfile/
│ │ ├── APEZProvider.java
│ │ ├── APKExpansionSupport.java
│ │ └── ZipResourceFile.java
│ └── res/
│ ├── values/
│ │ └── attrs.xml
│ └── xml/
│ └── filepaths.xml
├── babel.config.js
├── ios/
│ ├── CometChatSoundModule.h
│ ├── CometChatSoundModule.m
│ ├── CometchatUiKit.h
│ ├── CometchatUiKit.mm
│ ├── CometchatUiKit.xcodeproj/
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ └── IDEWorkspaceChecks.plist
│ ├── CommonUtil.h
│ ├── CommonUtil.m
│ ├── FileManager.h
│ ├── FileManager.m
│ ├── Proximity.h
│ ├── Proximity.m
│ ├── RichTextEditorView.swift
│ ├── RichTextEditorViewManager.m
│ ├── RichTextEditorViewManager.swift
│ ├── SoundPlayer.h
│ ├── SoundPlayer.m
│ ├── TimeZoneCodeManager.h
│ ├── TimeZoneCodeManager.m
│ ├── ToolbarIcons.swift
│ ├── Video/
│ │ ├── CCRCTVideo.h
│ │ ├── CCRCTVideo.m
│ │ ├── CCRCTVideoManager.h
│ │ ├── CCRCTVideoManager.m
│ │ ├── CCRCTVideoPlayerViewController.h
│ │ ├── CCRCTVideoPlayerViewController.m
│ │ ├── CCRCTVideoPlayerViewControllerDelegate.h
│ │ ├── UIView+FindUIViewController.h
│ │ └── UIView+FindUIViewController.m
│ ├── VideoManager.h
│ ├── VideoManager.m
│ ├── VideoPickerModule.h
│ ├── VideoPickerModule.mm
│ ├── WebViewManager.h
│ └── WebViewManager.m
├── package.json
├── packager.js
├── react-native-cometchat-ui-kit.podspec
├── src/
│ ├── CometChatAIAssistantChatHistory/
│ │ ├── CometChatAIAssistantChatHistory.tsx
│ │ ├── Skeleton.tsx
│ │ ├── index.ts
│ │ └── style.ts
│ ├── CometChatCompactMessageComposer/
│ │ ├── CometChatCompactMessageComposer.tsx
│ │ ├── SingleLineTextComposerConfiguration.tsx
│ │ ├── heightUtils.ts
│ │ ├── index.ts
│ │ ├── resources/
│ │ │ └── index.ts
│ │ └── styles.ts
│ ├── CometChatConversations/
│ │ ├── CometChatConversations.tsx
│ │ ├── Skeleton.tsx
│ │ ├── index.ts
│ │ ├── resources/
│ │ │ └── index.ts
│ │ └── style.ts
│ ├── CometChatGroupMembers/
│ │ ├── CometChatGroupMembers.tsx
│ │ ├── Skeleton.tsx
│ │ ├── index.ts
│ │ ├── resources/
│ │ │ └── index.ts
│ │ └── style.ts
│ ├── CometChatGroups/
│ │ ├── CometChatGroups.tsx
│ │ ├── GroupsStyle.ts
│ │ ├── Skeleton.tsx
│ │ ├── index.ts
│ │ ├── resources/
│ │ │ └── index.ts
│ │ └── style.ts
│ ├── CometChatMessageComposer/
│ │ ├── CometChatMessageComposer.tsx
│ │ ├── index.ts
│ │ ├── resources/
│ │ │ └── index.ts
│ │ └── styles.tsx
│ ├── CometChatMessageHeader/
│ │ ├── CometChatMessageHeader.tsx
│ │ ├── index.ts
│ │ ├── listners.ts
│ │ └── styles.ts
│ ├── CometChatMessageInformation/
│ │ ├── CometChatMessageInformation.tsx
│ │ ├── Skeleton.tsx
│ │ ├── index.ts
│ │ ├── resources/
│ │ │ └── index.ts
│ │ └── styles.ts
│ ├── CometChatMessageList/
│ │ ├── CometChatMessageList.tsx
│ │ ├── Skeleton.tsx
│ │ ├── components/
│ │ │ ├── MessageListItem.tsx
│ │ │ ├── MessageModals.tsx
│ │ │ ├── MessageOptionsSheet.tsx
│ │ │ └── ReactionModals.tsx
│ │ ├── index.ts
│ │ ├── resources/
│ │ │ └── index.ts
│ │ └── style.ts
│ ├── CometChatRichTextEditor/
│ │ ├── RichTextEditorViewNativeComponent.ts
│ │ ├── index.tsx
│ │ └── types.ts
│ ├── CometChatSearch/
│ │ ├── CometChatSearch.tsx
│ │ ├── SearchConstants.ts
│ │ ├── Skeleton.tsx
│ │ ├── index.ts
│ │ └── style.ts
│ ├── CometChatThreadHeader/
│ │ ├── CometChatThreadHeader.tsx
│ │ ├── Header.tsx
│ │ ├── index.ts
│ │ ├── resources/
│ │ │ └── index.ts
│ │ └── style.ts
│ ├── CometChatUsers/
│ │ ├── CometChatUsers.tsx
│ │ ├── Skeleton.tsx
│ │ ├── index.ts
│ │ └── style.ts
│ ├── calls/
│ │ ├── CallEvents.ts
│ │ ├── CallUtils.ts
│ │ ├── CallingConfiguration.ts
│ │ ├── CallingExtension.tsx
│ │ ├── CallingExtensionDecorator.tsx
│ │ ├── CallingPackage.ts
│ │ ├── CometChatCallBubble/
│ │ │ ├── CometChatCallBubble.tsx
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── styles.ts
│ │ ├── CometChatCallButtons/
│ │ │ ├── CometChatCallButtonConfiguration.ts
│ │ │ ├── CometChatCallButtons.tsx
│ │ │ ├── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatCallLogs/
│ │ │ ├── CometChatCallLogs.tsx
│ │ │ ├── Skeleton.tsx
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatIncomingCall/
│ │ │ ├── CometChatIncomingCall.tsx
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatOngoingCall/
│ │ │ ├── CometChatOngoingCall.tsx
│ │ │ └── index.ts
│ │ ├── CometChatOutgoingCall/
│ │ │ ├── CometChatOutgoingCall.tsx
│ │ │ ├── OutgoingCallConfiguration.ts
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── styles.ts
│ │ ├── index.ts
│ │ └── resources/
│ │ └── index.ts
│ ├── extensions/
│ │ ├── CollaborativeBubble/
│ │ │ ├── CometChatCollaborativeBubble.tsx
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── style.ts
│ │ ├── CollaborativeDocument/
│ │ │ ├── CollaborativeDocumentExtension.tsx
│ │ │ ├── CollaborativeDocumentExtensionDecorator.tsx
│ │ │ ├── index.ts
│ │ │ └── resources/
│ │ │ └── index.ts
│ │ ├── CollaborativeWhiteboard/
│ │ │ ├── CollaborativeWhiteboardExtension.tsx
│ │ │ ├── CollaborativeWhiteboardExtensionDecorator.tsx
│ │ │ ├── index.ts
│ │ │ └── resources/
│ │ │ └── index.ts
│ │ ├── ExtensionConstants.tsx
│ │ ├── ExtensionModerator.tsx
│ │ ├── LinkPreview/
│ │ │ ├── LinkPreviewBubble.tsx
│ │ │ ├── LinkPreviewExtension.tsx
│ │ │ ├── LinkPreviewExtensionDecorator.tsx
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── style.ts
│ │ ├── MessageTranslation/
│ │ │ ├── MessageTranslationBubble.tsx
│ │ │ ├── MessageTranslationDecorator.tsx
│ │ │ ├── MessageTranslationExtension.tsx
│ │ │ ├── index.ts
│ │ │ └── resources/
│ │ │ └── index.ts
│ │ ├── Polls/
│ │ │ ├── Header.tsx
│ │ │ ├── Polls.tsx
│ │ │ ├── PollsBubble.tsx
│ │ │ ├── PollsConfigurations.ts
│ │ │ ├── PollsDecorator.tsx
│ │ │ ├── PollsExtension.tsx
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── style.ts
│ │ ├── Stickers/
│ │ │ ├── CometChatStickerKeyboard/
│ │ │ │ ├── CometChatStickerKeyboard.tsx
│ │ │ │ ├── Skeleton.tsx
│ │ │ │ ├── StickerKeyboardConfiguration.ts
│ │ │ │ ├── hooks.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── StickerConfiguration.ts
│ │ │ ├── StickersBubble.tsx
│ │ │ ├── StickersExtension.ts
│ │ │ ├── StickersExtensionDecorator.tsx
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── style.ts
│ │ ├── ThumbnailGeneration/
│ │ │ ├── ThumbnailGenerationDecorator.tsx
│ │ │ ├── ThumbnailGenerationExtension.tsx
│ │ │ ├── index.ts
│ │ │ └── resources/
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── imageresolver.d.ts
│ ├── index.ts
│ ├── shared/
│ │ ├── CometChatUiKit/
│ │ │ ├── CometChatUIKit.ts
│ │ │ ├── CometChatUIKitHelper.ts
│ │ │ ├── UIKitSettings.ts
│ │ │ └── index.ts
│ │ ├── assets/
│ │ │ └── images/
│ │ │ └── index.ts
│ │ ├── base/
│ │ │ ├── BaseStyle.ts
│ │ │ ├── BorderStyle.ts
│ │ │ ├── FontStyle.ts
│ │ │ ├── ShadowStyle.ts
│ │ │ ├── Types.ts
│ │ │ ├── index.ts
│ │ │ └── vars.ts
│ │ ├── constants/
│ │ │ └── UIKitConstants.ts
│ │ ├── events/
│ │ │ ├── CometChatMessageEvents/
│ │ │ │ └── index.ts
│ │ │ ├── CometChatUIEventHandler/
│ │ │ │ ├── CometChatUIEventHandler.ts
│ │ │ │ └── Listener.ts
│ │ │ ├── CometChatUIEvents.ts
│ │ │ ├── ListenerInitializer.ts
│ │ │ ├── conversations.ts
│ │ │ ├── groups.ts
│ │ │ ├── index.ts
│ │ │ └── messages.ts
│ │ ├── formatters/
│ │ │ ├── CometChatMentionsFormatter/
│ │ │ │ ├── CometChatMentionsFormatter.tsx
│ │ │ │ ├── MentionTextStyle.ts
│ │ │ │ └── index.ts
│ │ │ ├── CometChatRichTextFormatter/
│ │ │ │ └── index.tsx
│ │ │ ├── CometChatTextFormatter.ts
│ │ │ ├── CometChatUrlsFormatter/
│ │ │ │ └── index.tsx
│ │ │ └── index.ts
│ │ ├── framework/
│ │ │ ├── ChatConfigurator.ts
│ │ │ ├── DataSource.ts
│ │ │ ├── DataSourceDecorator.tsx
│ │ │ ├── ExtensionsDataSource.tsx
│ │ │ ├── MessageDataSource.tsx
│ │ │ ├── index.ts
│ │ │ └── resources/
│ │ │ └── index.ts
│ │ ├── helper/
│ │ │ ├── LocalizedDateHelper.ts
│ │ │ ├── Toast.tsx
│ │ │ ├── composerHelpers.ts
│ │ │ ├── dateHelper.ts
│ │ │ ├── functions.js
│ │ │ ├── helperFunctions.ts
│ │ │ ├── types/
│ │ │ │ └── index.ts
│ │ │ ├── useKeyboard.tsx
│ │ │ └── useLocalizedDateHook.tsx
│ │ ├── icons/
│ │ │ ├── Icon.tsx
│ │ │ ├── components/
│ │ │ │ ├── ListItemCheck.tsx
│ │ │ │ ├── StickerBase-icon.tsx
│ │ │ │ ├── account-circle-fill.tsx
│ │ │ │ ├── account-circle.tsx
│ │ │ │ ├── activity.tsx
│ │ │ │ ├── add-a-photo-fill.tsx
│ │ │ │ ├── add-a-photo.tsx
│ │ │ │ ├── add-box-fill.tsx
│ │ │ │ ├── add-box.tsx
│ │ │ │ ├── add-call-fill.tsx
│ │ │ │ ├── add-call.tsx
│ │ │ │ ├── add-circle-fill.tsx
│ │ │ │ ├── add-circle.tsx
│ │ │ │ ├── add-comment-fill.tsx
│ │ │ │ ├── add-comment.tsx
│ │ │ │ ├── add-fill.tsx
│ │ │ │ ├── add-reaction-fill.tsx
│ │ │ │ ├── add-reaction.tsx
│ │ │ │ ├── add.tsx
│ │ │ │ ├── ai-chat-history.tsx
│ │ │ │ ├── ai-conversation-summary-fill.tsx
│ │ │ │ ├── ai-conversation-summary.tsx
│ │ │ │ ├── ai-copy-option.tsx
│ │ │ │ ├── ai-fill.tsx
│ │ │ │ ├── ai-new-chat.tsx
│ │ │ │ ├── ai-send-button.tsx
│ │ │ │ ├── ai-suggest-reply-fill.tsx
│ │ │ │ ├── ai-suggest-reply.tsx
│ │ │ │ ├── ai.tsx
│ │ │ │ ├── alternate-email-fill.tsx
│ │ │ │ ├── alternate-email.tsx
│ │ │ │ ├── animals-&-nature.tsx
│ │ │ │ ├── archive-fill.tsx
│ │ │ │ ├── archive.tsx
│ │ │ │ ├── arrow-back-fill.tsx
│ │ │ │ ├── arrow-back.tsx
│ │ │ │ ├── arrow-forward-fill.tsx
│ │ │ │ ├── arrow-forward.tsx
│ │ │ │ ├── attach-file-fill.tsx
│ │ │ │ ├── attach-file.tsx
│ │ │ │ ├── audio-file-type.tsx
│ │ │ │ ├── bar-chart-fill.tsx
│ │ │ │ ├── bar-chart.tsx
│ │ │ │ ├── base-icon.tsx
│ │ │ │ ├── bedtime-fill.tsx
│ │ │ │ ├── bedtime.tsx
│ │ │ │ ├── block-fill.tsx
│ │ │ │ ├── block.tsx
│ │ │ │ ├── bookmark-add-fill.tsx
│ │ │ │ ├── bookmark-add.tsx
│ │ │ │ ├── bookmark-fill.tsx
│ │ │ │ ├── bookmark.tsx
│ │ │ │ ├── calendar-add-on-fill.tsx
│ │ │ │ ├── calendar-add-on.tsx
│ │ │ │ ├── calendar-today-fill.tsx
│ │ │ │ ├── calendar-today.tsx
│ │ │ │ ├── call-end-fill.tsx
│ │ │ │ ├── call-end.tsx
│ │ │ │ ├── call-fill.tsx
│ │ │ │ ├── call-log-fill.tsx
│ │ │ │ ├── call-log.tsx
│ │ │ │ ├── call-made-fill.tsx
│ │ │ │ ├── call-made.tsx
│ │ │ │ ├── call-missed-fill.tsx
│ │ │ │ ├── call-missed-outgoing-fill.tsx
│ │ │ │ ├── call-missed-outgoing.tsx
│ │ │ │ ├── call-missed.tsx
│ │ │ │ ├── call-received-fill.tsx
│ │ │ │ ├── call-received.tsx
│ │ │ │ ├── call.tsx
│ │ │ │ ├── cancel-fill.tsx
│ │ │ │ ├── cancel.tsx
│ │ │ │ ├── change-circle-fill.tsx
│ │ │ │ ├── change-circle.tsx
│ │ │ │ ├── chat-bot-fill.tsx
│ │ │ │ ├── chat-bot.tsx
│ │ │ │ ├── chat-fill.tsx
│ │ │ │ ├── chat.tsx
│ │ │ │ ├── check-circle-fill.tsx
│ │ │ │ ├── check-circle.tsx
│ │ │ │ ├── check-fill.tsx
│ │ │ │ ├── check.tsx
│ │ │ │ ├── chevron-left-fill.tsx
│ │ │ │ ├── chevron-left.tsx
│ │ │ │ ├── chevron-right-fill.tsx
│ │ │ │ ├── chevron-right.tsx
│ │ │ │ ├── close-fill.tsx
│ │ │ │ ├── close.tsx
│ │ │ │ ├── code-blocks.tsx
│ │ │ │ ├── code.tsx
│ │ │ │ ├── collaborative-document-fill.tsx
│ │ │ │ ├── collaborative-document-icon.tsx
│ │ │ │ ├── collaborative-document.tsx
│ │ │ │ ├── collaborative-whiteboard-fill.tsx
│ │ │ │ ├── collaborative-whiteboard-icon.tsx
│ │ │ │ ├── collaborative-whiteboard.tsx
│ │ │ │ ├── content-copy-fill.tsx
│ │ │ │ ├── content-copy.tsx
│ │ │ │ ├── crop-fill.tsx
│ │ │ │ ├── crop.tsx
│ │ │ │ ├── delete-fill.tsx
│ │ │ │ ├── delete.tsx
│ │ │ │ ├── description-fill.tsx
│ │ │ │ ├── description.tsx
│ │ │ │ ├── document-file-type.tsx
│ │ │ │ ├── documents.tsx
│ │ │ │ ├── done-all-fill.tsx
│ │ │ │ ├── done-all.tsx
│ │ │ │ ├── download-fill.tsx
│ │ │ │ ├── download.tsx
│ │ │ │ ├── edit-calendar-fill.tsx
│ │ │ │ ├── edit-calendar.tsx
│ │ │ │ ├── edit-fill.tsx
│ │ │ │ ├── edit-square-fill.tsx
│ │ │ │ ├── edit-square.tsx
│ │ │ │ ├── edit.tsx
│ │ │ │ ├── empty-search.tsx
│ │ │ │ ├── empty-state.tsx
│ │ │ │ ├── error-fill.tsx
│ │ │ │ ├── error-state.tsx
│ │ │ │ ├── error.tsx
│ │ │ │ ├── event-available-fill.tsx
│ │ │ │ ├── event-available.tsx
│ │ │ │ ├── event-fill.tsx
│ │ │ │ ├── event.tsx
│ │ │ │ ├── favorite-fill.tsx
│ │ │ │ ├── favorite.tsx
│ │ │ │ ├── filter-list-fill.tsx
│ │ │ │ ├── filter-list-off-fill.tsx
│ │ │ │ ├── filter-list-off.tsx
│ │ │ │ ├── filter-list.tsx
│ │ │ │ ├── flags.tsx
│ │ │ │ ├── flip-camera-ios-fill.tsx
│ │ │ │ ├── flip-camera-ios.tsx
│ │ │ │ ├── food-&-drink.tsx
│ │ │ │ ├── format-bold.tsx
│ │ │ │ ├── format-italic.tsx
│ │ │ │ ├── format-list-bulleted.tsx
│ │ │ │ ├── format-list-numbered.tsx
│ │ │ │ ├── format-quote.tsx
│ │ │ │ ├── format-strikethrough.tsx
│ │ │ │ ├── format-underlined.tsx
│ │ │ │ ├── forward-fill.tsx
│ │ │ │ ├── forward.tsx
│ │ │ │ ├── gif-fill.tsx
│ │ │ │ ├── gif.tsx
│ │ │ │ ├── graphic-eq-fill.tsx
│ │ │ │ ├── graphic-eq.tsx
│ │ │ │ ├── group-add.tsx
│ │ │ │ ├── group-fill.tsx
│ │ │ │ ├── group.tsx
│ │ │ │ ├── help-fill.tsx
│ │ │ │ ├── help.tsx
│ │ │ │ ├── history-fill.tsx
│ │ │ │ ├── history.tsx
│ │ │ │ ├── image-file-type.tsx
│ │ │ │ ├── incoming-audio-fill.tsx
│ │ │ │ ├── incoming-audio.tsx
│ │ │ │ ├── incoming-video-fill.tsx
│ │ │ │ ├── incoming-video.tsx
│ │ │ │ ├── info-fill.tsx
│ │ │ │ ├── info.tsx
│ │ │ │ ├── keep-fill.tsx
│ │ │ │ ├── keep-off-fill.tsx
│ │ │ │ ├── keep-off.tsx
│ │ │ │ ├── keep.tsx
│ │ │ │ ├── keyboard-arrow-down-fill.tsx
│ │ │ │ ├── keyboard-arrow-down.tsx
│ │ │ │ ├── keyboard-arrow-up-fill.tsx
│ │ │ │ ├── keyboard-arrow-up.tsx
│ │ │ │ ├── keyboard-fill.tsx
│ │ │ │ ├── keyboard.tsx
│ │ │ │ ├── language-fill.tsx
│ │ │ │ ├── language.tsx
│ │ │ │ ├── link-fill.tsx
│ │ │ │ ├── link.tsx
│ │ │ │ ├── loading-fill.tsx
│ │ │ │ ├── loading.tsx
│ │ │ │ ├── location-on-fill.tsx
│ │ │ │ ├── location-on.tsx
│ │ │ │ ├── lock-fill.tsx
│ │ │ │ ├── lock-open-fill.tsx
│ │ │ │ ├── lock-open.tsx
│ │ │ │ ├── lock.tsx
│ │ │ │ ├── logout-fill.tsx
│ │ │ │ ├── logout.tsx
│ │ │ │ ├── mail-fill.tsx
│ │ │ │ ├── mail.tsx
│ │ │ │ ├── message-blocked.tsx
│ │ │ │ ├── mic-fill.tsx
│ │ │ │ ├── mic-off-fill.tsx
│ │ │ │ ├── mic-off.tsx
│ │ │ │ ├── mic.tsx
│ │ │ │ ├── missed-video-call-fill.tsx
│ │ │ │ ├── missed-video-call.tsx
│ │ │ │ ├── mood-fill.tsx
│ │ │ │ ├── mood.tsx
│ │ │ │ ├── more-vert-fill.tsx
│ │ │ │ ├── more-vert.tsx
│ │ │ │ ├── near-me-fill.tsx
│ │ │ │ ├── near-me.tsx
│ │ │ │ ├── no-photography-fill.tsx
│ │ │ │ ├── no-photography.tsx
│ │ │ │ ├── notifications-fill.tsx
│ │ │ │ ├── notifications-off-fill.tsx
│ │ │ │ ├── notifications-off.tsx
│ │ │ │ ├── notifications.tsx
│ │ │ │ ├── objects.tsx
│ │ │ │ ├── outgoing-audio-fill.tsx
│ │ │ │ ├── outgoing-audio.tsx
│ │ │ │ ├── outgoing-video-fill.tsx
│ │ │ │ ├── outgoing-video.tsx
│ │ │ │ ├── pause-fill.tsx
│ │ │ │ ├── pause.tsx
│ │ │ │ ├── pdf-file-type.tsx
│ │ │ │ ├── person-add-fill.tsx
│ │ │ │ ├── person-add.tsx
│ │ │ │ ├── person-fill.tsx
│ │ │ │ ├── person-off.tsx
│ │ │ │ ├── person.tsx
│ │ │ │ ├── phone-in-talk-fill.tsx
│ │ │ │ ├── phone-in-talk.tsx
│ │ │ │ ├── phone-incoming-fill.tsx
│ │ │ │ ├── phone-incoming.tsx
│ │ │ │ ├── phone-missed-fill.tsx
│ │ │ │ ├── phone-missed.tsx
│ │ │ │ ├── phone-outgoing-fill.tsx
│ │ │ │ ├── phone-outgoing.tsx
│ │ │ │ ├── photo-camera-fill.tsx
│ │ │ │ ├── photo-camera.tsx
│ │ │ │ ├── photo-fill.tsx
│ │ │ │ ├── photo.tsx
│ │ │ │ ├── play-arrow-fill.tsx
│ │ │ │ ├── play-arrow.tsx
│ │ │ │ ├── play-circle-fill.tsx
│ │ │ │ ├── play-circle.tsx
│ │ │ │ ├── poll-Icon.tsx
│ │ │ │ ├── poll-fill.tsx
│ │ │ │ ├── poll.tsx
│ │ │ │ ├── presentation-file-type.tsx
│ │ │ │ ├── rearrange-fill.tsx
│ │ │ │ ├── rearrange.tsx
│ │ │ │ ├── refresh-fill.tsx
│ │ │ │ ├── refresh.tsx
│ │ │ │ ├── reply-all-fill.tsx
│ │ │ │ ├── reply-all.tsx
│ │ │ │ ├── reply-fill.tsx
│ │ │ │ ├── reply.tsx
│ │ │ │ ├── retry-fill.tsx
│ │ │ │ ├── retry.tsx
│ │ │ │ ├── right-arrow.tsx
│ │ │ │ ├── schedule-fill.tsx
│ │ │ │ ├── schedule.tsx
│ │ │ │ ├── screen-share-fill.tsx
│ │ │ │ ├── screen-share.tsx
│ │ │ │ ├── search-fill.tsx
│ │ │ │ ├── search.tsx
│ │ │ │ ├── send-fill.tsx
│ │ │ │ ├── send.tsx
│ │ │ │ ├── settings-fill.tsx
│ │ │ │ ├── settings.tsx
│ │ │ │ ├── share-fill.tsx
│ │ │ │ ├── share.tsx
│ │ │ │ ├── shield-fill.tsx
│ │ │ │ ├── shield.tsx
│ │ │ │ ├── smileys-&-people.tsx
│ │ │ │ ├── spreadsheet-file-type.tsx
│ │ │ │ ├── star-fill.tsx
│ │ │ │ ├── star.tsx
│ │ │ │ ├── sticker-fill.tsx
│ │ │ │ ├── sticker.tsx
│ │ │ │ ├── stop-circle-fill.tsx
│ │ │ │ ├── stop-circle.tsx
│ │ │ │ ├── stop-fill.tsx
│ │ │ │ ├── stop-screen-share-fill.tsx
│ │ │ │ ├── stop-screen-share.tsx
│ │ │ │ ├── stop.tsx
│ │ │ │ ├── subdirectory-arrow-left-fill.tsx
│ │ │ │ ├── subdirectory-arrow-left.tsx
│ │ │ │ ├── subdirectory-arrow-right-fill.tsx
│ │ │ │ ├── subdirectory-arrow-right.tsx
│ │ │ │ ├── sunny-fill.tsx
│ │ │ │ ├── sunny.tsx
│ │ │ │ ├── symbols.tsx
│ │ │ │ ├── text-file-type.tsx
│ │ │ │ ├── thumb-down-fill.tsx
│ │ │ │ ├── thumb-down.tsx
│ │ │ │ ├── thumb-up-fill.tsx
│ │ │ │ ├── thumb-up.tsx
│ │ │ │ ├── translate-fill.tsx
│ │ │ │ ├── translate.tsx
│ │ │ │ ├── travel-&-places.tsx
│ │ │ │ ├── unknown-file-type.tsx
│ │ │ │ ├── unread.tsx
│ │ │ │ ├── upload-fill.tsx
│ │ │ │ ├── upload.tsx
│ │ │ │ ├── user-empty-icon.tsx
│ │ │ │ ├── video-call-fill.tsx
│ │ │ │ ├── video-call.tsx
│ │ │ │ ├── video-file-type.tsx
│ │ │ │ ├── videocam-fill.tsx
│ │ │ │ ├── videocam-off-fill.tsx
│ │ │ │ ├── videocam-off.tsx
│ │ │ │ ├── videocam.tsx
│ │ │ │ ├── visibility-off-fill.tsx
│ │ │ │ ├── visibility-off.tsx
│ │ │ │ ├── volume-down-fill.tsx
│ │ │ │ ├── volume-down.tsx
│ │ │ │ ├── volume-mute-fill.tsx
│ │ │ │ ├── volume-mute.tsx
│ │ │ │ ├── volume-off-fill.tsx
│ │ │ │ ├── volume-off.tsx
│ │ │ │ ├── volume-up-fill.tsx
│ │ │ │ ├── volume-up.tsx
│ │ │ │ ├── warning-fill.tsx
│ │ │ │ ├── warning.tsx
│ │ │ │ ├── wifi-fill.tsx
│ │ │ │ ├── wifi.tsx
│ │ │ │ ├── zip-file-type.tsx
│ │ │ │ ├── zoom-in-map-fill.tsx
│ │ │ │ ├── zoom-in-map.tsx
│ │ │ │ ├── zoom-out-map-fill.tsx
│ │ │ │ └── zoom-out-map.tsx
│ │ │ └── icon-mapping.tsx
│ │ ├── index.ts
│ │ ├── libs/
│ │ │ ├── ImageZoom/
│ │ │ │ ├── ReactNativeZoomableView.tsx
│ │ │ │ ├── ReactNativeZoomableViewWithGestures.tsx
│ │ │ │ ├── helper.ts
│ │ │ │ ├── index.tsx
│ │ │ │ └── types/
│ │ │ │ └── index.ts
│ │ │ └── VideoPlayer/
│ │ │ ├── DRMType.js
│ │ │ ├── FilterType.js
│ │ │ ├── TextTrackType.js
│ │ │ └── VideoResizeMode.js
│ │ ├── modals/
│ │ │ ├── CometChatAIAssistantTools.ts
│ │ │ ├── CometChatMessageOption.ts
│ │ │ ├── CometChatMessageTemplate.ts
│ │ │ ├── StreamMessage.ts
│ │ │ └── index.ts
│ │ ├── resources/
│ │ │ ├── CometChatLocalizeNew/
│ │ │ │ ├── CometChatI18nProvider.tsx
│ │ │ │ ├── CometChatLocalizationHelper.ts
│ │ │ │ ├── CometChatLocalizeContext.tsx
│ │ │ │ ├── LocalizationManager.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── resources/
│ │ │ │ │ ├── de/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── en/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── es/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── fr/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── hi/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── hu/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── it/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── ja/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── ko/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── lt/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── ms/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── nl/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── pt/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── ru/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── sv/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── tr/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ ├── zh/
│ │ │ │ │ │ └── translation.json
│ │ │ │ │ └── zh-tw/
│ │ │ │ │ └── translation.json
│ │ │ │ ├── type.ts
│ │ │ │ └── useCometChatTranslationHook.ts
│ │ │ ├── CometChatSoundManager/
│ │ │ │ ├── CometChatSoundManager.js
│ │ │ │ ├── index.ts
│ │ │ │ ├── resources/
│ │ │ │ │ └── index.js
│ │ │ │ └── sound.js
│ │ │ └── index.ts
│ │ ├── services/
│ │ │ └── stream-message.service.ts
│ │ ├── utils/
│ │ │ ├── CometChatMessageHelper/
│ │ │ │ └── index.ts
│ │ │ ├── CometChatMessagePreview/
│ │ │ │ ├── CometChatMessagePreview.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── resources/
│ │ │ │ │ └── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── CommonUtils.ts
│ │ │ ├── MarkdownUtils.ts
│ │ │ ├── MentionUtils.ts
│ │ │ ├── MessageReceiptUtils.ts
│ │ │ ├── MessageUtils.tsx
│ │ │ ├── NetworkUtils.tsx
│ │ │ ├── PermissionUtil.ts
│ │ │ ├── conversationUtils.ts
│ │ │ ├── icsToJson.js
│ │ │ └── index.ts
│ │ └── views/
│ │ ├── CometChatAIAssistantMessageBubble/
│ │ │ ├── CometChatAIAssistantMessageBubble.tsx
│ │ │ └── index.ts
│ │ ├── CometChatAICard/
│ │ │ ├── CometChatAICard.tsx
│ │ │ └── resources/
│ │ │ └── index.ts
│ │ ├── CometChatActionSheet/
│ │ │ ├── ActionItem.ts
│ │ │ ├── CometChatActionSheet.tsx
│ │ │ ├── hooks.ts
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatAudioBubble/
│ │ │ ├── AnimatedAudioWaves.tsx
│ │ │ ├── CometChatAudioBubble.tsx
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatAvatar/
│ │ │ ├── CometChatAvatar.tsx
│ │ │ ├── index.ts
│ │ │ └── styles.ts
│ │ ├── CometChatBadge/
│ │ │ ├── CometChatBadge.tsx
│ │ │ ├── index.ts
│ │ │ └── styles.ts
│ │ ├── CometChatBottomSheet/
│ │ │ ├── CometChatBottomSheet.tsx
│ │ │ └── index.ts
│ │ ├── CometChatConfirmDialog/
│ │ │ ├── CometChatConfirmDialog.tsx
│ │ │ ├── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatDate/
│ │ │ ├── CometChatDate.tsx
│ │ │ ├── index.ts
│ │ │ └── styles.ts
│ │ ├── CometChatDateSeperator/
│ │ │ ├── CometChatDateSeparator.tsx
│ │ │ ├── index.ts
│ │ │ └── styles.ts
│ │ ├── CometChatDeletedBubble/
│ │ │ ├── CometChatDeletedBubble.tsx
│ │ │ ├── index.ts
│ │ │ └── styles.ts
│ │ ├── CometChatEmojiKeyboard/
│ │ │ ├── CometChatEmojiKeyboard.tsx
│ │ │ ├── Emoji.ts
│ │ │ ├── EmojiCategory.ts
│ │ │ ├── emojis.ts
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatFileBubble/
│ │ │ ├── CometChatFileBubble.tsx
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatImageBubble/
│ │ │ ├── CometChatImageBubble.tsx
│ │ │ ├── CometChatImageLoader.tsx
│ │ │ ├── assets/
│ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── CometChatImageViewerModal/
│ │ │ ├── ImageViewerModal.tsx
│ │ │ ├── index.tsx
│ │ │ └── resources/
│ │ │ └── index.ts
│ │ ├── CometChatInlineAudioRecorder/
│ │ │ ├── AudioWaveformVisualizer.tsx
│ │ │ ├── CometChatInlineAudioRecorder.tsx
│ │ │ ├── index.ts
│ │ │ ├── style.ts
│ │ │ ├── types.ts
│ │ │ └── useAudioRecorder.ts
│ │ ├── CometChatLinkConfirmPopup/
│ │ │ ├── CometChatLinkConfirmPopup.tsx
│ │ │ ├── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatList/
│ │ │ ├── CometChatList.tsx
│ │ │ ├── Header.tsx
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── styles.ts
│ │ ├── CometChatListItem/
│ │ │ ├── CometChatListItem.tsx
│ │ │ ├── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatMediaRecorder/
│ │ │ ├── AnimatingMic.tsx
│ │ │ ├── CometChatAudioPlayer.tsx
│ │ │ ├── CometChatAudioPreview/
│ │ │ │ └── CometChatAudioPreview.tsx
│ │ │ ├── CometChatMediaRecorder.tsx
│ │ │ ├── Timer.tsx
│ │ │ ├── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatMessageBubble/
│ │ │ ├── CometChatMessageBubble.tsx
│ │ │ ├── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatMessageInput/
│ │ │ ├── CometChatMessageInput.tsx
│ │ │ ├── index.ts
│ │ │ └── styles.ts
│ │ ├── CometChatNewMessageIndicator/
│ │ │ ├── CometChatNewMessageIndicator.tsx
│ │ │ ├── index.ts
│ │ │ └── styles.ts
│ │ ├── CometChatQuickReactions/
│ │ │ ├── CometChatQuickReactions.tsx
│ │ │ ├── QuickReactionsStyle.tsx
│ │ │ ├── index.ts
│ │ │ └── resources/
│ │ │ └── index.ts
│ │ ├── CometChatReactionList/
│ │ │ ├── CometChatReactionList.tsx
│ │ │ ├── ReactionListStyle.tsx
│ │ │ ├── Skeleton.tsx
│ │ │ ├── index.ts
│ │ │ └── resources/
│ │ │ └── index.ts
│ │ ├── CometChatReactions/
│ │ │ ├── CometChatReactions.tsx
│ │ │ ├── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatReceipt/
│ │ │ ├── CometChatReceipt.tsx
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatReportDialog/
│ │ │ ├── CometChatReportDialog.tsx
│ │ │ ├── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatRetryButton/
│ │ │ ├── CometChatRetryButton.tsx
│ │ │ └── index.ts
│ │ ├── CometChatSendButtonView/
│ │ │ ├── CometChatSendButtonView.tsx
│ │ │ └── index.ts
│ │ ├── CometChatStatusIndicator/
│ │ │ ├── CometChatStatusIndicator.tsx
│ │ │ ├── index.ts
│ │ │ └── styles.ts
│ │ ├── CometChatStreamMessageBubble/
│ │ │ ├── CometChatStreamMessageBubble.tsx
│ │ │ └── index.ts
│ │ ├── CometChatSuggestionList/
│ │ │ ├── CometChatSuggestionList.tsx
│ │ │ ├── Skeleton.tsx
│ │ │ ├── SuggestionItem.ts
│ │ │ ├── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatTextBubble/
│ │ │ ├── CometChatTextBubble.tsx
│ │ │ └── index.ts
│ │ ├── CometChatTooltipMenu/
│ │ │ ├── CometChatTooltipMenu.tsx
│ │ │ ├── index.ts
│ │ │ └── styles.ts
│ │ ├── CometChatVideoBubble/
│ │ │ ├── CometChatVideoBubble.tsx
│ │ │ ├── index.ts
│ │ │ ├── resources/
│ │ │ │ └── index.ts
│ │ │ └── style.ts
│ │ ├── CometChatVideoPlayer/
│ │ │ ├── CometChatVideoPlayer.tsx
│ │ │ └── index.tsx
│ │ ├── ErrorEmptyView/
│ │ │ └── ErrorEmptyView.tsx
│ │ └── index.ts
│ └── theme/
│ ├── CometChatThemeHelper.ts
│ ├── context.ts
│ ├── default/
│ │ ├── color/
│ │ │ ├── dark.ts
│ │ │ └── light.ts
│ │ ├── default.ts
│ │ ├── index.ts
│ │ ├── resources/
│ │ │ └── icons/
│ │ │ └── index.ts
│ │ ├── spacing.ts
│ │ └── typography.ts
│ ├── hook.ts
│ ├── index.ts
│ ├── provider.tsx
│ └── type.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
**/.xcode.env.local
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
.cxx/
*.keystore
!debug.keystore
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
**/fastlane/test_output
# Bundle artifact
*.jsbundle
# Ruby / CocoaPods
**/Pods/
/vendor/bundle/
# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*
# testing
/coverage
# Yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Ignore android and ios folders in SampleAppExpo
examples/SampleAppExpo/android/
examples/SampleAppExpo/ios/
================================================
FILE: README.md
================================================
# CometChat React Native UI Kit
CometChat React Native UIKit provides a pre-built user interface kit that developers can use to quickly integrate a reliable & fully-featured chat experience into an existing or a new mobile app.
## 🚀 Explore the Sample Apps!
Dive straight into our Sample Apps to see CometChat UI Kit in action! Whether you're building a messaging app or enhancing your existing project, this sample app showcases the full potential of our React Native UI components.
- [Sample App ](examples/SampleApp#readme)
- [Sample App AI](examples/SampleAppAI#readme)
- [Sample App Expo](examples/SampleAppExpo#readme)
- [Sample App with Push Notifications](examples/SampleAppWithPushNotifications#readme)
## Prerequisites
- **Node.js** 18 or higher
- **React Native** Version 0.77 or later (up to the latest version)
**iOS**
- XCode
- Pod (CocoaPods) for iOS
- An iOS device or emulator with iOS 12.0 or above.
**Android**
- Android Studio
- Android device or emulator with Android version 5.0 or above.
## Getting Started
To set up React Native Chat UIKit and utilize CometChat for your chat functionality, you'll need to follow these steps:
1. Registration: Go to the [CometChat Dashboard](https://app.cometchat.com/) and sign up for an account.
2. After registering, log into your CometChat account and create a new app. Once created, CometChat will generate an Auth Key and App ID for you. Keep
these credentials secure as you'll need them later.
3. Check the [Key Concepts](https://www.cometchat.com/docs/fundamentals/key-concepts) to understand the basic components of CometChat.
4. Refer to the [Integration Steps](https://www.cometchat.com/docs/ui-kit/react-native/5.0/getting-started) in our documentation to integrate the UI Kit into your iOS app.
## Help and Support
For issues running the project or integrating with our UI Kits, consult our [documentation](https://www.cometchat.com/docs/ui-kit/react-native/5.0/overview)
or create a [support ticket](https://help.cometchat.com/hc/en-us) or seek real-time support via the [CometChat Dashboard](https://app.cometchat.com/).
================================================
FILE: examples/SampleApp/.bundle/config
================================================
BUNDLE_PATH: "vendor/bundle"
BUNDLE_FORCE_RUBY_PLATFORM: 1
================================================
FILE: examples/SampleApp/.eslintrc.js
================================================
module.exports = {
root: true,
extends: '@react-native',
};
================================================
FILE: examples/SampleApp/.gitignore
================================================
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
**/.xcode.env.local
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
.cxx/
*.keystore
!debug.keystore
.kotlin/
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
**/fastlane/test_output
# Bundle artifact
*.jsbundle
# Ruby / CocoaPods
**/Pods/
/vendor/bundle/
# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*
# testing
/coverage
# Yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
================================================
FILE: examples/SampleApp/.prettierrc.js
================================================
module.exports = {
arrowParens: 'avoid',
singleQuote: true,
trailingComma: 'all',
};
================================================
FILE: examples/SampleApp/.vscode/settings.json
================================================
{
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#fa1b49",
"activityBar.background": "#fa1b49",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
"activityBarBadge.background": "#155e02",
"activityBarBadge.foreground": "#e7e7e7",
"commandCenter.border": "#e7e7e799",
"sash.hoverBorder": "#fa1b49",
"statusBar.background": "#dd0531",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#fa1b49",
"statusBarItem.remoteBackground": "#dd0531",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#dd0531",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#dd053199",
"titleBar.inactiveForeground": "#e7e7e799"
},
"peacock.color": "#dd0531"
}
================================================
FILE: examples/SampleApp/.watchmanconfig
================================================
{}
================================================
FILE: examples/SampleApp/App.tsx
================================================
import './gesture-handler';
import React, { useState, useEffect, useRef } from 'react';
import {
Platform,
View,
PlatformColor,
AppState,
AppStateStatus,
} from 'react-native';
import { enableScreens } from 'react-native-screens';
enableScreens();
import {
CometChatI18nProvider,
CometChatIncomingCall,
CometChatTheme,
CometChatThemeProvider,
CometChatUIEventHandler,
CometChatUIEvents,
CometChatUIKit,
UIKitSettings,
} from '@cometchat/chat-uikit-react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import RootStackNavigator from './src/navigation/RootStackNavigator';
import { AppConstants } from './src/utils/AppConstants';
import { requestAndroidPermissions } from './src/utils/helper';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useConfig } from './src/config/store';
import { DeepPartial } from '@cometchat/chat-uikit-react-native/src/shared/helper/types';
import { createTypography } from './src/utils/themeTypography';
// Listener ID for registering and removing CometChat listeners.
const listenerId = 'app';
const App = (): React.ReactElement => {
const [callReceived, setCallReceived] = useState(false);
const incomingCall = useRef(
null,
);
const [isInitializing, setIsInitializing] = useState(true);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userLoggedIn, setUserLoggedIn] = useState(false);
const [currentToken, setCurrentToken] = useState('');
const [isTokenRegistered, setIsTokenRegistered] = useState(false);
const [hasValidAppCredentials, setHasValidAppCredentials] = useState(false);
const styleConfig = useConfig(state => state?.settings?.style);
const theme : { light: DeepPartial; dark: DeepPartial } = {
light: {
color: {
primary: styleConfig.color.brandColor,
textPrimary: styleConfig.color.primaryTextLight,
textSecondary: styleConfig.color.secondaryTextLight,
},
typography: createTypography(styleConfig.typography.font),
},
dark: {
color: {
primary: styleConfig.color.brandColor,
textPrimary: styleConfig.color.primaryTextDark,
textSecondary: styleConfig.color.secondaryTextDark,
},
typography: createTypography(styleConfig.typography.font),
},
};
/**
* Initialize CometChat UIKit and configure Google Sign-In.
* Retrieves credentials from AsyncStorage and uses fallback constants if needed.
*/
useEffect(() => {
async function init() {
try {
// Retrieve stored app credentials or default to an empty object.
const AppData = (await AsyncStorage.getItem('appCredentials')) || '{}';
const storedCredentials = JSON.parse(AppData);
// Determine the final credentials (from AsyncStorage or AppConstants).
const finalAppId = storedCredentials.appId || AppConstants.appId;
const finalAuthKey = storedCredentials.authKey || AppConstants.authKey;
const finalRegion = storedCredentials.region || AppConstants.region;
// Set hasValidAppCredentials based on whether all values are available.
if (finalAppId && finalAuthKey && finalRegion) {
setHasValidAppCredentials(true);
} else {
setHasValidAppCredentials(false);
}
await CometChatUIKit.init({
appId: finalAppId,
authKey: finalAuthKey,
region: finalRegion,
subscriptionType: CometChat.AppSettings
.SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings['subscriptionType'],
});
// If a user is already logged in, update the state.
const loggedInUser = CometChatUIKit.loggedInUser;
if (loggedInUser) {
setIsLoggedIn(true);
}
} catch (error) {
console.log('Error during initialization', error);
} finally {
// Mark initialization as complete.
setIsInitializing(false);
}
}
init();
}, []);
/**
* Monitor app state changes to verify the logged-in status and clear notifications.
* When the app becomes active, it cancels Android notifications and checks the login status.
*/
useEffect(() => {
if (Platform.OS === 'android') {
// Request required Android permissions for notifications.
requestAndroidPermissions();
}
const handleAppStateChange = async (nextState: AppStateStatus) => {
if (nextState === 'active') {
try {
// Verify if there is a valid logged-in user.
const chatUser = await CometChat.getLoggedinUser();
setIsLoggedIn(!!chatUser);
} catch (error) {
console.log('Error verifying CometChat user on resume:', error);
}
}
};
const subscription = AppState.addEventListener(
'change',
handleAppStateChange,
);
return () => subscription.remove();
}, []);
/**
* Attach CometChat login listener to handle login and logout events.
* Updates user login status accordingly.
*/
useEffect(() => {
CometChat.addLoginListener(
listenerId,
new CometChat.LoginListener({
loginSuccess: () => {
setUserLoggedIn(true);
},
loginFailure: (e: CometChat.CometChatException) => {
console.log('LoginListener :: loginFailure', e.message);
},
logoutSuccess: () => {
setUserLoggedIn(false);
},
logoutFailure: (e: CometChat.CometChatException) => {
console.log('LoginListener :: logoutFailure', e.message);
},
}),
);
// Clean up the login listener on component unmount.
return () => {
CometChat.removeLoginListener(listenerId);
};
}, []);
/**
* Attach CometChat call listeners to handle incoming, outgoing, and cancelled call events.
* Also handles UI events for call end.
*/
useEffect(() => {
// Listener for call events.
CometChat.addCallListener(
listenerId,
new CometChat.CallListener({
onIncomingCallReceived: (call: CometChat.Call) => {
// Check if there's already an active call
try {
const activeCall = CometChat.getActiveCall();
if (activeCall) {
// If there's an active call, reject the incoming call with busy status
setTimeout(() => {
CometChat.rejectCall(
call.getSessionId(),
CometChat.CALL_STATUS.BUSY,
)
.then(() => {
console.log('Incoming call rejected due to active call');
})
.catch(error => {
console.error(
'Error rejecting call with busy status:',
error,
);
});
}, 2000);
} else {
// No active call, proceed with normal incoming call handling
// Hide any bottom sheet UI before showing the incoming call screen.
CometChatUIEventHandler.emitUIEvent(
CometChatUIEvents.ccToggleBottomSheet,
{
isBottomSheetVisible: false,
},
);
// Store the incoming call and update state.
incomingCall.current = call;
setCallReceived(true);
}
} catch (error) {
console.error('Error getting active call:', error);
// If error getting active call, proceed with normal handling
CometChatUIEventHandler.emitUIEvent(
CometChatUIEvents.ccToggleBottomSheet,
{
isBottomSheetVisible: false,
},
);
incomingCall.current = call;
setCallReceived(true);
}
},
onOutgoingCallRejected: () => {
// Clear the call state if outgoing call is rejected.
incomingCall.current = null;
setCallReceived(false);
},
onIncomingCallCancelled: () => {
// Clear the call state if the incoming call is cancelled.
incomingCall.current = null;
setCallReceived(false);
},
}),
);
// Additional listener to handle call end events.
CometChatUIEventHandler.addCallListener(listenerId, {
ccCallEnded: () => {
incomingCall.current = null;
setCallReceived(false);
},
});
// Remove call listeners on cleanup.
return () => {
CometChatUIEventHandler.removeCallListener(listenerId);
CometChat.removeCallListener(listenerId);
};
}, [userLoggedIn]);
// Show a blank/splash screen while the app is initializing.
if (isInitializing) {
return (
);
}
// Once initialization is complete, render the main app UI.
return (
{/* Render the incoming call UI if the user is logged in and a call is received */}
{isLoggedIn && callReceived && incomingCall.current ? (
{
// Handle call decline by clearing the incoming call state.
incomingCall.current = null;
setCallReceived(false);
}}
/>
) : null}
{/* Render the main navigation stack, passing the login status as a prop */}
);
};
export default App;
================================================
FILE: examples/SampleApp/AppErrorBoundary.tsx
================================================
import React from 'react';
import { View, Text, StyleSheet, Appearance, Button } from 'react-native';
interface State {
hasError: boolean;
error: Error | null;
colorScheme: 'light' | 'dark' | null;
}
interface Props {
children: React.ReactNode;
}
class AppErrorBoundary extends React.Component {
constructor(props: Props) {
super(props);
const colorScheme = Appearance.getColorScheme() ?? null;
this.state = { hasError: false, error: null, colorScheme };
}
static getDerivedStateFromError(error: Error) {
// Update state so the next render shows the fallback UI.
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// Log error to an error reporting service here if needed.
console.log('ErrorBoundary caught an error', error, errorInfo);
}
componentDidMount() {
// Listen for changes in color scheme.
Appearance.addChangeListener(({ colorScheme }) => {
this.setState({ colorScheme: colorScheme ?? null });
});
}
// Reset error state to allow retrying
handleRetry = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
const styles = createStyles(this.state.colorScheme);
return (
Something went wrong
{/* Uncomment the next line to show the error message */}
{/*
{this.state.error ? this.state.error.toString() : 'Unknown error'}
*/}
);
}
return this.props.children;
}
}
const createStyles = (colorScheme: 'light' | 'dark' | null) => {
const isDark = colorScheme === 'dark';
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: isDark ? '#121212' : '#f2f2f2',
justifyContent: 'center',
alignItems: 'center',
padding: 16,
},
card: {
backgroundColor: isDark ? '#1e1e1e' : '#ffffff',
padding: 24,
borderRadius: 12,
alignItems: 'center',
shadowColor: isDark ? '#000000' : '#aaa',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 5,
width: '100%',
maxWidth: 400,
},
title: {
fontSize: 20,
fontWeight: '700',
marginBottom: 12,
color: isDark ? '#ffffff' : '#333333',
},
errorText: {
fontSize: 16,
textAlign: 'center',
color: isDark ? '#cccccc' : '#666666',
marginBottom: 20,
},
buttonContainer: {
width: '100%',
},
});
};
export default AppErrorBoundary;
================================================
FILE: examples/SampleApp/Gemfile
================================================
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby ">= 2.6.10"
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
gem 'xcodeproj', '< 1.26.0'
gem 'concurrent-ruby', '< 1.3.4'
# Ruby 3.4.0 has removed some libraries from the standard library.
gem 'bigdecimal'
gem 'logger'
gem 'benchmark'
gem 'mutex_m'
================================================
FILE: examples/SampleApp/README.md
================================================
# React Native Sample App by CometChat
This is a reference application showcasing the integration of [CometChat's React Native UI Kit](https://www.cometchat.com/docs/ui-kit/react-native/5.0/overview) in a React Native project. It demonstrates how to implement real-time messaging and voice/video calling features with ease.
## Prerequisites
Sign up for a [CometChat](https://app.cometchat.com/) account to obtain your app credentials: _`App ID`_, _`Region`_, and _`Auth Key`_
- **Node.js** 18 or higher
- **React Native** Version 0.77 or later (up to the latest version)
**iOS**
- XCode
- Pod (CocoaPods) for iOS
- An iOS device or emulator with iOS 12.0 or above.
- Ensure that you have configured the provisioning profile in Xcode to run the app on a physical device.
**Android**
- Android Studio
- Android device or emulator with Android version 5.0 or above.
## Installation
1. Clone the repository:
```sh
git clone https://github.com/cometchat/cometchat-uikit-react-native.git
```
1. Change into the specific app's directory (e.g., SampleApp).
```sh
cd examples/SampleApp
```
1. Run `npm install` to install the dependencies.
1. `[Optional]` Configure CometChat credentials:
- Open the `AppConstants.tsx` file located at `examples/SampleApp/src/utils/AppConstants.tsx` and enter your CometChat _`appId`_, _`region`_, and _`authKey`_:
```ts
export const AppConstants = {
appId: 'YOUR_APP_ID',
authKey: 'YOUR_AUTH_KEY',
region: 'REGION',
//other properties
}
```
1. For iOS, install dependencies after navigating to ios:
```sh
cd ios
pod install
```
1. Run the app on a device or emulator from the repo root.
```sh
npm start
npm run android
npm run ios
```
## Help and Support
For issues running the project or integrating with our UI Kits, consult our [documentation](https://www.cometchat.com/docs/ui-kit/react-native/5.0/getting-started) or create a [support ticket](https://help.cometchat.com/hc/en-us). You can also access real-time support via the [CometChat Dashboard](http://app.cometchat.com/).
================================================
FILE: examples/SampleApp/android/app/build.gradle
================================================
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '../..'
// root = file("../../")
// The folder where the react-native NPM package is. Default is ../../node_modules/react-native
// reactNativeDir = file("../../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
// codegenDir = file("../../node_modules/@react-native/codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js
// cliFile = file("../../node_modules/react-native/cli.js")
/* Variants */
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]
//
// The command to run when bundling. By default is 'bundle'
// bundleCommand = "ram-bundle"
//
// The path to the CLI configuration file. Default is empty.
// bundleConfig = file(../rn-cli.config.js)
//
// The name of the generated asset file containing your JS bundle
// bundleAssetName = "MyApplication.android.bundle"
//
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
// entryFile = file("../js/MyApplication.android.js")
//
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
//
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-O", "-output-source-map"]
/* Autolinking */
autolinkLibrariesWithApp()
}
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
android {
ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion
namespace "com.cometchat.sampleapp.reactnative.android"
defaultConfig {
applicationId "com.cometchat.sampleapp.reactnative.android"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "5.3.4"
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
implementation 'com.facebook.fresco:animated-gif:3.6.0' //gif android
}
================================================
FILE: examples/SampleApp/android/app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
================================================
FILE: examples/SampleApp/android/app/src/main/AndroidManifest.xml
================================================
================================================
FILE: examples/SampleApp/android/app/src/main/java/com/sampleapp/MainActivity.kt
================================================
package com.cometchat.sampleapp.reactnative.android
import android.os.Bundle
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
class MainActivity : ReactActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// REQUIRED for react-native-screens
super.onCreate(null)
}
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
*/
override fun getMainComponentName(): String = "sampleapp"
/**
* Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
* which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
*/
override fun createReactActivityDelegate(): ReactActivityDelegate =
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
}
================================================
FILE: examples/SampleApp/android/app/src/main/java/com/sampleapp/MainApplication.kt
================================================
package com.cometchat.sampleapp.reactnative.android
import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
}
override fun getJSMainModuleName(): String = "index"
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}
override val reactHost: ReactHost
get() = getDefaultReactHost(applicationContext, reactNativeHost)
override fun onCreate() {
super.onCreate()
loadReactNative(this)
}
}
================================================
FILE: examples/SampleApp/android/app/src/main/res/drawable/rn_edit_text_material.xml
================================================
================================================
FILE: examples/SampleApp/android/app/src/main/res/values/colors.xml
================================================
#FFFFFF
================================================
FILE: examples/SampleApp/android/app/src/main/res/values/strings.xml
================================================
sampleapp
================================================
FILE: examples/SampleApp/android/app/src/main/res/values/styles.xml
================================================
================================================
FILE: examples/SampleApp/android/build.gradle
================================================
buildscript {
ext {
buildToolsVersion = "36.0.0"
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 36
ndkVersion = "27.1.12297006"
kotlinVersion = "2.1.20"
version = "V5.3.4"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
}
}
apply plugin: "com.facebook.react.rootproject"
================================================
FILE: examples/SampleApp/android/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: examples/SampleApp/android/gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew -PreactNativeArchitectures=x86_64
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=true
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=true
# Use this property to enable edge-to-edge display support.
# This allows your app to draw behind system bars for an immersive UI.
# Note: Only works with ReactActivity and should not be used with custom Activity.
edgeToEdgeEnabled=false
================================================
FILE: examples/SampleApp/android/gradlew
================================================
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
================================================
FILE: examples/SampleApp/android/gradlew.bat
================================================
@REM Copyright (c) Meta Platforms, Inc. and affiliates.
@REM
@REM This source code is licensed under the MIT license found in the
@REM LICENSE file in the root directory of this source tree.
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: examples/SampleApp/android/settings.gradle
================================================
pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
plugins { id("com.facebook.react.settings") }
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
rootProject.name = 'SampleApp'
include ':app'
includeBuild('../node_modules/@react-native/gradle-plugin')
================================================
FILE: examples/SampleApp/app.json
================================================
{
"name": "sampleapp",
"displayName": "sampleapp"
}
================================================
FILE: examples/SampleApp/babel.config.js
================================================
module.exports = {
presets: ['module:@react-native/babel-preset'],
};
================================================
FILE: examples/SampleApp/gesture-handler.js
================================================
================================================
FILE: examples/SampleApp/gesture-handler.native.js
================================================
// Only import react-native-gesture-handler on native platforms
import 'react-native-gesture-handler';
================================================
FILE: examples/SampleApp/index.js
================================================
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import AppErrorBoundary from './AppErrorBoundary';
import {ActiveChatProvider} from './src/utils/ActiveChatContext';
if (global?.ErrorUtils) {
const defaultHandler = global.ErrorUtils.getGlobalHandler();
function globalErrorHandler(error, isFatal) {
console.log(
'[GlobalErrorHandler]:',
isFatal ? 'Fatal:' : 'Non-Fatal:',
error,
);
defaultHandler?.(error, isFatal);
}
global.ErrorUtils.setGlobalHandler(globalErrorHandler);
}
if (typeof process === 'object' && process.on) {
process.on('unhandledRejection', (reason, promise) => {
console.log('[Unhandled Promise Rejection]:', reason);
});
}
const Root = () => (
);
AppRegistry.registerComponent(appName, () => Root);
================================================
FILE: examples/SampleApp/ios/.xcode.env
================================================
# This `.xcode.env` file is versioned and is used to source the environment
# used when running script phases inside Xcode.
# To customize your local environment, you can create an `.xcode.env.local`
# file that is not versioned.
# NODE_BINARY variable contains the PATH to the node executable.
#
# Customize the NODE_BINARY variable here.
# For example, to use nvm with brew, add the following line
# . "$(brew --prefix nvm)/nvm.sh" --no-use
export NODE_BINARY=$(command -v node)
================================================
FILE: examples/SampleApp/ios/Podfile
================================================
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
"react-native/scripts/react_native_pods.rb",
{paths: [process.argv[1]]},
)', __dir__]).strip
platform :ios, min_ios_version_supported
prepare_react_native_project!
use_frameworks! :linkage => :static
target 'SampleApp' do
config = use_native_modules!
pod 'SPTPersistentCache', :modular_headers => true
pod 'DVAssetLoaderDelegate', :modular_headers => true
use_react_native!(
:path => config[:reactNativePath],
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
post_install do |installer|
# https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false,
# :ccache_enabled => true
)
# Fix fmt consteval compilation error with Xcode 26+
# https://github.com/expo/expo/issues/44229
fmt_base = File.join(installer.sandbox.pod_dir('fmt'), 'include', 'fmt', 'base.h')
if File.exist?(fmt_base)
content = File.read(fmt_base)
patched = content.gsub(/^#\s*define FMT_USE_CONSTEVAL 1$/, '# define FMT_USE_CONSTEVAL 0')
if patched != content
File.chmod(0644, fmt_base)
File.write(fmt_base, patched)
end
end
end
end
================================================
FILE: examples/SampleApp/ios/SampleApp/AppDelegate.swift
================================================
import UIKit
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var reactNativeDelegate: ReactNativeDelegate?
var reactNativeFactory: RCTReactNativeFactory?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
let delegate = ReactNativeDelegate()
let factory = RCTReactNativeFactory(delegate: delegate)
delegate.dependencyProvider = RCTAppDependencyProvider()
reactNativeDelegate = delegate
reactNativeFactory = factory
window = UIWindow(frame: UIScreen.main.bounds)
factory.startReactNative(
withModuleName: "sampleapp",
in: window,
launchOptions: launchOptions
)
return true
}
}
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
override func sourceURL(for bridge: RCTBridge) -> URL? {
self.bundleURL()
}
override func bundleURL() -> URL? {
#if DEBUG
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
}
================================================
FILE: examples/SampleApp/ios/SampleApp/Images.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"filename" : "CometChat_React_Native_40x40.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "CometChat_React_Native_60x60.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "CometChat_React_Native_58x58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "CometChat_React_Native_87x87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "CometChat_React_Native_80x80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "CometChat_React_Native_120x120 1.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "CometChat_React_Native_120x120.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "CometChat_React_Native_180x180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "CometChat React native.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: examples/SampleApp/ios/SampleApp/Images.xcassets/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: examples/SampleApp/ios/SampleApp/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleDisplayName
sampleapp
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
APPL
CFBundleShortVersionString
$(MARKETING_VERSION)
CFBundleSignature
????
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
LSRequiresIPhoneOS
NSAppTransportSecurity
NSAllowsArbitraryLoads
NSAllowsLocalNetworking
NSCameraUsageDescription
This is for Camera permission
NSLocationWhenInUseUsageDescription
NSMicrophoneUsageDescription
This is for Mic permission
RCTNewArchEnabled
UILaunchStoryboardName
LaunchScreen
UIRequiredDeviceCapabilities
arm64
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UIViewControllerBasedStatusBarAppearance
UIAppFonts
inter_regular.ttf
inter_medium.ttf
inter_bold.ttf
roboto_regular.ttf
roboto_medium.ttf
roboto_bold.ttf
times_new_roman_regular.ttf
times_new_roman_medium.otf
times_new_roman_bold.ttf
================================================
FILE: examples/SampleApp/ios/SampleApp/LaunchScreen.storyboard
================================================
================================================
FILE: examples/SampleApp/ios/SampleApp/PrivacyInfo.xcprivacy
================================================
NSPrivacyAccessedAPITypes
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategoryFileTimestamp
NSPrivacyAccessedAPITypeReasons
C617.1
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategoryUserDefaults
NSPrivacyAccessedAPITypeReasons
CA92.1
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategorySystemBootTime
NSPrivacyAccessedAPITypeReasons
35F9.1
NSPrivacyCollectedDataTypes
NSPrivacyTracking
================================================
FILE: examples/SampleApp/ios/SampleApp.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
295B57B00587C81464D7BEFE /* Pods_SampleApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D33CE56D95E35081CB0ADFD /* Pods_SampleApp.framework */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
BDCB18E08DA1EE3CCF31234A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1080499FF01EB6C0E2E655F6 /* Pods-SampleApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleApp.debug.xcconfig"; path = "Target Support Files/Pods-SampleApp/Pods-SampleApp.debug.xcconfig"; sourceTree = ""; };
13B07F961A680F5B00A75B9A /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = SampleApp/Images.xcassets; sourceTree = ""; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = SampleApp/Info.plist; sourceTree = ""; };
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = SampleApp/PrivacyInfo.xcprivacy; sourceTree = ""; };
24B03C26D609B5A540B05A65 /* Pods-SampleApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleApp.release.xcconfig"; path = "Target Support Files/Pods-SampleApp/Pods-SampleApp.release.xcconfig"; sourceTree = ""; };
5D33CE56D95E35081CB0ADFD /* Pods_SampleApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SampleApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = SampleApp/AppDelegate.swift; sourceTree = ""; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = SampleApp/LaunchScreen.storyboard; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
295B57B00587C81464D7BEFE /* Pods_SampleApp.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
13B07FAE1A68108700A75B9A /* SampleApp */ = {
isa = PBXGroup;
children = (
13B07FB51A68108700A75B9A /* Images.xcassets */,
761780EC2CA45674006654EE /* AppDelegate.swift */,
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,
);
name = SampleApp;
sourceTree = "";
};
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
5D33CE56D95E35081CB0ADFD /* Pods_SampleApp.framework */,
);
name = Frameworks;
sourceTree = "";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
);
name = Libraries;
sourceTree = "";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
13B07FAE1A68108700A75B9A /* SampleApp */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
BBD78D7AC51CEA395F1C20DB /* Pods */,
);
indentWidth = 2;
sourceTree = "";
tabWidth = 2;
usesTabs = 0;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* SampleApp.app */,
);
name = Products;
sourceTree = "";
};
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
1080499FF01EB6C0E2E655F6 /* Pods-SampleApp.debug.xcconfig */,
24B03C26D609B5A540B05A65 /* Pods-SampleApp.release.xcconfig */,
);
path = Pods;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
13B07F861A680F5B00A75B9A /* SampleApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "SampleApp" */;
buildPhases = (
93A7E3EE15C66A560927690C /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
B26D7914AEF39D3B5D13EA59 /* [CP] Embed Pods Frameworks */,
313738ECC7D8394BE4D3F3F3 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = SampleApp;
productName = SampleApp;
productReference = 13B07F961A680F5B00A75B9A /* SampleApp.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1210;
TargetAttributes = {
13B07F861A680F5B00A75B9A = {
LastSwiftMigration = 1120;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "SampleApp" */;
compatibilityVersion = "Xcode 12.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* SampleApp */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
BDCB18E08DA1EE3CCF31234A /* PrivacyInfo.xcprivacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"$(SRCROOT)/.xcode.env.local",
"$(SRCROOT)/.xcode.env",
);
name = "Bundle React Native code and images";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
};
313738ECC7D8394BE4D3F3F3 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources.sh\"\n";
showEnvVarsInLog = 0;
};
93A7E3EE15C66A560927690C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-SampleApp-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
B26D7914AEF39D3B5D13EA59 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
13B07F871A680F5B00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1080499FF01EB6C0E2E655F6 /* Pods-SampleApp.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = T9F7QDSC2S;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = SampleApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = v5.3.4;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.cometchat.internal.reactnative.ios;
PRODUCT_NAME = SampleApp;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 24B03C26D609B5A540B05A65 /* Pods-SampleApp.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = T9F7QDSC2S;
INFOPLIST_FILE = SampleApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = v5.3.4;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.cometchat.internal.reactnative.ios;
PRODUCT_NAME = SampleApp;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
);
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"\"$(SDKROOT)/usr/lib/swift\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
USE_HERMES = true;
};
name = Debug;
};
83CBBA211A601CBA00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
);
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"\"$(SDKROOT)/usr/lib/swift\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "SampleApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */,
13B07F951A680F5B00A75B9A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "SampleApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,
83CBBA211A601CBA00E9B192 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}
================================================
FILE: examples/SampleApp/ios/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme
================================================
================================================
FILE: examples/SampleApp/ios/SampleApp.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: examples/SampleApp/ios/SampleApp.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
================================================
FILE: examples/SampleApp/jest.config.js
================================================
module.exports = {
preset: 'react-native',
};
================================================
FILE: examples/SampleApp/metro.config.js
================================================
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
/**
* Metro configuration
* https://reactnative.dev/docs/metro
*
* @type {import('@react-native/metro-config').MetroConfig}
*/
const config = {};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
================================================
FILE: examples/SampleApp/package.json
================================================
{
"name": "sample-app",
"version": "5.3.4",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest"
},
"dependencies": {
"@cometchat/calls-sdk-react-native": "^4.4.0",
"@cometchat/chat-sdk-react-native": "^4.0.21",
"@cometchat/chat-uikit-react-native": "^5.3.4",
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-clipboard/clipboard": "^1.16.3",
"@react-native-community/datetimepicker": "^8.4.5",
"@react-native-community/netinfo": "^11.4.1",
"@react-native/new-app-screen": "0.81.4",
"@react-navigation/bottom-tabs": "^7.4.7",
"@react-navigation/native": "^7.1.17",
"@react-navigation/native-stack": "^7.14.12",
"@react-navigation/stack": "^7.4.8",
"dayjs": "^1.11.18",
"react": "19.1.0",
"react-native": "0.81.4",
"react-native-background-timer": "^2.4.1",
"react-native-callstats": "^3.73.22",
"react-native-gesture-handler": "^2.28.0",
"react-native-localize": "^3.5.2",
"react-native-safe-area-context": "^5.5.2",
"react-native-screens": "^4.16.0",
"react-native-svg": "^15.13.0",
"react-native-video": "^6.16.1",
"react-native-vision-camera": "^4.7.2",
"react-native-webrtc": "^124.0.7",
"zustand": "^5.0.8"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli": "20.0.0",
"@react-native-community/cli-platform-android": "20.0.0",
"@react-native-community/cli-platform-ios": "20.0.0",
"@react-native/babel-preset": "0.81.4",
"@react-native/eslint-config": "0.81.4",
"@react-native/metro-config": "0.81.4",
"@react-native/typescript-config": "0.81.4",
"@types/jest": "^29.5.13",
"@types/react": "^19.1.0",
"@types/react-test-renderer": "^19.1.0",
"eslint": "^8.19.0",
"jest": "^29.6.3",
"prettier": "2.8.8",
"react-test-renderer": "19.1.0",
"typescript": "^5.8.3"
},
"engines": {
"node": ">=20"
}
}
================================================
FILE: examples/SampleApp/src/assets/icons/AccountCircle.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/AddComment.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/AiIcon.tsx
================================================
import Svg, { Path, Defs, LinearGradient, Stop } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const AiIcon = ({ width = 24, height = 24 }: SvgProps) => (
);
export default AiIcon;
================================================
FILE: examples/SampleApp/src/assets/icons/ArrowBack.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Block.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Builder.tsx
================================================
import React from "react";
import Svg, { Path, SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Call.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/CallFill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Chat.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Chatfill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/CheckFill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Close.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Delete.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Flash.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ width = 24, height = 24, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Group.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/GroupAdd.tsx
================================================
import type { SvgProps } from "react-native-svg";
import Svg, { Path } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/GroupFill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Info.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/InfoIcon.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Logout.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Person.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/PersonAdd.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/PersonFill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/PersonOff.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/Reset.tsx
================================================
import React from "react";
import Svg, { Path, SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/UserEmptyIcon.tsx
================================================
import type { SvgProps } from "react-native-svg";
import Svg, { G, Mask, Path, Rect } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/assets/icons/VideoCam.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/AIAgent/AIAgents.tsx
================================================
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {CometChatUsers, useTheme} from '@cometchat/chat-uikit-react-native';
import React, {useCallback, useLayoutEffect} from 'react';
import {SafeAreaView} from 'react-native';
import {useFocusEffect, useNavigation} from '@react-navigation/native';
import {RootStackParamList} from '../../navigation/types';
import {StackNavigationProp} from '@react-navigation/stack';
type AIAgentNavigationProp = StackNavigationProp;
const AIAgents: React.FC = () => {
const theme = useTheme();
const navigation = useNavigation();
const [shouldHide, setShouldHide] = React.useState(false);
// Focus effect to manage component visibility
useFocusEffect(
useCallback(() => {
setShouldHide(false);
return () => {
setShouldHide(true);
};
}, []),
);
// Configure header with back button
useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
title: 'AI Assistants',
headerStyle: {
backgroundColor: theme.color.background1,
},
headerTitleStyle: {
color: theme.color.textPrimary,
},
});
}, [navigation, theme]);
const handleUserPress = (user: CometChat.User) => {
navigation.navigate('Messages', { user });
};
return shouldHide ? null : (
navigation.goBack()}
usersRequestBuilder={new CometChat.UsersRequestBuilder()
.setLimit(30)
.hideBlockedUsers(false)
.setRoles(['@agentic'])
.friendsOnly(false)
.setStatus('')
.setTags([])
.sortBy('name')
.setUIDs([])}
/>
);
};
export default AIAgents;
================================================
FILE: examples/SampleApp/src/components/calls/CallDetailHelper.tsx
================================================
import {CometChatTheme, CometChatUIKit} from '@cometchat/chat-uikit-react-native';
import {CallMade, CallMissedOutgoingFill, CallReceived} from './icons';
import {JSX} from 'react';
import { getCometChatTranslation } from '@cometchat/chat-uikit-react-native';
const t = getCometChatTranslation();
type CallDirection = 'incoming' | 'outgoing';
export type CallStatus =
| 'incoming'
| 'outgoing'
| 'incomingCallEnded'
| 'outgoingCallEnded'
| 'cancelledByMe'
| 'cancelledByThem'
| 'incomingRejected'
| 'outgoingRejected'
| 'incomingBusy'
| 'outgoingBusy'
| 'unansweredByMe'
| 'unansweredByThem';
export class CallDetailHelper {
static getFormattedInitiatedAt = (call: any): string => {
const date = new Date(call.getInitiatedAt() * 1000);
const now = new Date();
// Extracting parts
const day = date.getDate();
const month = new Intl.DateTimeFormat('en-US', {month: 'long'}).format(
date,
);
const year = date.getFullYear();
const time = date.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true,
});
// Determine if the year should be included
const includeYear = now.getFullYear() !== year;
return `${day} ${month}${includeYear ? `, ${year}` : ''}, ${time}`;
};
/** Returns the UI-facing callStatus plus the direction */
static getCallType = (
call: any,
): {type: CallDirection; callStatus: CallStatus} => {
const myUid = CometChatUIKit.loggedInUser?.getUid();
const type: CallDirection =
call.getInitiator().getUid() === myUid ? 'outgoing' : 'incoming';
const statusMap: Record<
string,
{incoming: CallStatus; outgoing: CallStatus}
> = {
ended: {
incoming: 'incomingCallEnded',
outgoing: 'outgoingCallEnded',
},
rejected: {
incoming: 'incomingRejected',
outgoing: 'outgoingRejected',
},
cancelled: {
incoming: 'unansweredByMe',
outgoing: 'cancelledByMe',
},
unanswered: {
incoming: 'unansweredByMe',
outgoing: 'unansweredByThem',
},
initiated: {
incoming: 'incoming',
outgoing: 'outgoing',
},
busy: {
incoming: 'incomingBusy',
outgoing: 'outgoingBusy',
},
};
return {
type,
callStatus:
statusMap[call.getStatus() as keyof typeof statusMap]?.[type] ??
(type === 'incoming' ? 'incoming' : 'outgoing'),
};
};
/** Which SVG to render for a given callStatus */
static getCallStatusDisplayIcon = (
callStatus: CallStatus,
theme: CometChatTheme,
): JSX.Element | undefined => {
const icons: Record = {
outgoing: ,
outgoingCallEnded: (
),
cancelledByMe: (
),
outgoingRejected: (
),
outgoingBusy: (
),
unansweredByThem: (
),
incoming: (
),
incomingCallEnded: (
),
cancelledByThem: (
),
incomingRejected: (
),
incomingBusy: (
),
unansweredByMe: (
),
};
return icons[callStatus];
};
static getCallStatusDisplayText = (callStatus: CallStatus): string => {
const labels: Record = {
outgoing: t('OUTGOING_CALL'),
outgoingCallEnded: t('OUTGOING_CALL'),
cancelledByMe: t('OUTGOING_CALL'),
outgoingRejected: t('OUTGOING_CALL'),
outgoingBusy: t('OUTGOING_CALL'),
unansweredByThem: t('OUTGOING_CALL'),
incoming: t('INCOMING_CALL'),
incomingCallEnded: t('INCOMING_CALL'),
cancelledByThem: t('MISSED_CALL'),
incomingRejected: t('INCOMING_CALL'),
incomingBusy: t('MISSED_CALL'),
unansweredByMe: t('MISSED_CALL'),
};
return labels[callStatus];
};
}
================================================
FILE: examples/SampleApp/src/components/calls/CallDetails.tsx
================================================
import React, {
JSX,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {View, TouchableOpacity, Text, TextStyle, ViewStyle} from 'react-native';
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {
CometChatListItem,
useCometChatTranslation,
useTheme,
useLocalizedDate,
LocalizedDateHelper
} from '@cometchat/chat-uikit-react-native';
import {CallHistory} from './CallHistory';
import {CallLogDetailHeader} from './CallLogDetailHeader';
import {Icon} from '@cometchat/chat-uikit-react-native';
import {CallParticipants} from './CallParticipants';
import {CallDetailHelper, CallStatus} from './CallDetailHelper';
import {CallRecordings} from './CallRecordings';
import {StackScreenProps} from '@react-navigation/stack';
import {ICONS} from '@cometchat/chat-uikit-react-native/src/shared/icons/icon-mapping';
import {RootStackParamList} from '../../navigation/types';
const listenerId = 'userListener_' + new Date().getTime();
const TABS = {
DETAILS: 'Details',
PARTICIPANTS: 'Participants',
RECORDINGS: 'Recordings',
HISTORY: 'History',
};
type Props = StackScreenProps;
export const CallDetails: React.FC = ({route, navigation}) => {
const {call} = route.params;
const theme = useTheme();
const {t, language} = useCometChatTranslation()
const {formatDate}= useLocalizedDate()
const [group, setGroup] = useState(null);
const [user, setUser] = useState(null);
const loggedInUser = useRef(null);
const [selectedTab, setSelectedTab] = useState(TABS.PARTICIPANTS);
const [tabStyle, setTableStyle] = useState<{
containerStyle: ViewStyle;
itemStyle: ViewStyle;
selectedItemStyle: ViewStyle;
itemEmojiStyle: TextStyle;
selectedItemEmojiStyle: TextStyle;
itemTextStyle: TextStyle;
selectedItemTextStyle: TextStyle;
}>();
const BackIcon = ICONS['arrow-back'];
const [toastMessage, setToastMessage] = useState(null);
useEffect(() => {
console.log('CALL RECEIVER: ', call);
CometChat.getLoggedinUser().then((loggedUser: CometChat.User | any) => {
loggedInUser.current = loggedUser;
let user =
call?.getReceiverType() == 'user'
? loggedInUser.current?.getUid() === call?.getInitiator()?.getUid()
? call.getReceiver()
: call?.getInitiator()
: undefined;
let group =
call?.getReceiverType() == 'group'
? loggedInUser.current?.getUid() === call?.getInitiator()?.getUid()
? call.getReceiver()
: call?.getInitiator()
: undefined;
if (user) {
CometChat.getUser(user.getUid()).then((userObject: CometChat.User) => {
setUser(userObject);
});
return;
}
CometChat.getGroup(group.getGuid()).then(
(groupObject: CometChat.Group) => {
setGroup(groupObject);
},
);
});
}, [call]);
useEffect(() => {
CometChat.addUserListener(
listenerId,
new CometChat.UserListener({
onUserOnline: (userDetails: any) => {
if (user?.getUid() === userDetails?.getUid()) {
setUser(userDetails);
}
},
onUserOffline: (userDetails: any) => {
if (user?.getUid() === userDetails?.getUid()) {
setUser(userDetails);
}
},
}),
);
return () => {
CometChat.removeUserListener(listenerId);
};
}, [user]);
useEffect(() => {
setTableStyle({
containerStyle: {
//flex: 1,
backgroundColor: theme.color.background1,
flexDirection: 'row',
//justifyContent: 'space-evenly', // ✅ Ensures even spacing
alignItems: 'center', // Optional, ensures vertical alignment
width: '100%', // ✅ Ensures full width for proper spacing
borderBottomWidth: 1,
borderColor: theme.color.borderDefault,
justifyContent: 'space-evenly',
},
itemStyle: {
// paddingHorizontal: theme.spacing.padding.p4,
paddingVertical: theme.spacing.padding.p2,
flexDirection: 'row',
borderBottomWidth: theme.spacing.spacing.s0_5,
borderBottomColor: 'transparent',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
},
selectedItemStyle: {
// paddingHorizontal: theme.spacing.padding.p4,
paddingVertical: theme.spacing.padding.p2,
flexDirection: 'row',
borderBottomWidth: theme.spacing.spacing.s0_5,
borderBottomColor: theme.color.primary,
alignItems: 'center',
justifyContent: 'center',
flex: 1,
},
itemEmojiStyle: {
color: theme.color.textSecondary,
borderColor: 'transparent',
...theme.typography.body.medium,
},
selectedItemEmojiStyle: {
color: theme.color.textSecondary,
borderColor: 'transparent',
...theme.typography.body.medium,
},
itemTextStyle: {
color: theme.color.textSecondary,
marginLeft: theme.spacing.margin.m1,
...theme.typography.body.medium,
},
selectedItemTextStyle: {
color: theme.color.primary,
marginLeft: theme.spacing.margin.m1,
...theme.typography.body.medium,
},
});
}, [theme]);
const getUserToFetchHistory = useCallback(() => {
if (call?.getInitiator().getUid() === loggedInUser.current.getUid()) {
return call?.getReceiver();
}
call?.getInitiator();
}, [call]);
const callTypeAndStatus = useMemo(
(): {
type: 'incoming' | 'outgoing';
callStatus: CallStatus;
} => CallDetailHelper.getCallType(call),
[call],
);
/** Busy call toast: only when attempting a call and target is busy */
useEffect(() => {
const callListener = new CometChat.CallListener({
onOutgoingCallRejected: (rejectedCall: any) => {
try {
const status = rejectedCall?.getStatus?.() || rejectedCall?.status;
if (
status &&
status.toLowerCase() === CometChat.CALL_STATUS.BUSY.toLowerCase()
) {
setToastMessage(t('CALL_BUSY'));
setTimeout(() => setToastMessage(null), 3000);
}
} catch {}
},
});
CometChat.addCallListener(listenerId, callListener);
return () => {
CometChat.removeCallListener(listenerId);
};
}, [t]);
const _style = useMemo(() => {
return {
headerContainerStyle: {
alignItems: 'flex-start',
justifyContent: 'center',
width: '100%',
borderRadius: 0,
paddingHorizontal: 0,
},
titleSeparatorStyle: {
borderBottomWidth: 1,
borderBottomColor: theme.color.borderLight,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
},
containerStyle: {
backgroundColor: theme.color.background2,
flex: 1,
},
itemStyle: {
containerStyle: {
flexDirection: 'row' as const,
paddingRight: theme.spacing.padding.p4,
paddingLeft: theme.spacing.padding.p2,
paddingVertical: theme.spacing.padding.p2,
gap: theme.spacing.spacing.s3,
flex: 1,
},
titleStyle: {
color: theme.color.textPrimary,
...theme.typography.heading4.bold,
},
subtitleStyle: {
color: theme.color.textSecondary,
...theme.typography.caption1.regular,
},
tailViewTextStyle: {
color: theme.color.textPrimary,
...theme.typography.caption1.bold,
},
avatarStyle: {
containerStyle: {},
textStyle: {},
imageStyle: {
height: 48,
width: 48,
},
},
},
};
}, [theme]);
const convertMinutesToTime = useCallback((decimalMinutes: number) => {
const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds
const minutes = Math.floor(totalSeconds / 60); // Get whole minutes
const seconds = totalSeconds % 60; // Get remaining seconds
return `${minutes} min ${seconds} sec`;
}, []);
const getFormattedInitiatedAt = useCallback(() => {
if (!call || !call.getInitiatedAt()) return '';
return formatDate(
call.getInitiatedAt() * 1000,
LocalizedDateHelper.patterns.dayDateTimeFormat
);
}, [call]);
const callStatusDisplayString = useMemo(() => {
return CallDetailHelper.getCallStatusDisplayText(
callTypeAndStatus.callStatus,
);
}, [callTypeAndStatus]);
const CallStatusIcon = useMemo(
() =>
CallDetailHelper.getCallStatusDisplayIcon(
callTypeAndStatus.callStatus,
theme,
),
[callTypeAndStatus, theme],
);
/** Local toast view component (shows only when toastMessage set; auto hides after 3s from effect) */
const ToastView = () => {
if (!toastMessage) return null;
return (
{toastMessage}
);
}
return (
navigation.goBack()}>
}
/>
{t('CALL_DETAILS')}
{(user || group) && (
{getFormattedInitiatedAt()}
}
TrailingView={
{convertMinutesToTime(call.getTotalDurationInMinutes())}
}
/>
{[
{ key: TABS.PARTICIPANTS, title: t('PARTICIPANT') },
{ key: TABS.RECORDINGS, title: t('RECORDING') },
{ key: TABS.HISTORY, title: t('HISTORY') }
].map((tab) => (
setSelectedTab(tab.key)}>
{tab.title}
))}
{selectedTab === TABS.PARTICIPANTS && (
)}
{selectedTab === TABS.HISTORY && (
)}
{selectedTab === TABS.RECORDINGS &&
call.getRecordings()?.length && (
)}
)}
);
};
================================================
FILE: examples/SampleApp/src/components/calls/CallHistory.tsx
================================================
import { CometChat } from '@cometchat/chat-sdk-react-native';
import {
CallingPackage,
CometChatListItem,
useTheme,
useCometChatTranslation,
useLocalizedDate,
LocalizedDateHelper
} from '@cometchat/chat-uikit-react-native';
import React, { JSX, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { ActivityIndicator, FlatList, Text, View } from 'react-native';
import { CallDetailHelper } from './CallDetailHelper';
import { Icon } from '@cometchat/chat-uikit-react-native';
const CometChatCalls = CallingPackage.CometChatCalls;
export const CallHistory = (props: { user?: any; group?: any }) => {
const { user, group } = props;
const theme = useTheme();
const { language, t } = useCometChatTranslation();
const { formatDate } = useLocalizedDate();
const [list, setList] = useState([]);
const [loading, setLoading] = useState(true);
const [isFetchingMore, setIsFetchingMore] = useState(false);
const loggedInUser = useRef(null);
const callRequestBuilderRef = useRef(null);
function setRequestBuilder() {
callRequestBuilderRef.current;
let builder = new CometChatCalls.CallLogRequestBuilder()
.setLimit(30)
.setAuthToken(loggedInUser.current?.getAuthToken() || '')
.setCallCategory('call');
if (user) {
builder = builder.setUid(user?.getUid());
} else if (group) {
builder = builder.setGuid(group?.getGuid());
}
callRequestBuilderRef.current = builder.build();
}
const fetchCallLogHistory = () => {
if (!callRequestBuilderRef.current || isFetchingMore) {
return;
}
setIsFetchingMore(true);
callRequestBuilderRef.current
.fetchNext()
.then((CallLogHistory: any) => {
if (CallLogHistory.length > 0) {
setList(prev => [...prev, ...CallLogHistory]);
}
})
.catch((err: any) => {
// onError && onError(err);
})
.finally(() => {
setLoading(false);
setIsFetchingMore(false);
});
};
useEffect(() => {
CometChat.getLoggedinUser()
.then((u: any) => {
loggedInUser.current = u;
setRequestBuilder();
fetchCallLogHistory();
})
.catch((e: any) => {
// onError && onError(e);
setLoading(false);
});
}, []);
const _style = useMemo(() => {
return {
headerContainerStyle: {
alignItems: 'flex-start',
justifyContent: 'center',
width: '100%',
borderRadius: 0,
paddingHorizontal: 0,
},
titleSeparatorStyle: {
borderBottomWidth: 1,
borderBottomColor: theme.color.borderLight,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
},
containerStyle: {
backgroundColor: theme.color.background1,
flex: 1,
},
itemStyle: {
containerStyle: {
flexDirection: 'row' as const,
marginHorizontal: theme.spacing.margin.m4,
paddingVertical: theme.spacing.padding.p2,
gap: theme.spacing.spacing.s3,
flex: 1,
},
titleStyle: {
color: theme.color.textPrimary,
flex: 1,
...theme.typography.heading4.bold,
},
subtitleStyle: {
color: theme.color.textSecondary,
...theme.typography.caption1.regular,
},
tailViewTextStyle: {
color: theme.color.textPrimary,
...theme.typography.caption1.bold,
},
avatarStyle: {
containerStyle: {},
textStyle: {},
imageStyle: {
height: 48,
width: 48,
},
},
},
};
}, [theme]);
// Updated to use proper localization
const getFormattedInitiatedAt = useCallback((call: any) => {
if (!call || !call.getInitiatedAt()) return '';
// Use localizedDateHelper to format the date with proper localization
return formatDate(
call.getInitiatedAt() * 1000,
LocalizedDateHelper.patterns.dayDateTimeFormat
);
}, [formatDate]);
const getCallType = useCallback((call: any) => {
return CallDetailHelper.getCallType(call);
}, []);
const getCallStatusIcon = useCallback((item: any): JSX.Element => {
const CallStatusIcon = CallDetailHelper.getCallStatusDisplayIcon(
getCallType(item).callStatus,
theme,
);
return CallStatusIcon || ;
}, []);
const convertMinutesToTime = useCallback((decimalMinutes: number) => {
const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds
const minutes = Math.floor(totalSeconds / 60); // Get whole minutes
const seconds = totalSeconds % 60; // Get remaining seconds
return `${minutes} min ${seconds} sec`;
}, []);
// Added to get localized call status text
const getCallStatusText = useCallback((callStatus: any) => {
// Get translation key for this status
const translationKeys: Record = {
outgoing: 'OUTGOING_CALL',
outgoingCallEnded: 'OUTGOING_CALL',
cancelledByMe: 'OUTGOING_CALL',
outgoingRejected: 'OUTGOING_CALL',
outgoingBusy: 'OUTGOING_CALL',
unansweredByThem: 'OUTGOING_CALL',
incoming: 'INCOMING_CALL',
incomingCallEnded: 'INCOMING_CALL',
cancelledByThem: 'MISSED_CALL',
incomingRejected: 'INCOMING_CALL',
incomingBusy: 'MISSED_CALL',
unansweredByMe: 'MISSED_CALL',
};
const key = translationKeys[callStatus] || 'UNKNOWN_CALL';
return t(key);
}, [t]);
const _render = ({ item, index }: any) => {
return (
{getFormattedInitiatedAt(item)}
}
TrailingView={
{convertMinutesToTime(item.getTotalDurationInMinutes())}
}
/>
);
};
return (
item.sessionId + '_' + index}
renderItem={_render}
onEndReached={fetchCallLogHistory}
onEndReachedThreshold={0.5}
ListEmptyComponent={
loading ? (
) : null
}
/>
);
};
================================================
FILE: examples/SampleApp/src/components/calls/CallLogDetailHeader.tsx
================================================
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {
useTheme,
CometChatAvatar,
CometChatCallButtons,
useCometChatTranslation,
} from '@cometchat/chat-uikit-react-native';
import {
GroupTypeConstants,
UserStatusConstants,
} from '@cometchat/chat-uikit-react-native/src/shared/constants/UIKitConstants';
import {CometChatCompThemeProvider} from '@cometchat/chat-uikit-react-native/src/theme/provider';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {Text, View} from 'react-native';
import { useConfig } from '../../config/store';
export type CallLogDetailHeaderInterface = {
user?: CometChat.User;
/**
*
* @type {CometChat.Group}
* To pass group object
*/
group?: CometChat.Group;
};
export const CallLogDetailHeader = (props: CallLogDetailHeaderInterface) => {
const theme = useTheme();
const {t}= useCometChatTranslation()
const {user, group} = props;
const [groupObj, setGroupObj] = useState(group);
const [userStatus, setUserStatus] = useState(
user &&
!(user.getBlockedByMe() || user.getHasBlockedMe()) &&
user?.getStatus
? user?.getStatus()
: '',
);
const oneOnOneVoiceCalling = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling
);
const oneOnOneVideoCalling = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling
);
const groupVideoConference = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference
);
const groupVoiceConference = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference
);
const receiverTypeRef = useRef(
user
? CometChat.RECEIVER_TYPE.USER
: group
? CometChat.RECEIVER_TYPE.GROUP
: null,
);
useEffect(() => {
setGroupObj(group);
}, [group]);
useEffect(() => {
setUserStatus(
user && !(user.getBlockedByMe() || user.getHasBlockedMe())
? user?.getStatus()
: '',
);
}, [user]);
const messageHeaderStyles = useMemo(() => {
return theme.messageHeaderStyles;
}, [theme.messageHeaderStyles]);
const statusIndicatorType = useMemo(() => {
if (groupObj?.getType() === GroupTypeConstants.password) {
return 'protected';
} else if (groupObj?.getType() === GroupTypeConstants.private) {
return 'private';
} else if (userStatus === 'online') {
return 'online';
}
return '';
}, [userStatus, groupObj]);
const AvatarWithStatusView = useCallback(() => {
return (
);
}, [user, groupObj, statusIndicatorType]);
const SubtitleViewFnc = () => {
const statusTytle =
receiverTypeRef.current === CometChat.RECEIVER_TYPE.GROUP &&
(groupObj?.['membersCount'] || groupObj?.['membersCount'] === 0)
? `${groupObj['membersCount']} ${t('MEMBERS')}`
: receiverTypeRef.current === CometChat.RECEIVER_TYPE.USER
? userStatus === UserStatusConstants.online
? t('ONLINE')
: userStatus === UserStatusConstants.offline
? t('OFFLINE')
: ''
: '';
if(!statusTytle) return <>>;
return (
{statusTytle}
);
};
return (
{user ? user.getName() : groupObj ? groupObj.getName() : ''}
{ }
);
};
================================================
FILE: examples/SampleApp/src/components/calls/CallParticipants.tsx
================================================
import { CometChatListItem, useTheme, useCometChatTranslation, localizedDateHelperInstance, useLocalizedDate, LocalizedDateHelper } from '@cometchat/chat-uikit-react-native';
import React, { useCallback, useMemo } from 'react';
import { View, FlatList, Text } from 'react-native';
import { CallDetailHelper } from './CallDetailHelper';
export const CallParticipants = (props: {
/**
* Participant list
*/
data: any[];
call: any;
}) => {
const { call, data } = props;
const theme = useTheme();
const { language, t } = useCometChatTranslation();
const { formatDate } = useLocalizedDate();
const getCallDetails = (item: any) => {
return {
title: item['name'],
avatarUrl: item['avatar'],
};
};
// Updated to use proper localization
const formattedInitiatedAt = useMemo(() => {
if (!call || !call.getInitiatedAt()) return '';
// Use formatDate for proper localization of date/time
return formatDate(
call.getInitiatedAt() * 1000,
LocalizedDateHelper.patterns.dayDateTimeFormat
);
}, [call, formatDate]);
const _style = useMemo(() => {
return {
headerContainerStyle: {
alignItems: 'flex-start',
justifyContent: 'center',
width: '100%',
borderRadius: 0,
paddingHorizontal: 0,
},
titleSeparatorStyle: {
borderBottomWidth: 1,
borderBottomColor: theme.color.borderLight,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
},
containerStyle: {
backgroundColor: theme.color.background1,
flex: 1,
},
itemStyle: {
containerStyle: {
flexDirection: 'row' as const,
paddingHorizontal: theme.spacing.padding.p4,
paddingVertical: theme.spacing.padding.p2,
gap: theme.spacing.spacing.s3,
},
titleStyle: {
color: theme.color.textPrimary,
flex: 1,
...theme.typography.heading4.medium,
},
subtitleStyle: {
color: theme.color.textSecondary,
...theme.typography.body.regular,
},
tailViewTextStyle: {
color: theme.color.textPrimary,
...theme.typography.caption1.medium,
},
avatarStyle: {
containerStyle: {},
textStyle: {},
imageStyle: {
height: 48,
width: 48,
},
},
},
};
}, [theme]);
const convertMinutesToTime = useCallback((decimalMinutes: number) => {
const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds
const minutes = Math.floor(totalSeconds / 60); // Get whole minutes
const seconds = totalSeconds % 60; // Get remaining seconds
return `${minutes} min ${seconds} sec`;
}, []);
const _render = ({ item, index }: any) => {
const { title, avatarUrl } = getCallDetails(item);
return (
{formattedInitiatedAt}
}
// avatarName={title}
title={title}
// subtitle={'8 August, 8:14 pm'}
avatarURL={avatarUrl}
TrailingView={
{convertMinutesToTime(item.getTotalDurationInMinutes())}
}
/>
);
};
return (
{data.length && (
item.sessionId + '_' + index}
renderItem={_render}
/>
)}
);
};
================================================
FILE: examples/SampleApp/src/components/calls/CallRecordings.tsx
================================================
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {useTheme, CometChatListItem} from '@cometchat/chat-uikit-react-native';
import React, {useRef, useEffect, useMemo, useState} from 'react';
import {
View,
FlatList,
Text,
Pressable,
NativeEventEmitter,
NativeModules,
Platform,
} from 'react-native';
import {PlayArrow} from './icons';
import {Icon} from '@cometchat/chat-uikit-react-native';
import {CallDetailHelper} from './CallDetailHelper';
const {FileManager} = NativeModules;
const eventEmitter = new NativeEventEmitter(FileManager);
export const CallRecordings = (props: {call: any}) => {
const {call} = props;
const [data, setData] = useState(call.getRecordings());
const theme = useTheme();
const loggedInUser = useRef(null);
const downloadIdRef = useRef(0);
const [processing, setProcessing] = React.useState(false);
const [fileExists, setFileExists] = React.useState(false); // State to track if the file exists
let listener: any = useRef(undefined);
const [fileUrl, setFileUrl] = useState();
const [reOpenCount, setReopenCount] = useState(0);
useEffect(() => {
CometChat.getLoggedinUser()
.then((u: any) => {
loggedInUser.current = u;
})
.catch((e: any) => {
// onError && onError(e);
});
}, []);
useEffect(() => {
if (Platform.OS == 'android') {
listener.current = eventEmitter.addListener(
'downloadComplete',
(data: {downloadId: number}) => {
if (
data.downloadId &&
downloadIdRef.current &&
data.downloadId == downloadIdRef.current
) {
setProcessing(false);
setFileExists(true);
openFile(fileUrl);
}
},
);
}
return () => {
if (Platform.OS == 'android') {
listener.current.remove();
}
};
}, []);
useEffect(() => {
if (!fileUrl) return;
const fileName = getFileName(fileUrl);
setProcessing(true);
FileManager.doesFileExist(fileName, (result: string) => {
setProcessing(false);
if (JSON.parse(result).exists) {
setFileExists(true);
openFile(fileUrl);
} else {
downloadFile(fileUrl);
}
});
}, [fileUrl, reOpenCount]);
const downloadFile = (fileUrl: any) => {
if (processing || fileExists) return; // Do not process if file already exists
if (!fileUrl) return;
setProcessing(true);
FileManager.checkAndDownload(
fileUrl,
getFileName(fileUrl),
(result: string) => {
if (Platform.OS == 'ios') {
let parsedResult = JSON.parse(result);
if (parsedResult.success == true) {
setProcessing(false);
setFileExists(true);
}
openFile(fileUrl);
} else if (Platform.OS == 'android') {
downloadIdRef.current = JSON.parse(result).downloadId;
}
},
);
};
const openFile = (fileUrl: any) => {
if (processing) return;
if (!fileUrl) return;
setProcessing(true);
FileManager.openFileWithOption(getFileName(fileUrl), (isOpened: string) => {
setProcessing(false);
});
};
const getFileName = (fileUrl: any) => {
return fileUrl.substring(fileUrl.lastIndexOf('/') + 1).replace(' ', '_');
};
const _style = useMemo(() => {
return {
headerContainerStyle: {
alignItems: 'flex-start',
justifyContent: 'center',
width: '100%',
borderRadius: 0,
paddingHorizontal: 0,
},
titleSeparatorStyle: {
borderBottomWidth: 1,
borderBottomColor: theme.color.borderLight,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
},
containerStyle: {
backgroundColor: theme.color.background1,
flex: 1,
},
itemStyle: {
containerStyle: {
flexDirection: 'row' as const,
paddingHorizontal: theme.spacing.padding.p4,
paddingVertical: theme.spacing.padding.p2,
gap: theme.spacing.spacing.s3,
minHeight: 72,
},
titleStyle: {
color: theme.color.textPrimary,
flex: 1,
...theme.typography.heading4.medium,
},
subtitleStyle: {
color: theme.color.textSecondary,
...theme.typography.body.regular,
},
tailViewTextStyle: {
color: theme.color.textPrimary,
...theme.typography.caption1.medium,
},
avatarStyle: {
containerStyle: {},
textStyle: {},
imageStyle: {
height: 48,
width: 48,
},
},
},
};
}, [theme]);
const formattedInitiatedAt = useMemo(() => {
return CallDetailHelper.getFormattedInitiatedAt(call);
}, [call]);
const _render = ({item, index}: any) => {
const title = item['rid'];
return (
{formattedInitiatedAt}
}
titleStyle={_style.itemStyle.titleStyle}
containerStyle={_style.itemStyle.containerStyle}
trailingViewContainerStyle={{
alignSelf: 'center',
}}
TrailingView={
{
setFileUrl(item.getRecordingURL());
setReopenCount(reOpenCount + 1);
}}>
}
containerStyle={{marginLeft: theme.spacing.margin.m4}}>
}
/>
);
};
return (
{data.length && (
item.sessionId + '_' + index}
renderItem={_render}
/>
)}
);
};
================================================
FILE: examples/SampleApp/src/components/calls/Calls.tsx
================================================
import {CometChatCallLogs, useTheme, Icon} from '@cometchat/chat-uikit-react-native';
import {useFocusEffect, useNavigation} from '@react-navigation/native';
import React, {useCallback} from 'react';
import {View, TouchableOpacity} from 'react-native';
import {StackNavigationProp} from '@react-navigation/stack';
import {RootStackParamList} from '../../navigation/types';
import { useConfig } from '../../config/store';
type CallNavigationProp = StackNavigationProp;
const Calls: React.FC = () => {
const [shouldHide, setShouldHide] = React.useState(false);
const navigation = useNavigation();
const oneOnOneVoiceCalling = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling
);
const oneOnOneVideoCalling = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling
);
const groupVideoConference = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference
);
const groupVoiceConference = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference
);
useFocusEffect(
useCallback(() => {
setShouldHide(false);
return () => {
setShouldHide(true);
};
}, []),
);
const theme = useTheme();
const onItemPress = (item: any) => {
navigation.navigate('CallDetails', {
call: item,
});
};
// Create TrailingView that respects visibility configuration
const TrailingView = useCallback((call?: any, onPress?: (call: any) => void) => {
if (!call || !onPress) return (<>>);
const receiverType = call?.getReceiverType?.();
const callType = call?.getType?.();
const isUser = receiverType === 'user';
const isAudio = callType === 'audio';
// Check if button should be hidden based on configuration
const shouldHide =
(isUser && isAudio && !oneOnOneVoiceCalling) ||
(isUser && !isAudio && !oneOnOneVideoCalling) ||
(!isUser && isAudio && !groupVoiceConference) ||
(!isUser && !isAudio && !groupVideoConference);
if (shouldHide) {
return ;
}
// Return call button - styling matches CometChatCallLogs default button
return (
onPress(call)}
style={{
marginLeft: "auto",
}}
>
);
}, [oneOnOneVoiceCalling, oneOnOneVideoCalling, groupVoiceConference, groupVideoConference, theme]);
return (
{!shouldHide && (
)}
);
};
export default Calls;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-end-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-end.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-log-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-log.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-made-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-made.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-missed-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-missed-outgoing-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-missed-outgoing.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-missed.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-received-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call-received.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/call.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/cancel-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/calls/icons/index.tsx
================================================
export {default as Call} from './call';
export {default as CallEnd} from './call-end';
export {default as CallEndFill} from './call-end-fill';
export {default as CallFill} from './call-fill';
export {default as CallLog} from './call-log';
export {default as CallLogFill} from './call-log-fill';
export {default as CallMade} from './call-made';
export {default as CallMadeFill} from './call-made-fill';
export {default as CallMissed} from './call-missed';
export {default as CallMissedFill} from './call-missed-fill';
export {default as CallMissedOutgoing} from './call-missed-outgoing';
export {default as CallMissedOutgoingFill} from './call-missed-outgoing-fill';
export {default as CallReceived} from './call-received';
export {default as CallReceivedFill} from './call-received-fill';
export {default as CancelFill} from './cancel-fill';
export {default as PlayArrow} from './play-arrow';
================================================
FILE: examples/SampleApp/src/components/calls/icons/play-arrow.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleApp/src/components/conversations/helper/GroupListeners.ts
================================================
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {CometChatUIKit} from '@cometchat/chat-uikit-react-native';
export const listners = {
addListener: {
groupListener: ({groupListenerId, handleGroupListener}: any) =>
CometChat.addGroupListener(
groupListenerId,
new CometChat.GroupListener({
onGroupMemberKicked: (
message: any,
kickedUser: any,
kickedBy: any,
kickedFrom: any,
) => {
handleGroupListener(kickedFrom);
},
onGroupMemberBanned: (
message: any,
bannedUser: any,
bannedBy: any,
bannedFrom: any,
) => {
handleGroupListener(bannedFrom);
},
onMemberAddedToGroup: (
message: any,
userAdded: any,
userAddedBy: any,
userAddedIn: any,
) => {
handleGroupListener(userAddedIn);
},
onGroupMemberLeft: (message: any, leavingUser: any, group: any) => {
handleGroupListener(group);
},
onGroupMemberScopeChanged: (
message: any,
changedUser: CometChat.User,
newScope: any,
oldScope: any,
changedGroup: CometChat.Group,
) => {
console.log('changedGroup: ', message, changedGroup);
if (changedUser.getUid() == CometChatUIKit.loggedInUser!.getUid()) {
changedGroup.setScope(newScope);
handleGroupListener(changedGroup);
}
},
}),
),
},
removeListner: {
removeUserListener: ({userStatusListenerId}: any) =>
CometChat.removeUserListener(userStatusListenerId),
removeGroupListener: ({groupListenerId}: any) =>
CometChat.removeGroupListener(groupListenerId),
},
};
================================================
FILE: examples/SampleApp/src/components/conversations/screens/AddMember.tsx
================================================
import { CometChat } from '@cometchat/chat-sdk-react-native';
import React, {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
import {
View,
Text,
TouchableOpacity,
NativeModules,
Platform,
KeyboardAvoidingView,
BackHandler,
} from 'react-native';
import {
CometChatGroupsEvents,
CometChatUIEventHandler,
CometChatUIKit,
CometChatUiKitConstants,
CometChatUsers,
CometChatUsersActionsInterface,
useCometChatTranslation,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import { Icon } from '@cometchat/chat-uikit-react-native';
import {
useRoute,
useNavigation,
RouteProp,
useFocusEffect,
} from '@react-navigation/native';
import { styles } from './AddMemberStyles';
import { commonVars } from '@cometchat/chat-uikit-react-native/src/shared/base/vars';
import ArrowBack from '../../../assets/icons/ArrowBack';
import { RootStackParamList } from '../../../navigation/types';
import { useConfig } from '../../../config/store';
const { CommonUtil } = NativeModules;
const AddMember: React.FC = () => {
const route = useRoute>();
const navigation = useNavigation();
const { group } = route.params;
const theme = useTheme();
const { t } = useCometChatTranslation();
const userRef = useRef(null);
const [selectedUsers, setSelectedUsers] = useState([]);
const [errorToastVisible, setErrorToastVisible] = useState(false);
const [errorToastMessage, setErrorToastMessage] = useState('');
const errorTimeoutRef = useRef | null>(null);
const [kbOffset, setKbOffset] = React.useState(900);
const userAndFriendsPresence = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence
);
useEffect(() => {
return () => {
if (errorTimeoutRef.current) {
clearTimeout(errorTimeoutRef.current);
}
};
}, []);
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
// Navigate back to message screen (same as your onPress handler)
navigation.goBack();
return true; // Prevent default back behavior
};
const subscription = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
return () => subscription.remove();
}, [navigation]),
);
useLayoutEffect(() => {
if (Platform.OS === 'ios') {
if (Number.isInteger(commonVars.safeAreaInsets.top)) {
setKbOffset(commonVars.safeAreaInsets.top ?? 0);
return;
}
CommonUtil.getSafeAreaInsets().then((res: any) => {
if (Number.isInteger(res.top)) {
commonVars.safeAreaInsets.top = res.top;
commonVars.safeAreaInsets.bottom = res.bottom;
setKbOffset(res.top);
}
});
}
}, []);
const addMembersToGroup = useCallback(
async (users: Array) => {
try {
const membersList = users.map((item: CometChat.User) => {
const groupMember = new CometChat.GroupMember(
item.getUid(),
CometChat.GROUP_MEMBER_SCOPE.PARTICIPANT,
);
groupMember.setName(item.getName());
return groupMember;
});
const guid = group.getGuid();
const response = await CometChat.addMembersToGroup(
guid,
membersList,
[],
);
const addedUIDs = Object.entries(response)
.filter(([_, status]) => status === 'success')
.map(([uid]) => uid);
const addedMembers = membersList.filter(member =>
addedUIDs.includes(member.getUid()),
);
if (addedUIDs.length > 0) {
navigation.goBack();
} else {
setErrorToastMessage('Error, Unable to add members');
setErrorToastVisible(true);
errorTimeoutRef.current = setTimeout(() => {
setErrorToastVisible(false);
}, 3000);
}
// If all succeeded, emit individual events for each member
if (addedMembers.length > 0) {
const groupInfo = await CometChat.getGroup(group.getGuid());
group.setMembersCount(groupInfo.getMembersCount());
// Create separate action for each added member
addedMembers.forEach(member => {
const action: CometChat.Action = new CometChat.Action(
guid,
CometChatUiKitConstants.MessageTypeConstants.groupMember,
CometChat.RECEIVER_TYPE.GROUP,
CometChat.CATEGORY_ACTION as CometChat.MessageCategory,
);
action.setConversationId(guid);
action.setActionBy(CometChatUIKit.loggedInUser!);
action.setActionFor(group);
action.setSender(CometChatUIKit.loggedInUser!);
// Initialize data to prevent crash when SDK accesses getData().metadata during render
action.setData({ metadata: {} });
// Emit individual event for each member added
CometChatUIEventHandler.emitGroupEvent(
CometChatGroupsEvents.ccGroupMemberAdded,
{
addedBy: CometChatUIKit.loggedInUser,
message: action,
usersAdded: [member],
userAddedIn: group,
},
);
});
}
} catch (error) {
console.error('Something went wrong', error);
setErrorToastMessage('Error, Unable to add members');
setErrorToastVisible(true);
errorTimeoutRef.current = setTimeout(() => {
setErrorToastVisible(false);
}, 2000);
}
},
[group, navigation],
);
const handleUserSelection = useCallback((users: CometChat.User[]) => {
setSelectedUsers(users);
}, []);
return (
{/* Header */}
navigation.goBack()}
>
}
/>
{t('ADD_MEMBERS')}
{/* Users List */}
navigation.goBack()}
usersStatusVisibility={userAndFriendsPresence}
/>
{/* Add Members Button */}
addMembersToGroup(selectedUsers)}
style={styles.addMembersButton}
>
{t('ADD_MEMBERS')}
{/* Error Toast */}
{errorToastVisible && (
{errorToastMessage}
)}
);
};
export default AddMember;
================================================
FILE: examples/SampleApp/src/components/conversations/screens/AddMemberStyles.tsx
================================================
import { StyleSheet } from "react-native";
export const styles = StyleSheet.create({
toastTextStyle: {
color: '#fff',
fontSize: 16,
},
toastContainer: {
position: 'absolute',
bottom: 20,
left: 20,
right: 20,
paddingVertical: 10,
borderRadius: 5,
alignItems: 'center',
},
addMemberContainer: {
paddingTop: 15,
paddingLeft: 10,
flexDirection: 'row',
paddingBottom: 15,
borderBottomWidth: 1,
},
addMemberText: {
paddingLeft: 10,
},
iconContainer: {
flexDirection: 'row',
alignItems: 'center',
},
addMembersButton: {
alignContent: 'center',
justifyContent: 'center',
paddingVertical: 1,
height: 50,
width: '100%',
alignSelf: 'center',
},
addMembersButtonContainer: {
marginHorizontal: 20,
alignSelf: 'center',
justifyContent: 'center',
borderRadius: 6,
height: '75%',
width: '95%',
},
});
================================================
FILE: examples/SampleApp/src/components/conversations/screens/BannedMember.tsx
================================================
import React, { useCallback, useRef, useState } from 'react';
import {
View,
Text,
TouchableOpacity,
Modal,
TouchableWithoutFeedback,
BackHandler,
} from 'react-native';
import {
CometChatConfirmDialog,
CometChatList,
CometChatListActionsInterface,
} from '@cometchat/chat-uikit-react-native/src/shared';
import { Skeleton } from '@cometchat/chat-uikit-react-native/src/CometChatUsers/Skeleton';
import {
Icon,
useCometChatTranslation,
} from '@cometchat/chat-uikit-react-native';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import {
CometChatGroupsEvents,
CometChatUIEventHandler,
CometChatUIKit,
CometChatUiKitConstants,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import { ErrorEmptyView } from '@cometchat/chat-uikit-react-native/src/shared/views/ErrorEmptyView/ErrorEmptyView';
import { RouteProp, useFocusEffect } from '@react-navigation/native';
import { RootStackParamList } from '../../../navigation/types';
import { StackNavigationProp } from '@react-navigation/stack';
import {
getBannedMemberStyleLight,
styles,
getBannedMemberStyleDark,
} from './BannedMemberStyles';
import UserEmptyIcon from '../../../assets/icons/UserEmptyIcon';
import Close from '../../../assets/icons/Close';
import Block from '../../../assets/icons/Block';
type BannedMembersRouteProp = {
route: RouteProp;
navigation: StackNavigationProp;
};
const BannedMember: React.FC = ({
route,
navigation,
}) => {
const { group } = route.params;
const theme = useTheme();
const { t } = useCometChatTranslation();
const bannedListRef = useRef(null);
const [isModalVisible, setModalVisible] = useState(false);
const [selectedUser, setSelectedUser] = useState(null);
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
// Navigate back to message screen (same as your onPress handler)
navigation.goBack();
return true; // Prevent default back behavior
};
const subscription = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
return () => subscription.remove();
}, [navigation]),
);
/**
* --- Callbacks / Handlers ---
*/
const openUnbanModal = (user: CometChat.User) => {
setSelectedUser(user);
setModalVisible(true);
};
const closeUnbanModal = () => {
setModalVisible(false);
setSelectedUser(null);
};
const handleUnbanUser = async () => {
if (!group || !selectedUser) return;
try {
const guid = group.getGuid();
const uid = selectedUser.getUid();
// Unban the user
await CometChat.unbanGroupMember(guid, uid);
// Create and dispatch an Action message for the unban event
const actionMessage = new CometChat.Action(
guid,
CometChatUiKitConstants.MessageTypeConstants.groupMember,
CometChat.RECEIVER_TYPE.GROUP,
CometChat.CATEGORY_ACTION as CometChat.MessageCategory,
);
actionMessage.setConversationId(guid);
actionMessage.setActionFor(group);
actionMessage.setActionOn(selectedUser);
actionMessage.setActionBy(CometChatUIKit.loggedInUser!);
actionMessage.setSender(CometChatUIKit.loggedInUser!);
// Initialize data to prevent crash when SDK accesses getData().metadata during render
actionMessage.setData({ metadata: {} });
actionMessage.setMessage(
`${CometChatUIKit.loggedInUser?.getName()} ${t(
'UNBANNED',
)} ${selectedUser.getName()}`,
);
CometChatUIEventHandler.emitGroupEvent(
CometChatGroupsEvents.ccGroupMemberUnBanned,
{
unbannedBy: CometChatUIKit.loggedInUser,
userUnbanned: selectedUser,
group,
message: actionMessage,
},
);
// Remove from the banned list
bannedListRef.current?.removeItemFromList(uid);
// Close modal
closeUnbanModal();
} catch (error) {
console.error('Error unbanning user:', error);
}
};
/**
* --- Render Helpers ---
*/
const renderEmptyView = useCallback(() => {
return (
}
size={theme.spacing.spacing.s20}
containerStyle={{ marginBottom: theme.spacing.spacing.s5 }}
/>
}
containerStyle={styles.emptyViewContainer}
titleStyle={[
theme.userStyles.emptyStateStyle.titleStyle,
{ color: theme.color.textPrimary },
]}
/>
);
}, [theme]);
const renderLoadingView = () => ;
const renderTailView = (user: CometChat.User) => (
openUnbanModal(user)}>
}
/>
);
const renderUnbanModal = () => {
if (!selectedUser) return null;
return (
}
cancelButtonText={t('CANCEL')}
confirmButtonText={t('UNBAN')}
messageText={t('UNBAN_SURE') + ' ' + selectedUser.getName()}
isOpen={true}
onCancel={closeUnbanModal}
onConfirm={handleUnbanUser}
/>
);
};
/**
* --- Main JSX ---
*/
return (
<>
navigation.goBack()}
listItemKey="uid"
hideBackButton={false}
LoadingView={renderLoadingView}
EmptyView={renderEmptyView}
TrailingView={renderTailView}
listStyle={
theme.mode === 'light'
? getBannedMemberStyleLight(theme)
: getBannedMemberStyleDark(theme)
}
requestBuilder={new CometChat.BannedMembersRequestBuilder(
group.getGuid(),
).setLimit(30)}
/>
{renderUnbanModal()}
>
);
};
export default BannedMember;
================================================
FILE: examples/SampleApp/src/components/conversations/screens/BannedMemberStyles.tsx
================================================
import {StyleSheet} from 'react-native';
import {
CometChatListStylesInterface,
CometChatTheme,
} from '@cometchat/chat-uikit-react-native';
import {deepMerge} from '@cometchat/chat-uikit-react-native/src/shared/helper/helperFunctions';
import {DeepPartial} from '@cometchat/chat-uikit-react-native/src/shared/helper/types';
import {ColorValue, ViewStyle} from 'react-native';
export const styles = StyleSheet.create({
flexContainer: {
flex: 1,
},
emptyViewContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: '10%',
},
/* Modal Styles */
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
modalContainer: {
position: 'absolute',
top: '30%',
left: '2%',
right: '2%',
borderRadius: 10,
padding: 20,
elevation: 5,
},
modalIconContainer: {
alignSelf: 'center',
marginBottom: 10,
width: 64,
height: 64,
borderRadius: 32,
justifyContent: 'center',
alignItems: 'center',
},
modalContentContainer: {
alignItems: 'center',
},
modalTitle: {
marginBottom: 15,
},
modalDesc: {
textAlign: 'center',
marginBottom: 20,
},
buttonContainer: {
flexDirection: 'row',
},
cancelButton: {
flex: 1,
paddingVertical: 10,
marginHorizontal: 5,
borderRadius: 5,
borderWidth: 1,
alignItems: 'center',
},
unbanButton: {
flex: 1,
paddingVertical: 10,
marginHorizontal: 5,
borderRadius: 5,
alignItems: 'center',
},
});
export type BannedMemberStyle = CometChatListStylesInterface & {
skeletonStyle: {
backgroundColor: ColorValue;
linearGradientColors: [string, string];
shimmerBackgroundColor: ColorValue;
shimmerOpacity: number;
speed: number;
};
headerContainerStyle: ViewStyle;
};
export const getBannedMemberStyleLight = (
theme: CometChatTheme,
): DeepPartial => {
const {color, spacing, typography} = theme;
return {
headerContainerStyle: {
alignItems: 'flex-start',
justifyContent: 'center',
width: '100%',
borderRadius: 0,
paddingHorizontal: 0,
},
titleSeparatorStyle: {
borderBottomWidth: 1,
borderBottomColor: color.borderLight,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
},
containerStyle: {
backgroundColor: color.background1,
flex: 1,
},
itemStyle: {
containerStyle: {
flexDirection: 'row',
paddingHorizontal: spacing.padding.p4,
paddingVertical: spacing.padding.p2,
gap: spacing.spacing.s3,
},
titleStyle: {
color: color.textPrimary,
...typography.heading4.medium,
},
subtitleStyle: {
color: color.textSecondary,
...typography.body.regular,
},
statusIndicatorStyle: {},
avatarStyle: {
containerStyle: {},
textStyle: {},
imageStyle: {},
},
headViewContainerStyle: {},
titleSubtitleContainerStyle: {
alignSelf: 'center',
},
trailingViewContainerStyle: {
alignSelf: 'center',
},
},
confirmSelectionStyle: {},
selectionCancelStyle: {},
loadingIconTint: color.primary,
sectionHeaderTextStyle: {
marginHorizontal: spacing.spacing.s5,
color: color.primary,
...typography.heading4.medium,
},
onlineStatusColor: color.success,
titleViewStyle: {
paddingVertical: spacing.spacing.s3,
paddingLeft: spacing.spacing.s3,
margin: spacing.spacing.s0,
},
titleStyle: {
color: color.textPrimary,
...typography.heading1.bold,
},
backButtonIconStyle: {
tintColor: color.iconPrimary,
height: spacing.spacing.s6,
width: spacing.spacing.s6,
},
searchStyle: {
textStyle: {
color: color.textPrimary,
...typography.heading4.regular,
textAlignVertical: 'center',
paddingVertical: 0,
height: spacing.spacing.s7,
},
containerStyle: {
backgroundColor: color.background3,
paddingVertical: spacing.spacing.s3,
marginTop: spacing.spacing.s3,
width: '95%',
gap: spacing.spacing.s1,
alignContent: 'space-around',
alignSelf: 'center',
flexDirection: 'row',
alignItems: 'center',
},
icon: undefined,
iconStyle: {
tintColor: color.iconSecondary,
},
placehodlerTextStyle: {
color: color.textTertiary,
},
},
emptyStateStyle: {
titleStyle: {
color: color.textPrimary,
...typography.heading3.bold,
marginBottom: spacing.margin.m1,
},
subTitleStyle: {
color: color.textSecondary,
textAlign: 'center' as const,
...typography.body.regular,
},
containerStyle: {
justifyContent: 'center',
display: 'none',
alignItems: 'center',
padding: spacing.padding.p3,
},
},
errorStateStyle: {
titleStyle: {
color: color.textPrimary,
...typography.heading3.bold,
marginBottom: spacing.margin.m1,
},
subTitleStyle: {
color: color.textSecondary,
textAlign: 'center' as const,
...typography.body.regular,
},
containerStyle: {
justifyContent: 'center',
alignItems: 'center',
padding: spacing.padding.p3,
},
},
skeletonStyle: {
backgroundColor: color.background3,
linearGradientColors: ['#E8E8E8', '#F5F5F5'] as [string, string],
shimmerBackgroundColor: color.staticBlack,
shimmerOpacity: 0.01,
speed: 1,
},
};
};
export const getBannedMemberStyleDark = (
theme: CometChatTheme,
): DeepPartial => {
const {color, spacing, typography} = theme;
return deepMerge(getBannedMemberStyleLight(theme), {
skeletonStyle: {
backgroundColor: color.background3,
linearGradientColors: ['#383838', '#272727'] as [string, string],
shimmerBackgroundColor: color.staticWhite,
shimmerOpacity: 0.01,
speed: 1,
},
});
};
================================================
FILE: examples/SampleApp/src/components/conversations/screens/Conversations.tsx
================================================
import { CometChat } from '@cometchat/chat-sdk-react-native';
import React, { useCallback, useContext, useRef, useState } from 'react';
import { TouchableOpacity, View } from 'react-native';
import {
CometChatAvatar,
CometChatConversations,
CometChatUIKit,
useCometChatTranslation,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import { AuthContext } from '../../../navigation/AuthContext';
import {
useFocusEffect,
useNavigation,
CommonActions,
} from '@react-navigation/native';
import { TooltipMenu } from '../../../utils/TooltipMenu';
import { StackNavigationProp } from '@react-navigation/stack';
import { RootStackParamList } from '../../../navigation/types';
import AccountCircle from '../../../assets/icons/AccountCircle';
import AddComment from '../../../assets/icons/AddComment';
import InfoIcon from '../../../assets/icons/InfoIcon';
import Logout from '../../../assets/icons/Logout';
import { navigate, navigationRef } from '../../../navigation/NavigationService';
import { AppConstants, SCREEN_CONSTANTS } from '../../../utils/AppConstants';
import Builder from '../../../assets/icons/Builder';
import { useConfig, useConfigStore } from '../../../config/store'; // adjust import if needed
import AsyncStorage from '@react-native-async-storage/async-storage';
import Reset from '../../../assets/icons/Reset';
import AiIcon from '../../../assets/icons/AiIcon';
type ChatNavigationProp = StackNavigationProp<
RootStackParamList,
'Conversation'
>;
const Conversations: React.FC<{}> = ({}) => {
const theme = useTheme();
const { setIsLoggedIn: setLogout } = useContext(AuthContext);
const [isLoggingOut, setIsLoggingOut] = useState(false);
const tooltipPositon = React.useRef({ pageX: 0, pageY: 0 });
const [tooltipVisible, setTooltipVisible] = useState(false);
const selectedConversation = useRef(null);
const navigation = useNavigation();
const avatarContainerRef = useRef(null);
const loggedInUser = useRef(
CometChatUIKit.loggedInUser,
).current;
const { t } = useCometChatTranslation();
const [isConfigUpdated, setIsConfigUpdated] = useState(false);
const messageDeliveryAndReadReceipts = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts
);
useFocusEffect(
useCallback(() => {
// Check config updated flag
AsyncStorage.getItem('@config_updated').then(val => {
setIsConfigUpdated(val === 'true');
});
return () => {
setTooltipVisible(false);
};
}, []),
);
const handleResetConfig = async () => {
useConfigStore.getState().resetConfig();
await AsyncStorage.removeItem('@config_updated');
setIsConfigUpdated(false);
};
const userAndFriendsPresence = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence
);
const openMessagesFor = (item: CometChat.Conversation) => {
// Determine if it's a user or group conversation
const isUser = item.getConversationType() === 'user';
const isGroup = item.getConversationType() === 'group';
// Navigate to Messages with appropriate params
navigation.navigate('Messages', {
user: isUser ? (item.getConversationWith() as CometChat.User) : undefined,
group: isGroup
? (item.getConversationWith() as CometChat.Group)
: undefined,
});
};
const _conversationsConfig = {
onItemPress: openMessagesFor,
onError: (err: any) => {
console.log('ERROR IN CONVO: ', err);
},
};
const handleAvatarPress = () => {
try {
if (avatarContainerRef.current) {
avatarContainerRef.current.measureInWindow((x, y, height) => {
// Set tooltip position 10px below the avatar
tooltipPositon.current = { pageX: x, pageY: y + height };
});
selectedConversation.current = null;
setTooltipVisible(true);
}
} catch (error) {
console.error('Error while handling avatar press:', error);
}
};
const handleLogout = async () => {
if (isLoggingOut) return;
setIsLoggingOut(true);
// Step 1: Logout from CometChat
try {
await CometChat.logout();
} catch (error) {
console.error('CometChat logout failed:', error);
setIsLoggingOut(false);
return; // Exit if CometChat logout fails
}
// If all operations succeed, navigate to the LoginScreen
setIsLoggingOut(false);
setLogout(false);
navigationRef.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'SampleUser' }],
}),
);
};
const NewConversation = () => {
return (
{
handleAvatarPress();
}}
>
);
};
return (
{
navigate(SCREEN_CONSTANTS.SEARCH_MESSAGES);
}}
usersStatusVisibility={userAndFriendsPresence}
receiptsVisibility={messageDeliveryAndReadReceipts}
/>
{
setTooltipVisible(false);
}}
onDismiss={() => {
setTooltipVisible(false);
}}
event={{
nativeEvent: tooltipPositon.current,
}}
menuItems={[
{
text: t('CREATE_CONVERSATION'),
onPress: () => {
navigation.navigate('CreateConversation');
},
icon: (
),
textColor: theme.color.textPrimary,
iconColor: theme.color.textPrimary,
},
{
text: t('AI_ASSISTANTS'),
onPress: () => {
navigation.navigate(SCREEN_CONSTANTS.AI_AGENTS);
},
icon: (
),
textColor: theme.color.textPrimary,
iconColor: theme.color.textPrimary,
},
{
text: loggedInUser?.getName() || 'User',
onPress: () => {
setTooltipVisible(false);
},
icon: (
),
textColor: theme.color.textPrimary,
iconColor: theme.color.textPrimary,
},
{
text: t('LOGOUT'),
onPress: () => {
handleLogout();
},
icon: (
),
textColor: theme.color.error,
iconColor: theme.color.error,
},
{
text: AppConstants.versionNumber,
onPress: () => {},
icon: (
),
},
isConfigUpdated
? {
text: 'Reset to Default',
onPress: handleResetConfig,
icon: (
),
textColor: theme.color.textPrimary,
iconColor: theme.color.textPrimary,
}
: {
text: 'Builder Live Preview',
onPress: () => {
navigation.navigate(SCREEN_CONSTANTS.QR_SCREEN);
},
icon: (
),
textColor: theme.color.textPrimary,
iconColor: theme.color.textPrimary,
},
]}
/>
);
};
export default Conversations;
================================================
FILE: examples/SampleApp/src/components/conversations/screens/CreateConversation.tsx
================================================
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native';
import {
useTheme,
CometChatUsers,
useCometChatTranslation,
} from '@cometchat/chat-uikit-react-native';
import { RootStackParamList } from '../../../navigation/types';
import Groups from '../../groups/Groups';
import { Icon } from '@cometchat/chat-uikit-react-native';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import ArrowBack from '../../../assets/icons/ArrowBack';
// Define prop types for the component using React Navigation's types
type Props = {
route: RouteProp;
navigation: StackNavigationProp;
};
const CreateConversation: React.FC = ({ route, navigation }) => {
const theme = useTheme();
const { t } = useCometChatTranslation();
const {
background1,
background3,
iconPrimary,
textPrimary,
textSecondary,
primary,
} = theme.color;
const { heading1 } = theme.typography;
const [selectedTab, setSelectedTab] = useState<'Users' | 'Groups'>('Users');
return (
{/* Header */}
}
/>
{t('NEW_CHAT')}
{/* Tab Bar */}
{['Users', 'Groups'].map(tab => (
setSelectedTab(tab as 'Users' | 'Groups')}
>
{t(tab.toUpperCase())}
))}
{/* Content */}
{selectedTab === 'Users' ? (
navigation.navigate('Messages', { user })
}
/>
) : (
)}
);
};
export default CreateConversation;
// Style definitions for the component
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
paddingTop: 15,
paddingLeft: 10,
flexDirection: 'row',
alignItems: 'center',
},
rowCenter: {
flexDirection: 'row',
alignItems: 'center',
},
headerText: {
paddingLeft: 5,
},
tabContainer: {
marginTop: 20,
flexDirection: 'row',
marginHorizontal: 20,
borderRadius: 30,
padding: 5,
},
tabButton: {
flex: 1,
paddingVertical: 10,
alignItems: 'center',
borderRadius: 30,
},
activeTab: {
borderRadius: 30,
},
tabText: {
fontSize: 16,
fontWeight: 'bold',
},
content: {
flex: 1,
},
});
================================================
FILE: examples/SampleApp/src/components/conversations/screens/GroupInfo.tsx
================================================
import React, { useEffect, useRef, useState } from 'react';
import {
View,
Text,
TouchableOpacity,
useWindowDimensions,
BackHandler,
} from 'react-native';
import { RouteProp, useFocusEffect } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import {
CometChatAvatar,
CometChatGroupsEvents,
CometChatUIEventHandler,
CometChatConfirmDialog,
useTheme,
CometChatConversationEvents,
CometChatUIEvents,
useCometChatTranslation,
} from '@cometchat/chat-uikit-react-native';
import { Icon } from '@cometchat/chat-uikit-react-native';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import {
CometChatUIKit,
CometChatUiKitConstants,
} from '@cometchat/chat-uikit-react-native';
import { RootStackParamList } from '../../../navigation/types';
import { listners } from '../helper/GroupListeners';
import { styles } from './GroupInfoStyles';
import { leaveGroup } from '../../../utils/helper';
import { CommonUtils } from '../../../utils/CommonUtils';
import ArrowBack from '../../../assets/icons/ArrowBack';
import Group from '../../../assets/icons/Group';
import PersonAdd from '../../../assets/icons/PersonAdd';
import PersonOff from '../../../assets/icons/PersonOff';
import Block from '../../../assets/icons/Block';
import Delete from '../../../assets/icons/Delete';
import { useConfig } from '../../../config/store';
type GroupInfoProps = {
route: RouteProp;
navigation: StackNavigationProp;
};
const GroupInfo: React.FC = ({route, navigation}) => {
const addMembersToGroups = useConfig(
(state) => state.settings.chatFeatures.groupManagement.addMembersToGroups
);
const joinLeaveGroup = useConfig(
(state) => state.settings.chatFeatures.groupManagement.joinLeaveGroup
);
const deleteGroup = useConfig(
(state) => state.settings.chatFeatures.groupManagement.deleteGroup
);
const viewGroupMembers = useConfig(
(state) => state.settings.chatFeatures.groupManagement.viewGroupMembers
);
const { group } = route.params;
const theme = useTheme();
const { t } = useCometChatTranslation();
const groupListenerId = useRef('groupListener' + new Date().getTime());
const [data, setData] = useState({ groupDetails: group });
const [userScope, setUserScope] = useState(
group?.getOwner() === CometChatUIKit.loggedInUser?.getUid()
? CometChatUiKitConstants.GroupMemberScope.owner
: group?.getScope(),
);
// Separate states for each type of modal
const [isLeaveModalOpen, setIsLeaveModalOpen] = useState(false);
const [isOwnerLeaveModalOpen, setIsOwnerLeaveModalOpen] = useState(false);
const [isDeleteExitModalOpen, setIsDeleteExitModalOpen] = useState(false);
const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);
const { width } = useWindowDimensions();
const isSmallDevice = width < 360;
useEffect(() => {
// Update group details in state whenever group changes
const handleGroupListener = (updatedGroup: CometChat.Group) => {
if (updatedGroup.getGuid() === route.params.group.getGuid()) {
setData({ groupDetails: updatedGroup });
setUserScope(
updatedGroup?.getOwner() === CometChatUIKit.loggedInUser?.getUid()
? CometChatUiKitConstants.GroupMemberScope.owner
: (updatedGroup?.getScope() ?? userScope),
);
}
};
const handleGroupMemberKicked = ({ kickedFrom }: any) => {
handleGroupListener(CommonUtils.clone(kickedFrom));
};
const handleGroupMemberBanned = ({ kickedFrom }: any) => {
handleGroupListener(CommonUtils.clone(kickedFrom));
};
const handleGroupMemberAdded = ({ userAddedIn }: any) => {
handleGroupListener(CommonUtils.clone(userAddedIn));
};
const handleOwnershipChanged = ({ group }: any) => {
handleGroupListener(group);
};
// Add group listeners
listners.addListener.groupListener({
groupListenerId: groupListenerId.current,
handleGroupListener,
});
CometChatUIEventHandler.addGroupListener(groupListenerId.current, {
ccGroupMemberKicked: (item: any) => handleGroupMemberKicked(item),
ccGroupMemberBanned: (item: any) => handleGroupMemberBanned(item),
ccGroupMemberAdded: (item: any) => handleGroupMemberAdded(item),
ccOwnershipChanged: (item: any) => handleOwnershipChanged(item),
});
return () => {
// Cleanup
listners.removeListner.removeGroupListener({
groupListenerId: groupListenerId.current,
});
CometChatUIEventHandler.removeGroupListener(groupListenerId.current);
CometChat.removeGroupListener(groupListenerId.current);
};
}, [group, userScope]);
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
// Navigate back to message screen (same as your onPress handler)
navigation.goBack();
return true; // Prevent default back behavior
};
const subscription = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
return () => subscription.remove();
}, [navigation]),
);
const getLabel = (key: string) => {
const label = t(key);
// Split into two words if device is small
if (isSmallDevice && label.split(' ').length === 2) {
return label.split(' ').join('\n');
}
return label;
};
/**
* Handlers for each modal's confirm action
*/
// 1) Normal "Leave Group" confirm
const handleLeaveConfirm = () => {
setIsLeaveModalOpen(false);
if (data.groupDetails) {
leaveGroup(data.groupDetails, navigation, 2);
}
};
// 2) "Owner => Transfer Ownership" confirm
const handleOwnerLeaveConfirm = () => {
if (!data.groupDetails) return;
setIsOwnerLeaveModalOpen(false);
navigation.navigate('TransferOwnershipSection', {
group: data.groupDetails,
});
};
// 3) "Delete and Exit" confirm
const handleDeleteExitConfirm = () => {
setIsDeleteExitModalOpen(false);
if (!data.groupDetails) return;
// Delete group
CometChat.deleteGroup(data.groupDetails.getGuid())
.then(() => {
navigation.pop(2);
})
.catch(error => {
console.log('Group deletion failed:', error);
});
// Emit group deleted event
CometChatUIEventHandler.emitGroupEvent(
CometChatGroupsEvents.ccGroupDeleted,
{
group: data.groupDetails,
},
);
};
/** DELETE CONVERSATION LOGIC **/
const handleDeleteConversationConfirm = () => {
setDeleteModalOpen(false); // close the dialog
if (group) {
CometChat.getConversation(group.getGuid(), 'group')
.then(conversation => {
CometChat.deleteConversation(group.getGuid(), 'group')
.then(deletedConversation => {
console.log(deletedConversation);
CometChatUIEventHandler.emitConversationEvent(
CometChatConversationEvents.ccConversationDeleted,
{ conversation: conversation },
);
navigation.pop(2);
})
.catch(error => {
console.log('Error while deleting conversation:', error);
});
})
.catch(error => {
console.log('Error while deleting conversation:', error);
});
}
};
return (
{/* Header */}
navigation.goBack()}
>
}
/>
{t('GROUP_INFO')}
{/* Main Group Info Container */}
{data.groupDetails?.getName()}
{data.groupDetails?.getMembersCount() +
' ' +
t(
data.groupDetails?.getMembersCount() === 1
? 'MEMBER'
: 'MEMBERS',
)}
{/* Action Boxes: Add Members / View Members / Banned Members */}
{/* Add Members */}
{addMembersToGroups &&
[CometChatUiKitConstants.GroupMemberScope.owner,
CometChatUiKitConstants.GroupMemberScope.admin
].includes(userScope) && (
{
navigation.navigate('AddMember', { group });
}}
style={[
styles.buttonContainer,
{ borderColor: theme.color.borderDefault },
]}
>
}
containerStyle={styles.buttonIcon}
/>
{getLabel('ADD_MEMBERS')}
)}
{/* View Members */}
{viewGroupMembers && (
{
navigation.navigate('ViewMembers', { group });
}}
style={[
styles.buttonContainer,
{ borderColor: theme.color.borderDefault },
]}
>
}
containerStyle={styles.buttonIcon}
/>
{getLabel('VIEW_MEMBERS')}
)}
{/* Banned Members */}
{(userScope === CometChatUiKitConstants.GroupMemberScope.owner ||
userScope === CometChatUiKitConstants.GroupMemberScope.admin ||
userScope ===
CometChatUiKitConstants.GroupMemberScope.moderator) && (
{
navigation.navigate('BannedMember', { group });
}}
style={[
styles.buttonContainer,
{ borderColor: theme.color.borderDefault },
]}
>
}
containerStyle={styles.buttonIcon}
/>
{getLabel('BANNED_MEMBERS')}
)}
{/* Actions */}
setDeleteModalOpen(true)}
style={styles.iconContainer}
>
}
/>
{t('DELETE_CHAT_TEXT')}
{/* If user is owner but group has multiple members => must TRANSFER ownership.
Otherwise => normal leave. */}
{joinLeaveGroup &&
(data.groupDetails.getMembersCount() > 1 ||
userScope !== CometChatUiKitConstants.GroupMemberScope.owner) && (
{
if (userScope === CometChatUiKitConstants.GroupMemberScope.owner) {
setIsOwnerLeaveModalOpen(true);
} else {
setIsLeaveModalOpen(true);
}
}}
style={styles.iconContainer}
>
} />
{t('LEAVE')}
)}
{/* Delete and Exit (Group owner only) */}
{deleteGroup &&
[
CometChatUiKitConstants.GroupMemberScope.owner,
CometChatUiKitConstants.GroupMemberScope.admin,
].includes(userScope) && (
setIsDeleteExitModalOpen(true)}
style={styles.iconContainer}
>
}
/>
{t('DELETE_AND_EXIT')}
)}
{/* ============== LEAVE GROUP (Regular) Dialog ============== */}
setIsLeaveModalOpen(false)}
onConfirm={handleLeaveConfirm}
titleText={t('LEAVE_GROUP_TEXT')}
messageText={t('LEAVE_SURE')}
cancelButtonText={t('CANCEL')}
confirmButtonText={t('LEAVE')}
icon={ }
/>
{/* ============== TRANSFER OWNERSHIP Dialog ============== */}
setIsOwnerLeaveModalOpen(false)}
onConfirm={handleOwnerLeaveConfirm}
titleText={t('TRANSFER_OWNERSHIP')}
messageText={t('TRANSFER_SURE')}
cancelButtonText={t('CANCEL')}
confirmButtonText={t('TRANSFER')}
icon={ }
/>
{/* ============== DELETE AND EXIT Dialog ============== */}
setIsDeleteExitModalOpen(false)}
onConfirm={handleDeleteExitConfirm}
titleText={`${t('DELETE_AND_EXIT')}?`}
messageText={t('DELETE_AND_EXIT_SURE')}
cancelButtonText={t('CANCEL')}
confirmButtonText={t('DELETE_AND_EXIT')}
icon={ }
/>
{/* =============== DELETE CHAT MODAL =============== */}
setDeleteModalOpen(false)}
onConfirm={handleDeleteConversationConfirm}
onDismiss={() => console.log('Delete Modal dismissed')}
titleText={t('DELETE_CHAT')}
messageText={t('SURE_TO_DELETE_CHAT')}
cancelButtonText={t('CANCEL')}
confirmButtonText={t('DELETE')}
icon={ }
/>
);
};
export default GroupInfo;
================================================
FILE: examples/SampleApp/src/components/conversations/screens/GroupInfoStyles.tsx
================================================
import {StyleSheet} from 'react-native';
export const styles = StyleSheet.create({
flexOne: {
flex: 1,
},
headerContainer: {
paddingTop: 15,
paddingLeft: 10,
flexDirection: 'row',
},
ellipseTail: {paddingHorizontal: 10, width: '80%'},
iconContainer: {
flexDirection: 'row',
alignItems: 'center',
},
pL5: {
paddingLeft: 5,
},
groupInfoSection: {
marginTop: 12,
borderTopWidth: 1,
borderBottomWidth: 1,
paddingVertical: 10,
},
infoTitleContainer: {
alignSelf: 'center',
alignItems: 'center',
marginTop: 20,
},
avatarContainer: {
height: 120,
width: 120,
},
avatarText: {
fontSize: 28,
lineHeight: 55,
},
avatarImage: {
height: '100%',
width: '100%',
},
titleName: {
marginTop: 10,
// width:'20%',
alignSelf: 'center',
},
boxLabel: {
marginTop: 5,
textAlign: 'center',
alignSelf: 'center',
},
boxContainerRow: {
flexDirection: 'row',
paddingHorizontal: 20,
justifyContent: 'space-between',
marginVertical: 20,
},
buttonContainer: {
flex: 1,
paddingVertical: 10,
borderWidth: 1,
borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
marginHorizontal: 5,
},
buttonIcon: {
marginBottom: 5,
},
actionContainer: {
paddingTop: 10,
gap: 4,
},
actionButtons: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
paddingLeft: 20,
width: '100%',
},
mL5: {
marginLeft: 5,
},
});
================================================
FILE: examples/SampleApp/src/components/conversations/screens/Messages.tsx
================================================
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
TouchableOpacity,
View,
StyleSheet,
Text,
BackHandler,
Platform,
Modal,
Animated,
Dimensions,
AppState,
AppStateStatus,
} from 'react-native';
import {
CometChatUIKit,
CometChatMessageHeader,
CometChatMessageList,
CometChatCompactMessageComposer,
CometChatMessageComposer,
useTheme,
CometChatUIEventHandler,
CometChatUIEvents,
ChatConfigurator,
useCometChatTranslation,
Icon,
CometChatAIAssistantChatHistory,
CometChatAIAssistantTools,
CometChatThemeProvider,
stopStreamingForRunId,
} from '@cometchat/chat-uikit-react-native';
import { StackScreenProps } from '@react-navigation/stack';
import { RootStackParamList } from '../../../navigation/types';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import InfoIcon from '../../../assets/icons/InfoIcon';
import { CommonUtils } from '../../../utils/CommonUtils';
import Info from '../../../assets/icons/Info';
import {useActiveChat} from '../../../utils/ActiveChatContext';
import { useConfig } from '../../../config/store';
import { useGroupMemberStatus } from '../../../hooks/useGroupMemberStatus';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const { width } = Dimensions.get('window');
type Props = StackScreenProps;
const Messages: React.FC = ({ route, navigation }) => {
const {
user,
group,
fromMention = false,
fromMessagePrivately = false,
parentMessageId: routeParentMessageId,
messageId,
searchKeyword,
navigatedFromSearch
} = route.params;
const typingIndicator = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.typingIndicator
);
const threadConversationAndReplies = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.threadConversationAndReplies
);
const photosSharing = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.photosSharing
);
const videoSharing = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.videoSharing
);
const audioSharing = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.audioSharing
);
const fileSharing = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.fileSharing
);
const editMessage = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.editMessage
);
const deleteMessage = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.deleteMessage
);
const messageDeliveryAndReadReceipts = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts
);
const userAndFriendsPresence = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence
);
const mentions = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.mentions
);
const reactions = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.reactions
);
const messageTranslation = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.messageTranslation
);
const polls = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.polls
);
const collaborativeWhiteboard = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.collaborativeWhiteboard
);
const collaborativeDocument = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.collaborativeDocument
);
const voiceNotes = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.voiceNotes
);
const stickers = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.stickers
);
const userInfo = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.userInfo
);
const groupInfo = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.groupInfo
);
const sendPrivateMessageToGroupMembers = useConfig(
(state) => state.settings.chatFeatures.privateMessagingWithinGroups.sendPrivateMessageToGroupMembers
);
const oneOnOneVoiceCalling = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling
);
const oneOnOneVideoCalling = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling
);
const groupVideoConference = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference
);
const groupVoiceConference = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference
);
const compactMessageComposer = useConfig(
(state) => state.settings.layout.compactMessageComposer
);
const loggedInUser = useRef(
CometChatUIKit.loggedInUser!,
).current;
const theme = useTheme();
const { t } = useCometChatTranslation();
const themeRef = useRef(theme);
const navigationRef = useRef(navigation);
const routeRef = useRef(route);
const userListenerId = 'app_messages' + new Date().getTime();
// Stable listener id for the lifetime of this component (prevents multiple listeners)
const openmessageListenerIdRef = useRef('message_' + new Date().getTime());
const lastOpenChatRef = useRef<{ uid: string; time: number } | null>(null);
const [localUser, setLocalUser] = useState(user);
const [messageListKey, setMessageListKey] = useState(0);
const [messageComposerKey, setMessageComposerKey] = useState(0);
const [showHistoryModal, setShowHistoryModal] = useState(false);
// Manage parentMessageId in parent component
const [parentMessageId, setParentMessageId] = useState(routeParentMessageId);
const { setActiveChat } = useActiveChat();
const insets = useSafeAreaInsets();
// Add ref to track streaming state
const messageComposerRef = useRef(null);
/** Animation state for drawer */
const slideAnim = useRef(new Animated.Value(width)).current;
useEffect(() => {
if (showHistoryModal) {
Animated.timing(slideAnim, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start();
} else {
Animated.timing(slideAnim, {
toValue: width,
duration: 300,
useNativeDriver: true,
}).start();
}
}, [showHistoryModal, slideAnim]);
/** Agentic user check */
const isAgenticUser = useCallback((): boolean => {
if (localUser) {
return localUser.getRole?.() === '@agentic';
}
return false;
}, [localUser]);
const agentic = isAgenticUser();
// ============================================
// Group Kicked/Banned Detection (real-time)
// ============================================
const isNoLongerMember = useGroupMemberStatus(group);
// Stop streaming when app goes to background for agentic users
useEffect(() => {
if (!agentic) return;
const handleAppStateChange = (nextAppState: AppStateStatus) => {
if (nextAppState === 'background' || nextAppState === 'inactive') {
stopStreamingForRunId();
if (messageComposerRef.current?.resetStreaming) {
messageComposerRef.current.resetStreaming();
}
}
};
const subscription = AppState.addEventListener('change', handleAppStateChange);
return () => subscription.remove();
}, [agentic]);
useEffect(() => {
// if it’s a user chat
if (user) {
setActiveChat({ type: 'user', id: user.getUid() });
} else if (group) {
setActiveChat({ type: 'group', id: group.getGuid() });
}
// Cleanup on unmount => setActiveChat(null)
return () => {
setActiveChat(null);
// Reset streaming state when leaving chat
if (messageComposerRef.current?.resetStreaming) {
messageComposerRef.current.resetStreaming();
}
};
}, [user, group, setActiveChat]);
useEffect(() => {
const backAction = () => {
if (fromMention || fromMessagePrivately) {
navigation.goBack();
} else {
navigation.popToTop();
}
return true;
};
// Add event listener for hardware back press
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
backAction,
);
return () => backHandler.remove();
}, [navigation, fromMention, fromMessagePrivately]);
useEffect(() => {
CometChatUIEventHandler.addUserListener(userListenerId, {
ccUserBlocked: (item: { user: CometChat.User }) =>
handleccUserBlocked(item),
ccUserUnBlocked: (item: { user: CometChat.User }) =>
handleccUserUnBlocked(item),
});
const statusListenerId = 'user_status_messages_' + new Date().getTime();
if (localUser) {
CometChat.addUserListener(
statusListenerId,
new CometChat.UserListener({
onUserOnline: (onlineUser: CometChat.User) => {
if (onlineUser.getUid() === localUser.getUid()) {
console.log('🚀 ~ onUserOnline ~ onlineUser:', onlineUser);
setLocalUser(onlineUser);
}
},
onUserOffline: (offlineUser: CometChat.User) => {
console.log('🚀 ~ onUserOffline ~ offlineUser:', offlineUser);
if (offlineUser.getUid() === localUser.getUid()) {
setLocalUser(offlineUser);
}
},
}),
);
}
// Only attach the openChat listener when we are in a group context.
// This prevents stacking duplicate private chat screens because the group
// screen remains mounted underneath the user chat.
const currentListenerId = openmessageListenerIdRef.current;
if (group) {
CometChatUIEventHandler.addUIListener(currentListenerId, {
openChat: ({ user: chatUser }) => {
if (!chatUser) return;
try {
const uid = chatUser.getUid();
// 1. Debounce rapid duplicate events (within 800ms for the same UID)
const now = Date.now();
if (
lastOpenChatRef.current &&
lastOpenChatRef.current.uid === uid &&
now - lastOpenChatRef.current.time < 800
) {
return; // ignore duplicate
}
const state = navigation.getState();
const routes = state?.routes || [];
const topRoute = routes[routes.length - 1];
// If the top route already represents this user chat, skip.
if (
topRoute?.name === 'Messages' &&
(topRoute as any)?.params?.user?.getUid &&
(topRoute as any).params.user.getUid() === uid
) {
return;
}
// If any existing route (beneath) already has this user chat, pop back to it instead of pushing another.
const existingIndex = routes.findIndex(
r =>
r.name === 'Messages' &&
(r as any)?.params?.user?.getUid &&
(r as any).params.user.getUid() === uid,
);
if (existingIndex !== -1) {
const popCount = routes.length - existingIndex - 1;
if (popCount > 0) {
navigation.pop(popCount);
}
return; // we're now at the existing user chat
}
lastOpenChatRef.current = { uid, time: now };
navigation.push('Messages', { user: chatUser, fromMessagePrivately: true });
} catch (e) {
console.warn('openChat navigation prevented due to error', e);
}
},
});
}
// Close sticker panel when screen loses focus
const blurSub = navigation.addListener('blur', () => {
CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {
alignment: 'composerBottom',
child: () => null,
panelId: 'sticker',
});
});
const focusSub = navigation.addListener('focus', () => {
// Force re-mount composer so auxiliary options (StickerButton) reset correctly
setMessageComposerKey(prev => prev + 1);
});
return () => {
CometChatUIEventHandler.removeUserListener(userListenerId);
CometChat.removeUserListener(statusListenerId);
if (group) {
CometChatUIEventHandler.removeUIListener(currentListenerId);
}
blurSub();
focusSub();
};
// Listener re-attached only when group context changes
}, [navigation, group, userListenerId]);
const handleccUserBlocked = ({ user: blockedUser }: { user: CometChat.User }) => {
setLocalUser(CommonUtils.clone(blockedUser));
};
const handleccUserUnBlocked = ({ user: unblockedUser }: { user: CometChat.User }) => {
setLocalUser(CommonUtils.clone(unblockedUser));
};
/** Reset messages */
const handleNewChatClick = useCallback(() => {
if (messageComposerRef.current?.resetStreaming) {
messageComposerRef.current.resetStreaming();
}
setParentMessageId(undefined);
setMessageListKey(prev => prev + 1);
setMessageComposerKey(prev => prev + 1);
setShowHistoryModal(false);
navigation.replace('Messages', {
user,
group,
});
}, [navigation, user, group]);
/** Open chat history modal */
const handleChatHistoryClick = useCallback(() => {
setShowHistoryModal(true);
}, []);
/** Handle history message click */
const handleHistoryMessageClick = useCallback(
(message: CometChat.BaseMessage) => {
if (messageComposerRef.current && messageComposerRef.current.stopStreaming) {
messageComposerRef.current.stopStreaming();
}
setShowHistoryModal(false);
setParentMessageId(message.getId().toString());
setMessageListKey(prev => prev + 1);
setMessageComposerKey(prev => prev + 1);
},
[],
);
/** Handle history error */
const handleChatHistoryError = useCallback(
(_error: CometChat.CometChatException) => {},
[],
);
const unblock = async (userToUnblock: CometChat.User) => {
let uid = userToUnblock.getUid();
try {
const response = await CometChat.unblockUsers([uid]);
const unBlockedUser = await CometChat.getUser(uid);
if (response) {
setLocalUser(unBlockedUser);
// Optionally emit an event or let the server call do the job
CometChatUIEventHandler.emitUserEvent(
CometChatUIEvents.ccUserUnBlocked,
{
user: unBlockedUser,
},
);
}
} catch (error) {
console.error('Error unblocking user:', error);
}
};
// Options for the header menu
const options = useMemo(() => {
return () => {
// For agentic users, don't show any options menu
if (agentic) {
return [];
}
const menuOptions = [];
// Add info option first
if (group && loggedInUser) {
menuOptions.push({
text: 'Group Info',
onPress: () => {
navigation.navigate('GroupInfo', { group });
},
icon: ,
});
} else if (localUser && !localUser.getBlockedByMe()) {
menuOptions.push({
text: 'User Info',
onPress: () => {
navigation.navigate('UserInfo', { user: localUser });
},
icon: ,
});
}
// Then add search option
menuOptions.push({
text: 'Search',
onPress: () => {
if (group) {
navigation.navigate('SearchMessages', { group });
} else if (localUser) {
navigation.navigate('SearchMessages', { user: localUser });
}
},
icon: ,
});
return menuOptions;
};
}, [navigation, group, localUser, theme, agentic, loggedInUser]);
const getMentionsTap = useCallback(() => {
const mentionsFormatter =
ChatConfigurator.getDataSource().getMentionsFormatter(
loggedInUser,
theme,
);
if (user) mentionsFormatter.setUser(user);
if (group) mentionsFormatter.setGroup(group);
mentionsFormatter.setOnMentionClick(
(_message: CometChat.BaseMessage, uid: string) => {
if (uid !== loggedInUser.getUid()) {
// Get the user by UID and navigate to Messages
CometChat.getUser(uid)
.then((mentionedUser: CometChat.User) => {
navigation.push('Messages', {
user: mentionedUser,
fromMention: true,
});
})
.catch((error: any) => {
console.error('Error fetching mentioned user:', error);
});
}
},
);
return mentionsFormatter;
}, [user, group, loggedInUser, navigation, theme]);
/** Theme override for agentic outgoing bubble */
const providerTheme = useMemo(() => {
const defaultOutgoingBg =
theme?.messageListStyles?.outgoingMessageBubbleStyles?.containerStyle?.backgroundColor;
const defaultTextColor =
theme?.messageListStyles?.outgoingMessageBubbleStyles?.textBubbleStyles?.textStyle?.color;
const outgoingOverride = {
messageComposerStyles: {
containerStyle: {
backgroundColor: agentic
? theme.color.background3
: theme?.messageComposerStyles?.containerStyle?.backgroundColor,
},
},
messageListStyles: {
outgoingMessageBubbleStyles: {
containerStyle: {
backgroundColor: agentic
? theme.color.background4
: defaultOutgoingBg,
},
textBubbleStyles: {
textStyle: {
color: agentic
? theme.color.textPrimary
: defaultTextColor,
},
},
dateStyles: {
textStyle: {
color: agentic
? theme.color.textSecondary
: defaultTextColor,
},
},
},
},
};
return {
light: outgoingOverride,
dark: outgoingOverride,
mode: "auto" as "auto",
};
}, [agentic, theme]);
return (
{
if (fromMention || fromMessagePrivately) {
navigation.goBack();
} else {
navigation.popToTop();
}
}}
showBackButton={true}
usersStatusVisibility={userAndFriendsPresence}
hideVoiceCallButton={
(user && !oneOnOneVoiceCalling) || (group && !groupVoiceConference)
}
hideVideoCallButton={
(user && !oneOnOneVideoCalling) || (group && !groupVideoConference)
}
hideChatHistoryButton={false}
hideNewChatButton={false}
onChatHistoryButtonClick={handleChatHistoryClick}
onNewChatButtonClick={handleNewChatClick}
options={options}
/>
{
CometChatUIEventHandler.emitUIEvent?.(
CometChatUIEvents.hidePanel,
{
alignment: 'composerBottom',
child: () => null,
},
);
navigation.navigate('ThreadView', { message: messageObject, user, group });
}}
hideReplyInThreadOption={!threadConversationAndReplies}
hideEditMessageOption={!editMessage}
hideDeleteMessageOption={!deleteMessage}
receiptsVisibility={messageDeliveryAndReadReceipts}
hideTranslateMessageOption={!messageTranslation}
hideReactionOption={!reactions}
hideMessagePrivatelyOption={!sendPrivateMessageToGroupMembers}
aiAssistantTools={new CometChatAIAssistantTools({
getCurrentWeather: (args: any) => console.log('Weather args', args),
})}
streamingSpeed={10}
goToMessageId={messageId}
searchKeyword={searchKeyword}
navigatedFromSearch={navigatedFromSearch}
showMarkAsUnreadOption={true}
startFromUnreadMessages={true}
/>
{/* Chat History Drawer */}
{agentic && (
setShowHistoryModal(false)}>
setShowHistoryModal(false)}
onMessageClicked={handleHistoryMessageClick}
onError={handleChatHistoryError}
onNewChatButtonClick={handleNewChatClick}
/>
)}
{isNoLongerMember ? (
{t('GROUP_NO_LONGER_MEMBER')}
) : localUser?.getBlockedByMe() ? (
{t('BLOCKED_USER_DESC')}
unblock(localUser)}
style={[styles.button, { borderColor: theme.color.borderDefault }]}
>
{t('UNBLOCK')}
) : (
compactMessageComposer ? (
) : (
)
)}
);
};
const styles = StyleSheet.create({
flexOne: {
flex: 1,
},
blockedContainer: {
alignItems: 'center',
height: 90,
paddingVertical: 10,
},
button: {
flex: 1,
justifyContent: 'center',
borderWidth: 2,
width: '90%',
borderRadius: 8,
},
buttontext: {
paddingVertical: 5,
textAlign: 'center',
alignContent: 'center',
},
});
const drawerStyles = StyleSheet.create({
backdrop: {
justifyContent: 'flex-start',
alignItems: 'flex-end',
},
drawer: {
width: '100%',
height: '100%',
overflow: 'hidden',
},
});
export default Messages;
================================================
FILE: examples/SampleApp/src/components/conversations/screens/OngoingCallScreen.tsx
================================================
import React, { useMemo, useRef } from 'react';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import { navigate, navigationRef } from '../../../navigation/NavigationService';
import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
import { CometChatOngoingCall } from '@cometchat/chat-uikit-react-native';
import { SCREEN_CONSTANTS } from '../../../utils/AppConstants';
import type { RouteProp } from '@react-navigation/native';
import { CallType, RootStackParamList } from '../../../navigation/types';
type Props = {
navigation: any;
route: RouteProp;
};
const OngoingCallScreen = ({ navigation, route }: Props) => {
const params = route.params;
// 1) Normalize sessionId
const sessionID: string | undefined = useMemo(() => {
if ('sessionId' in params) return params.sessionId;
const c = (params as { call: any }).call;
return c?.sessionId ?? c?.getSessionId?.();
}, [params]);
// 2) Normalize/derive callType
const callType: CallType | undefined = useMemo(() => {
if ('callType' in params) return params.callType;
const c = (params as { call: any }).call;
if (!c) return undefined;
// Prefer public getter if available
if (typeof c?.getType === 'function') {
const t = c.getType();
if (t === CometChat.CALL_TYPE.AUDIO) return 'audio';
if (t === CometChat.CALL_TYPE.VIDEO) return 'video';
}
// Fallbacks for raw payloads
if (c?.callType === 'audio') return 'audio';
if (c?.data?.type === CometChat.CALL_TYPE.AUDIO) return 'audio';
if (c?.data?.type === CometChat.CALL_TYPE.VIDEO) return 'video';
return undefined;
}, [params]);
const isAudioOnly = callType === 'audio';
const callListener = useRef(
new CometChatCalls.OngoingCallListener({
onCallEnded: () => {
CometChat.clearActiveCall();
CometChatCalls.endSession();
navigate('BottomTabNavigator', undefined as any);
navigationRef.reset({
index: 0,
routes: [{ name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR }],
});
},
onCallEndButtonPressed: () => {
if (sessionID) CometChat.endCall(sessionID);
navigate('BottomTabNavigator', undefined as any);
navigationRef.reset({
index: 0,
routes: [{ name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR }],
});
},
}),
);
const callSettings = useMemo(() => {
return new CometChatCalls.CallSettingsBuilder()
.enableDefaultLayout(true)
.setCallEventListener(callListener.current)
.setIsAudioOnlyCall(!!isAudioOnly);
}, [isAudioOnly]);
if (!sessionID) {
// Belt & suspenders: try to recover from active call if param missing
const active: any = CometChat.getActiveCall?.();
const recovered =
typeof active?.getSessionId === 'function'
? active.getSessionId()
: undefined;
if (!recovered) return null; // or render a fallback/loading
}
return (
);
};
export default OngoingCallScreen;
================================================
FILE: examples/SampleApp/src/components/conversations/screens/SearchMessages.tsx
================================================
import React, { useCallback, useEffect, useRef } from 'react';
import {
View,
StyleSheet,
BackHandler,
Platform,
StatusBar,
} from 'react-native';
import {
CometChatSearch,
useTheme,
useCometChatTranslation,
} from '@cometchat/chat-uikit-react-native';
import { StackScreenProps } from '@react-navigation/stack';
import { RootStackParamList } from '../../../navigation/types';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { navigate } from '../../../navigation/NavigationService';
import { SCREEN_CONSTANTS } from '../../../utils/AppConstants';
type Props = StackScreenProps;
const SearchMessages: React.FC = ({ route, navigation }) => {
const { user, group } = route.params || {};
const theme = useTheme();
const { t } = useCometChatTranslation();
const insets = useSafeAreaInsets();
const navigationRef = useRef(navigation);
const routeRef = useRef(route);
// Update refs when navigation/route changes
useEffect(() => {
navigationRef.current = navigation;
routeRef.current = route;
}, [navigation, route]);
// Handle back button on Android
useEffect(() => {
const backAction = () => {
navigation.goBack();
return true;
};
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
backAction,
);
return () => backHandler.remove();
}, [navigation]);
const handleBack = useCallback(() => {
navigation.goBack();
}, [navigation]);
const handleConversationClicked = useCallback((conversation: CometChat.Conversation, searchKeyword?: string) => {
// Navigate to messages screen with the selected conversation
const conversationWith = conversation.getConversationWith();
if (conversationWith instanceof CometChat.User) {
navigate(SCREEN_CONSTANTS.MESSAGES, {
user: conversationWith,
searchKeyword,
});
} else if (conversationWith instanceof CometChat.Group) {
navigate(SCREEN_CONSTANTS.MESSAGES, {
group: conversationWith,
searchKeyword,
});
}
}, []);
const handleMessageClicked = useCallback(async (message: CometChat.BaseMessage, searchKeyword?: string) => {
// Navigate to messages screen and highlight the specific message
const messageReceiver = message.getReceiver();
const parentMessageId = message.getParentMessageId();
let targetUser: CometChat.User | undefined;
let targetGroup: CometChat.Group | undefined;
if (messageReceiver instanceof CometChat.User) {
// For user messages, determine if it's a direct message to/from the user
const sender = message.getSender();
const loggedInUser = await CometChat.getLoggedinUser();
if (sender.getUid() === loggedInUser?.getUid()) {
// Message sent by logged-in user, target is receiver
targetUser = messageReceiver;
} else {
// Message received by logged-in user, target is sender
targetUser = sender;
}
} else if (messageReceiver instanceof CometChat.Group) {
targetGroup = messageReceiver;
}
if (parentMessageId) {
try {
const parentMessage = await CometChat.getMessageDetails(parentMessageId);
if (parentMessage) {
navigate(SCREEN_CONSTANTS.THREAD_VIEW, {
message: parentMessage,
user: targetUser,
group: targetGroup,
highlightMessageId: String(message.getId()),
});
return;
}
} catch (e) {
console.error("Failed to fetch parent message", e);
}
}
if (targetUser) {
navigate(SCREEN_CONSTANTS.MESSAGES, {
user: targetUser,
messageId: String(message.getId()),
searchKeyword,
navigatedFromSearch: true,
});
} else if (targetGroup) {
navigate(SCREEN_CONSTANTS.MESSAGES, {
group: targetGroup,
messageId: String(message.getId()),
searchKeyword,
navigatedFromSearch: true,
});
}
}, []);
// Determine placeholder text
let searchPlaceholder = "Search";
if (user && user.getName()) {
searchPlaceholder = `Search in ${user.getName()}`;
} else if (group && group.getName()) {
searchPlaceholder = `Search in ${group.getName()}`;
}
return (
{Platform.OS === 'ios' && (
)}
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
export default SearchMessages;
================================================
FILE: examples/SampleApp/src/components/conversations/screens/ThreadView.tsx
================================================
import React, {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
BackHandler,
Platform,
} from 'react-native';
import {
RouteProp,
useRoute,
useNavigation,
useFocusEffect,
} from '@react-navigation/native';
import {
CometChatThreadHeader,
CometChatMessageList,
CometChatCompactMessageComposer,
CometChatMessageComposer,
useCometChatTranslation,
CometChatUIKit,
ChatConfigurator,
CometChatUIEventHandler,
CometChatUIEvents,
} from '@cometchat/chat-uikit-react-native';
import { Icon } from '@cometchat/chat-uikit-react-native';
import { useTheme } from '@cometchat/chat-uikit-react-native';
import { RootStackParamList } from '../../../navigation/types';
import ArrowBack from '../../../assets/icons/ArrowBack';
import { StackNavigationProp } from '@react-navigation/stack';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import { CommonUtils } from '../../../utils/CommonUtils';
import { useConfig } from '../../../config/store';
import { useGroupMemberStatus } from '../../../hooks/useGroupMemberStatus';
type ThreadViewRouteProp = RouteProp;
type ThreadViewNavProp = StackNavigationProp;
const ThreadView = () => {
const { params } = useRoute();
const navigation = useNavigation(); // <-- added navigation
const { goBack } = navigation;
const theme = useTheme();
const { message, user, group } = params || {};
const { t } = useCometChatTranslation();
const messageDeliveryAndReadReceipts = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts
);
const hideReactionOption = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.reactions
);
const photosSharing = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.photosSharing
);
const videoSharing = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.videoSharing
);
const audioSharing = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.audioSharing
);
const fileSharing = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.fileSharing
);
const mentions = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.mentions
);
const voiceNotes = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.voiceNotes
);
const stickers = useConfig(
(state) => state.settings.chatFeatures.deeperUserEngagement.stickers
);
const sendPrivateMessageToGroupMembers = useConfig(
(state) => state.settings.chatFeatures.privateMessagingWithinGroups.sendPrivateMessageToGroupMembers
);
const compactMessageComposer = useConfig(
(state) => state.settings.layout.compactMessageComposer
);
const loggedInUser = useRef(
CometChatUIKit.loggedInUser!,
).current;
const [localUser, setLocalUser] = useState(
params?.user,
);
// ============================================
// Group Kicked/Banned Detection (real-time)
// ============================================
const isNoLongerMember = useGroupMemberStatus(group);
// keep listener ids unique
const userListenerId = 'thread_user_' + new Date().getTime();
// Fetch latest user on mount (if user present)
useEffect(() => {
let mounted = true;
const init = async () => {
try {
if (params?.user) {
const uid = params.user.getUid();
const fresh = await CometChat.getUser(uid);
if (mounted) setLocalUser(CommonUtils.clone(fresh));
}
} catch (err) {
console.error('Error fetching user in ThreadView:', err);
}
};
init();
return () => {
mounted = false;
};
}, [params?.user]);
// add UI listeners for block/unblock to update localUser
useEffect(() => {
CometChatUIEventHandler.addUserListener(userListenerId, {
ccUserBlocked: (payload: { user: CometChat.User }) => {
setLocalUser(CommonUtils.clone(payload.user));
},
ccUserUnBlocked: (payload: { user: CometChat.User }) => {
setLocalUser(CommonUtils.clone(payload.user));
},
});
return () => {
CometChatUIEventHandler.removeUserListener(userListenerId);
};
}, []);
useEffect(() => {
CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {
alignment: 'composerBottom',
child: () => null,
panelId: 'sticker',
});
}, []);
// B) Ensure back also asks to close (already using goBack())
const handleBack = useCallback(() => {
CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {
alignment: 'composerBottom',
child: () => null,
panelId: 'sticker',
});
navigation.goBack();
return true;
}, [navigation]);
useFocusEffect(
useCallback(() => {
// Android hardware back -> just pop
const sub = BackHandler.addEventListener('hardwareBackPress', handleBack);
// iOS gesture/back button -> let default POP happen, but run side-effects
const unsub = navigation.addListener('beforeRemove', e => {
const type = e?.data?.action?.type;
if (type === 'GO_BACK' || type === 'POP') {
// IMPORTANT: do NOT e.preventDefault()
CometChatUIEventHandler.emitUIEvent?.(CometChatUIEvents.hidePanel, {
alignment: 'composerBottom',
child: () => null,
panelId: 'sticker',
});
}
});
return () => {
sub.remove();
unsub();
};
}, [navigation, handleBack]),
);
// Header back
… ;
// Keep gesture enabled
useLayoutEffect(() => {
navigation.setOptions?.({ gestureEnabled: true } as any);
}, [navigation]);
const unblock = async (userToUnblock: CometChat.User) => {
try {
const uid = userToUnblock.getUid();
const response = await CometChat.unblockUsers([uid]);
if (response) {
const fresh = await CometChat.getUser(uid);
setLocalUser(CommonUtils.clone(fresh));
CometChatUIEventHandler.emitUserEvent(
CometChatUIEvents.ccUserUnBlocked,
{
user: fresh,
},
);
}
} catch (error) {
console.error('Error unblocking user from ThreadView:', error);
}
};
const getMentionsTap = useCallback(() => {
const mentionsFormatter =
ChatConfigurator.getDataSource().getMentionsFormatter(
loggedInUser,
theme,
);
if (user) mentionsFormatter.setUser(user);
if (group) mentionsFormatter.setGroup(group);
mentionsFormatter.setOnMentionClick(
(_message: CometChat.BaseMessage, uid: string) => {
if (uid !== loggedInUser.getUid()) {
CometChat.getUser(uid)
.then((mentionedUser: CometChat.User) => {
navigation.push('Messages', {
user: mentionedUser,
fromMention: true,
});
})
.catch((error: any) => {
console.error('Error fetching mentioned user:', error);
});
}
},
);
return mentionsFormatter;
}, [user, group, loggedInUser, navigation, theme]);
const threadHeaderMentionsFormatter = useMemo(
() => getMentionsTap(),
[getMentionsTap],
);
return (
{/* Custom Header */}
}
/>
{t('THREAD')}
{user ? user?.getName() : group?.getName()}
{/* Thread Header */}
{/* Threaded Message List */}
{/* Message Composer for Thread */}
{isNoLongerMember ? (
{t('GROUP_NO_LONGER_MEMBER')}
) : localUser?.getBlockedByMe() ? (
{t('BLOCKED_USER_DESC')}
unblock(localUser)}
style={[styles.button, { borderColor: theme.color.borderDefault }]}
>
{t('UNBLOCK')}
) : (
compactMessageComposer ? (
console.error('Composer Error:', error)}
keyboardAvoidingViewProps={
Platform.OS === 'android' ? {} : { behavior: 'padding' }
}
hideImageAttachmentOption={!photosSharing}
hideVideoAttachmentOption={!videoSharing}
hideAudioAttachmentOption={!audioSharing}
hideFileAttachmentOption={!fileSharing}
hideCameraOption={!photosSharing}
disableMentions={!mentions}
hideVoiceRecordingButton={!voiceNotes}
hideStickersButton={!stickers}
/>
) : (
console.error('Composer Error:', error)}
keyboardAvoidingViewProps={
Platform.OS === 'android' ? {} : { behavior: 'padding' }
}
hideImageAttachmentOption={!photosSharing}
hideVideoAttachmentOption={!videoSharing}
hideAudioAttachmentOption={!audioSharing}
hideFileAttachmentOption={!fileSharing}
hideCameraOption={!photosSharing}
disableMentions={!mentions}
hideVoiceRecordingButton={!voiceNotes}
hideStickersButton={!stickers}
/>
)
)}
);
};
const styles = StyleSheet.create({
headerStyle: {
paddingVertical: 10,
paddingLeft: 10,
flexDirection: 'row',
},
iconStyle: {
flexDirection: 'row',
alignItems: 'center',
},
textStyle: {
paddingLeft: 10,
alignItems: 'flex-start',
},
blockedContainer: {
alignItems: 'center',
height: 90,
paddingVertical: 10,
},
button: {
flex: 1,
justifyContent: 'center',
borderWidth: 2,
width: '90%',
borderRadius: 8,
},
buttontext: {
paddingVertical: 5,
textAlign: 'center',
alignContent: 'center',
},
});
export default ThreadView;
================================================
FILE: examples/SampleApp/src/components/conversations/screens/TransferOwnership.tsx
================================================
import React, {useState} from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
import {RouteProp} from '@react-navigation/native';
import {
CometChatGroupMembers,
CometChatUIEventHandler,
CometChatUIEvents,
useCometChatTranslation,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import {Icon} from '@cometchat/chat-uikit-react-native';
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {StackNavigationProp} from '@react-navigation/stack';
import {RootStackParamList} from '../../../navigation/types';
import {styles} from './TransferOwnershipStyles';
import ArrowBack from '../../../assets/icons/ArrowBack';
import {CommonUtils} from '../../../utils/CommonUtils';
type TransferOwnershipScreenProps = {
route: RouteProp;
navigation: StackNavigationProp<
RootStackParamList,
'TransferOwnershipSection'
>;
};
const TransferOwnership: React.FC = ({
route,
navigation,
}) => {
const {group} = route.params;
const theme = useTheme();
const {t}= useCometChatTranslation()
const [selectedOwnershipMember, setSelectedOwnershipMember] =
useState(null);
const handleBack = () => {
navigation.goBack();
};
// Function to leave the group after ownership transfer
const leaveGroup = (group: CometChat.Group) => {
CometChat.leaveGroup(group.getGuid())
.then(() => {
CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupLeft, {
leftGroup: CommonUtils.clone(group),
});
navigation.pop(3);
})
.catch(error => {
console.log('Group leaving failed with exception:', error);
});
};
const handleTransferOwnership = async () => {
if (!selectedOwnershipMember || !group) return;
try {
await CometChat.transferGroupOwnership(
group.getGuid(),
selectedOwnershipMember.getUid(),
);
leaveGroup(group);
} catch (error) {
console.error('Ownership transfer failed:', error);
}
};
return (
{/* Header */}
}
/>
{t('TRANSFER_OWNERSHIP')}
{/* Group Members List */}
{group && (
{
setSelectedOwnershipMember(
members && members.length > 0 ? members[0] : null,
);
}}
/>
)}
{/* Transfer Ownership Button */}
{t('TRANSFER_OWNERSHIP')}
);
};
export default TransferOwnership;
================================================
FILE: examples/SampleApp/src/components/conversations/screens/TransferOwnershipStyles.tsx
================================================
import {StyleSheet} from 'react-native';
export const styles = StyleSheet.create({
headerSection: {
paddingTop: 15,
paddingLeft: 10,
flexDirection: 'row',
},
backButtonContainer: {
flexDirection: 'row',
alignItems: 'center',
},
leftPaddingSmall: {
paddingLeft: 5,
},
transferButtonWrapper: {
alignContent: 'center',
justifyContent: 'center',
paddingVertical: 1,
height: '7%',
width: '100%',
alignSelf: 'center',
},
transferButtonContent: {
marginHorizontal: 20,
alignSelf: 'center',
justifyContent: 'center',
borderRadius: 6,
height: '75%',
width: '95%',
},
centerAligned: {
alignSelf: 'center',
},
});
================================================
FILE: examples/SampleApp/src/components/conversations/screens/UserInfo.tsx
================================================
import React, {useState, useEffect, useRef, FC} from 'react';
import {View, Text, TouchableOpacity, BackHandler} from 'react-native';
import {
CometChatAvatar,
useTheme,
CometChatUIEventHandler,
CallUIEvents,
CometChatConversationEvents,
CometChatConfirmDialog,
CometChatOutgoingCall,
useCometChatTranslation,
getLastSeenTime,
} from '@cometchat/chat-uikit-react-native';
import {Icon} from '@cometchat/chat-uikit-react-native';
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {permissionUtil} from '@cometchat/chat-uikit-react-native/src/shared/utils/PermissionUtil';
import {CallTypeConstants, UserStatusConstants} from '@cometchat/chat-uikit-react-native/src/shared/constants/UIKitConstants';
import { blockUser, unblock } from '../../../utils/helper';
import {styles} from './UserInfoStyles';
import ArrowBack from '../../../assets/icons/ArrowBack';
import Block from '../../../assets/icons/Block';
import Delete from '../../../assets/icons/Delete';
import Videocam from '../../../assets/icons/VideoCam';
import Call from '../../../assets/icons/Call';
import {StackNavigationProp, StackScreenProps} from '@react-navigation/stack';
import {
RootStackParamList,
} from '../../../navigation/types';
import {useFocusEffect} from '@react-navigation/native';
import { useConfig } from '../../../config/store';
type ScreenProps = StackScreenProps;
type NavigationProps = StackNavigationProp;
type Props = ScreenProps & {navigation: NavigationProps};
const UserInfo: FC = ({route, navigation}) => {
const {user} = route.params;
const theme = useTheme();
const {t}= useCometChatTranslation()
const [userObj, setUserObj] = useState(user);
/** STATES **/
const [disableButton, setDisableButton] = useState(false);
const [blocked, setBlocked] = useState(false);
// separate modal states
const [isBlockModalOpen, setBlockModalOpen] = useState(false);
const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);
const [userStatus, setUserStatus] = useState(userObj.getStatus());
const listenerId = useRef('CallListener_' + Date.now());
const userStatusListenerId = 'user_status_' + new Date().getTime();
const [callObj, setCallObj] = useState();
const callType = useRef(undefined);
const oneOnOneVoiceCalling = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling
);
const oneOnOneVideoCalling = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling
);
useEffect(() => {
setUserStatus(userObj.getStatus());
setBlocked(userObj.getBlockedByMe());
CometChat.addCallListener(
listenerId.current,
new CometChat.CallListener({
onIncomingCallReceived: () => {
setDisableButton(true);
},
onOutgoingCallAccepted: () => {
console.log('call accepted');
},
onOutgoingCallRejected: () => {
setDisableButton(false);
setCallObj(undefined);
},
onIncomingCallCancelled: () => {
setDisableButton(false);
},
}),
);
CometChatUIEventHandler.addCallListener(listenerId.current, {
ccCallRejected: () => {
setDisableButton(false);
setCallObj(undefined);
},
ccCallEnded: () => {
setDisableButton(false);
setCallObj(undefined);
},
});
CometChat.addUserListener(
userStatusListenerId,
new CometChat.UserListener({
onUserOnline: (onlineUser: CometChat.User) => {
if (onlineUser.getUid() === userObj.getUid()) {
setUserObj(onlineUser);
setUserStatus(onlineUser.getStatus());
}
},
onUserOffline: (offlineUser: CometChat.User) => {
if (offlineUser.getUid() === userObj.getUid()) {
setUserObj(offlineUser);
setUserStatus(offlineUser.getStatus());
}
},
}),
);
return () => {
CometChat.removeCallListener(listenerId.current);
CometChatUIEventHandler.removeCallListener(listenerId.current);
CometChat.removeUserListener(userStatusListenerId);
};
}, [userObj]);
const translations = {
lastSeen: 'Last seen',
minutesAgo: (minutes: number) =>
`${minutes} minute${minutes === 1 ? '' : 's'} ago`,
hoursAgo: (hours: number) => `${hours} hour${hours === 1 ? '' : 's'} ago`,
};
const makeVoiceCall = async (): Promise => {
if (disableButton) return;
if (!(await permissionUtil.startResourceBasedTask(['mic']))) return;
callType.current = CallTypeConstants.audio;
makeCall(CallTypeConstants.audio);
};
const makeVideoCall = async (): Promise => {
if (disableButton) return;
if (!(await permissionUtil.startResourceBasedTask(['mic', 'camera'])))
return;
callType.current = CallTypeConstants.video;
makeCall(CallTypeConstants.video);
};
const makeCall = (type: string): void => {
if (type === CallTypeConstants.audio || type === CallTypeConstants.video) {
const receiverID = userObj.getUid();
const callTypeValue = type;
const receiverType = CometChat.RECEIVER_TYPE.USER;
if (!receiverID || !receiverType) return;
const call = new CometChat.Call(
receiverID,
callTypeValue,
receiverType,
CometChat.CATEGORY_CALL,
);
CometChat.initiateCall(call).then(
initiatedCall => {
setCallObj(initiatedCall);
setDisableButton(true);
CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccOutgoingCall, {
call: initiatedCall,
});
},
error => {
console.log('Call initialization failed with exception:', error);
CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallFailed, {
call,
});
},
);
}
};
/** BLOCK/UNBLOCK LOGIC **/
const handleBlockUnblockConfirm = () => {
setBlockModalOpen(false); // close the dialog
if (blocked) {
// user is already blocked by me -> now unblocking
unblock(userObj.getUid(), userObj, setBlocked, setUserObj);
} else {
// user is not blocked -> blocking user
blockUser(userObj.getUid(), userObj, setBlocked);
}
};
/** DELETE CONVERSATION LOGIC **/
const handleDeleteConversationConfirm = () => {
setDeleteModalOpen(false); // close the dialog
if (userObj) {
CometChat.getConversation(userObj.getUid(), 'user')
.then(conversation => {
CometChat.deleteConversation(userObj.getUid(), 'user')
.then(deletedConversation => {
console.log(deletedConversation);
CometChatUIEventHandler.emitConversationEvent(
CometChatConversationEvents.ccConversationDeleted,
{conversation: conversation},
);
navigation.pop(2);
})
.catch(error => {
console.log('Error while deleting conversation:', error);
});
})
.catch(error => {
console.log('Error while deleting conversation:', error);
});
}
};
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
// Navigate back to message screen (same as your onPress handler)
navigation.goBack();
return true; // Prevent default back behavior
};
const subscription = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
return () => subscription.remove();
}, [navigation]),
);
return (
{/* Header */}
navigation.goBack()}>
}
/>
{t('USER_INFO')}
{/* User Information Section */}
{userObj?.getName()}
{userObj &&
!(userObj.getBlockedByMe() || userObj.getHasBlockedMe()) &&
(userStatus === UserStatusConstants.online
? t('ONLINE')
: getLastSeenTime(userObj.getLastActiveAt()))}
{/* Action Boxes */}
{(oneOnOneVoiceCalling || oneOnOneVideoCalling) && (
{oneOnOneVoiceCalling && (
}
/>
{t('AUDIO_CALL')}
)}
{oneOnOneVideoCalling && (
}
/>
{t('VIDEO_CALL')}
)}
)}
{/* Block/Unblock and Delete Chat Buttons */}
setBlockModalOpen(true)}
style={styles.backButtonContainer}>
}
/>
{blocked ? t('UNBLOCK') : t('BLOCK')}
{/* DELETE CHAT */}
setDeleteModalOpen(true)}
style={styles.backButtonContainer}>
}
/>
{t('DELETE_CHAT_TEXT')}
{/* =============== BLOCK/UNBLOCK MODAL =============== */}
setBlockModalOpen(false)}
onConfirm={handleBlockUnblockConfirm}
onDismiss={() => console.log('Block/Unblock Modal dismissed')}
titleText={
blocked ? t('UNBLOCK_CONTACT') : t('BLOCK_USER')
}
messageText={
blocked ? t('UNBLOCK_SURE') : t('BLOCK_SURE')
}
cancelButtonText={t('CANCEL')}
confirmButtonText={blocked ? t('UNBLOCK') : t('BLOCK')}
icon={ }
/>
{/* =============== DELETE CHAT MODAL =============== */}
setDeleteModalOpen(false)}
onConfirm={handleDeleteConversationConfirm}
onDismiss={() => console.log('Delete Modal dismissed')}
titleText={t('DELETE_CHAT')}
messageText={t('SURE_TO_DELETE_CHAT')}
cancelButtonText={t('CANCEL')}
confirmButtonText={t('DELETE')}
icon={ }
/>
{callObj && (
{
CometChat.rejectCall(
call?.getSessionId(),
CometChat.CALL_STATUS.CANCELLED,
).then(
rejectedCall => {
console.log('🚀 ~ rejectedCall:', rejectedCall);
CometChatUIEventHandler.emitCallEvent(
CallUIEvents.ccCallRejected,
{
call: rejectedCall,
},
);
setCallObj(undefined);
},
err => {
console.log('🚀 ~ err:', err);
setCallObj(undefined);
// onError && onError(err);
},
);
}}
/>
)}
);
};
export default UserInfo;
================================================
FILE: examples/SampleApp/src/components/conversations/screens/UserInfoStyles.tsx
================================================
import { StyleSheet } from "react-native";
export const styles = StyleSheet.create({
headerContainer: {
paddingTop: 15,
paddingLeft: 10,
flexDirection: 'row',
},
backButtonContainer: {
flexDirection: 'row',
alignItems: 'center',
},
smallPaddingLeft: {
paddingLeft: 5,
},
profileCard: {
marginTop: 12,
borderTopWidth: 1,
borderBottomWidth: 1,
paddingVertical: 10,
},
profileInfo: {
alignSelf: 'center',
alignItems: 'center',
marginTop: 20,
},
avatarContainer: {
height: 120,
width: 120,
},
avatarText: {
fontSize: 28,
lineHeight: 55,
},
avatarImage: {
height: '100%',
width: '100%',
},
mt10Centered: {
marginTop: 10,
alignSelf: 'center',
},
mt5Centered: {
marginTop: 5,
textAlign: 'center',
alignSelf: 'center',
},
actionsRow: {
flexDirection: 'row',
paddingHorizontal: 20,
justifyContent: 'space-between',
marginVertical: 20,
},
callActionButton: {
flex: 1,
paddingVertical: 10,
borderWidth: 1,
borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
marginHorizontal: 5,
},
optionsContainer: {
paddingTop: 10,
gap: 4,
},
optionRow: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
paddingLeft: 20,
width: '100%',
},
ml5: {
marginLeft: 5,
},
});
================================================
FILE: examples/SampleApp/src/components/conversations/screens/ViewMembers.tsx
================================================
import React from 'react';
import { BackHandler, View } from 'react-native';
import {
CometChatGroupMembers,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import {
useRoute,
useNavigation,
RouteProp,
useFocusEffect,
} from '@react-navigation/native';
import { RootStackParamList } from '../../../navigation/types';
import { useConfig } from '../../../config/store';
const ViewMembers: React.FC = () => {
const route = useRoute>();
const navigation = useNavigation();
const { group } = route.params;
const theme = useTheme();
const kickUsers = useConfig(
(state) => state.settings.chatFeatures.moderatorControls.kickUsers
);
const banUsers = useConfig(
(state) => state.settings.chatFeatures.moderatorControls.banUsers
);
const promoteDemoteMembers = useConfig(
(state) => state.settings.chatFeatures.moderatorControls.promoteDemoteMembers
);
const userAndFriendsPresence = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence
);
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
// Navigate back to message screen (same as your onPress handler)
navigation.goBack();
return true; // Prevent default back behavior
};
const subscription = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
return () => subscription.remove();
}, [navigation]),
);
return (
{
navigation.goBack();
}}
selectionMode="none"
showBackButton={true}
hideKickMemberOption={!kickUsers}
hideBanMemberOption={!banUsers}
hideScopeChangeOption={!promoteDemoteMembers}
usersStatusVisibility={userAndFriendsPresence}
/>
);
};
export default ViewMembers;
================================================
FILE: examples/SampleApp/src/components/conversations/screens/qr_screen.tsx
================================================
import React, { useEffect, useState, useRef } from 'react';
import {
View,
Text,
StyleSheet,
Alert,
TouchableOpacity,
Image,
} from 'react-native';
import { Camera, useCameraDevice, useCodeScanner } from 'react-native-vision-camera';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RootStackParamList } from '../../../navigation/types';
import { Animated, Easing } from 'react-native';
import Flash from '../../../assets/icons/Flash';
import sync from '../../../assets/icons/cometchat_sync_img.png';
interface ConfigData {
data: {
builderId: string;
settings: any;
name: string;
type: string;
createdAt: number;
updatedAt: number;
expiresAt: number;
};
}
const CONFIG_STORAGE_KEY = '@app_config';
const MINIMUM_LOADING_TIME = 5000;
const QRScreen: React.FC = () => {
const device = useCameraDevice('back');
const [hasPermission, setHasPermission] = useState(false);
const [scannedCode, setScannedCode] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [lineAnim] = useState(new Animated.Value(0));
const [isFlashOn, setIsFlashOn] = useState(false);
const [loadingProgress] = useState(new Animated.Value(0));
const apiResponseReceived = useRef(false);
const loadingStartTime = useRef(0);
type ChatNavigationProp = StackNavigationProp<
RootStackParamList,
'Conversation'
>;
const navigation = useNavigation();
const codeScanner = useCodeScanner({
codeTypes: ['qr', 'ean-13'],
onCodeScanned: (codes) => {
if (codes.length > 0 && !isLoading && !scannedCode) {
const qrData = codes[0].value ?? '';
setScannedCode(qrData);
fetchDataFromAPI(qrData);
}
},
});
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(lineAnim, {
toValue: 1,
duration: 2000,
easing: Easing.linear,
useNativeDriver: true,
}),
Animated.timing(lineAnim, {
toValue: 0,
duration: 2000,
easing: Easing.linear,
useNativeDriver: true,
}),
])
).start();
}, [lineAnim]);
const toggleFlash = () => {
setIsFlashOn(prev => !prev);
};
useEffect(() => {
const requestPermissions = async () => {
let cameraStatus = await Camera.getCameraPermissionStatus();
if (cameraStatus !== 'granted') {
cameraStatus = await Camera.requestCameraPermission();
}
if (cameraStatus === 'granted') {
setHasPermission(true);
} else {
Alert.alert(
'Camera permission denied',
'Please enable camera permission in your settings to scan QR codes'
);
}
};
requestPermissions();
}, []);
const saveConfigToStorage = async (configData: ConfigData) => {
try {
// Save to AsyncStorage
await AsyncStorage.setItem(CONFIG_STORAGE_KEY, JSON.stringify(configData));
try {
const { useConfigStore } = await import('../../../config/store');
// Use the data property which contains the actual config structure
useConfigStore.setState({ config: configData.data });
await AsyncStorage.setItem('@config_updated', 'true');
} catch (importError) {
console.log('Config store not available or failed to update:', importError);
}
return true;
} catch (error) {
console.error('Error saving config to AsyncStorage:', error);
throw error;
}
};
const finishLoading = () => {
setIsLoading(false);
navigation.goBack();
};
const fetchDataFromAPI = async (qrCodeData: string) => {
try {
setIsLoading(true);
apiResponseReceived.current = false;
loadingStartTime.current = Date.now();
// Start progress bar animation (5 seconds to match minimum loading time)
loadingProgress.setValue(0);
Animated.timing(loadingProgress, {
toValue: 1,
duration: MINIMUM_LOADING_TIME,
easing: Easing.ease,
useNativeDriver: false,
}).start();
// QR code contains the builder ID, construct the API URL
const builderId = qrCodeData.trim();
const apiUrl = `https://apivcb.cometchat.io/v1/builders/${builderId}`;
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`API request failed with status ${response.status}`);
}
const data: ConfigData = await response.json();
await saveConfigToStorage(data);
apiResponseReceived.current = true;
const elapsedTime = Date.now() - loadingStartTime.current;
const remainingTime = Math.max(0, MINIMUM_LOADING_TIME - elapsedTime);
setTimeout(() => {
finishLoading();
}, remainingTime);
return data;
} catch (error) {
console.error('Error fetching data from API:', error);
Alert.alert(
'Error',
`Failed to update configuration: ${error instanceof Error ? error.message : 'Unknown error'}`,
[
{
text: 'OK',
onPress: () => {
setIsLoading(false);
setScannedCode(null);
apiResponseReceived.current = false;
}
}
]
);
}
};
if (!device) {
return (
Loading camera...
);
}
if (!hasPermission) {
return (
No camera permission
);
}
return (
{/* Top Overlay */}
{/* Header */}
navigation.goBack()}
>
✕
{/* Title Section */}
Scan to Preview Your Chat UI
Instantly load and test your configuration{'\n'}from the Visual builder.
{/* Simple full overlay approach */}
{/* Bottom Overlay */}
{/* Instructions Section */}
How to Use:
1.
Go to the Visual builder & generate QR code.
2.
Point your camera at the QR code to scan.
3.
Preview your design live on this device instantly.
{/* Note Section */}
Note:
Make sure to save changes on the builder before scanning again to view updates.
{/* Camera Container Border */}
{/* Loading Overlay */}
{isLoading && (
Syncing with Visual Builder...
)}
{/* Scanned Code Display */}
{scannedCode && !isLoading && (
Scanned:
{scannedCode}
)}
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'black' ,
},
center: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'black'
},
centerText: {
color: 'white',
fontSize: 16,
},
topOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 180,
zIndex: 3,
},
overlayTop: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 180,
backgroundColor: 'rgba(0,0,0,0.9)',
zIndex: 1,
},
overlayLeft: {
position: 'absolute',
top: 180,
left: 0,
width: 20,
height: 370,
backgroundColor: 'rgba(0,0,0,0.9)',
zIndex: 1,
},
overlayRight: {
position: 'absolute',
top: 180,
right: 0,
width: 20,
height: 370,
backgroundColor: 'rgba(0,0,0,0.9)',
zIndex: 1,
},
overlayBottom: {
position: 'absolute',
top: 550,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.9)',
zIndex: 1,
},
animatedLine: {
position: 'absolute',
left: 0,
right: 0,
height: 3,
backgroundColor: '#6366f1',
borderRadius: 2,
},
bottomOverlay: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
top: 560,
backgroundColor: 'rgba(0,0,0,0.9)',
zIndex: 3,
},
cameraBorder: {
position: 'absolute',
top: 180,
left: 20,
right: 20,
height: 370,
zIndex: 4,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 20,
paddingTop: 20,
paddingBottom: 20,
},
closeButton: {
width: 40,
height: 40,
justifyContent: 'center',
alignItems: 'center',
},
closeIcon: {
color: '#FFFFFF',
fontSize: 24,
fontWeight: 'bold',
},
flashButton: {
width: 40,
height: 40,
justifyContent: 'center',
alignItems: 'center',
},
flashIcon: {
fontSize: 24,
},
titleSection: {
alignItems: 'center',
paddingHorizontal: 20,
marginBottom: 30,
},
title: {
color: '#FFFFFF',
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 12,
},
subtitle: {
color: '#CCCCCC',
fontSize: 16,
textAlign: 'center',
lineHeight: 22,
},
instructionsSection: {
flex: 1,
paddingHorizontal: 20,
paddingBottom: 40,
},
instructionsTitle: {
color: '#FFFFFF',
fontSize: 18,
fontWeight: 'bold',
marginBottom: 16,
},
stepContainer: {
flexDirection: 'row',
alignItems: 'flex-start',
marginBottom: 12,
},
stepNumber: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
marginRight: 8,
minWidth: 20,
},
stepText: {
color: '#CCCCCC',
fontSize: 16,
lineHeight: 24,
flex: 1,
},
noteSection: {
backgroundColor: '#0B7BEA33',
borderRadius: 8,
padding: 16,
marginTop: 20,
borderLeftWidth: 4,
},
noteTitle: {
color: '#CCCCCC',
fontSize: 16,
fontWeight: 'bold',
marginBottom: 8,
},
noteText: {
color: '#CCCCCC',
fontSize: 14,
lineHeight: 20,
},
resultBox: {
position: 'absolute',
bottom: 130,
alignSelf: 'center',
padding: 12,
backgroundColor: 'rgba(0,0,0,0.8)',
borderRadius: 8,
maxWidth: '80%',
borderWidth: 1,
borderColor: '#6366f1',
zIndex: 2,
},
resultLabel: {
color: '#6366f1',
fontSize: 12,
fontWeight: 'bold',
marginBottom: 4,
},
resultText: {
color: 'white',
fontSize: 14
},
loadingOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.9)',
justifyContent: 'center',
alignItems: 'center',
zIndex: 999,
},
loadingContent: {
alignItems: 'center',
justifyContent: 'center',
width: '100%',
},
syncImage: {
width: 300,
height: 455,
marginBottom: 24,
},
loadingText: {
color: '#000000',
fontSize: 18,
fontWeight: '600',
marginBottom: 24,
},
progressBarContainer: {
width: 280,
height: 8,
backgroundColor: '#E5E7EB',
borderRadius: 4,
overflow: 'hidden',
},
progressBarFill: {
height: '100%',
backgroundColor: '#6366f1',
borderRadius: 4,
},
});
export default QRScreen;
================================================
FILE: examples/SampleApp/src/components/groups/GroupHelper.tsx
================================================
import React, {useEffect, useState} from 'react';
import {
Dimensions,
View,
Text,
TouchableOpacity,
TextInput,
TouchableWithoutFeedback,
} from 'react-native';
import {
CometChatAvatar,
CometChatBottomSheet,
CometChatUIKitHelper,
Icon,
useCometChatTranslation,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import {styles} from './styles';
import {CometChat} from '@cometchat/chat-sdk-react-native';
import GroupAdd from '../../assets/icons/GroupAdd';
import Group from '../../assets/icons/Group';
/**
* Subcomponent for the default AppBar Options
* (e.g., the "Add Group" button/icon on the header).
*/
export const GroupScreenAppBarOptions: React.FC<{
onPress: () => void;
}> = ({onPress}) => {
const theme = useTheme();
const {t}= useCometChatTranslation()
return (
}
size={28}
/>
);
};
/**
* Bottom sheet for creating a new group.
*/
interface CreateGroupBottomSheetProps {
visible: boolean;
onClose: () => void;
onGroupCreated: (group: CometChat.Group) => void;
}
export const CreateGroupBottomSheet: React.FC = ({
visible,
onClose,
onGroupCreated,
}) => {
const theme = useTheme();
const { t } = useCometChatTranslation()
// Group state
const [groupName, setGroupName] = useState('');
const [groupPassword, setGroupPassword] = useState('');
const [selectedOption, setSelectedOption] = useState('Public');
const [showPasswordField, setShowPasswordField] = useState(false);
const [showError, setShowError] = useState('');
const groupTypes = [
{ display: t('PUBLIC'), value: 'Public' },
{ display: t('PRIVATE'), value: 'Private' },
{ display: t('PASSWORD'), value: 'Password' }
];
const resetFields = () => {
setGroupName('');
setGroupPassword('');
setSelectedOption('Public');
setShowPasswordField(false);
setShowError('');
};
const handleDismiss = () => {
resetFields(); // clear everything the user typed/selected
onClose(); // let the parent know the sheet closed
};
useEffect(() => {
if (!showError) return;
const timer = setTimeout(() => setShowError(''), 3000);
return () => clearTimeout(timer);
}, [showError]);
const handleOptionPress = (option: { display: string, value: string }) => {
setSelectedOption(option.value);
setShowPasswordField(option.value === 'Password');
};
const handleCreateGroup = async () => {
if (!groupName.trim()) {
setShowError('Group name cannot be empty');
return;
}
const GUID = 'group_' + Date.now();
let groupType = CometChat.GROUP_TYPE.PUBLIC;
let password = '';
switch (selectedOption) {
case 'Private':
groupType = CometChat.GROUP_TYPE.PRIVATE;
break;
case 'Password':
groupType = CometChat.GROUP_TYPE.PASSWORD;
break;
default:
groupType = CometChat.GROUP_TYPE.PUBLIC;
}
if (groupType === CometChat.GROUP_TYPE.PASSWORD) {
if (!groupPassword.trim()) {
setShowError('Password is mandatory for password-protected groups');
return;
}
password = groupPassword;
}
const newGroup = new CometChat.Group(GUID, groupName, groupType, password);
try {
const createdGroup = await CometChat.createGroup(newGroup);
CometChatUIKitHelper.onGroupCreated(createdGroup);
onGroupCreated(createdGroup);
// Reset fields after creation
resetFields();
onClose();
} catch (error) {
console.log('Group creation failed with exception:', error);
}
};
return (
{/* Header / Icon */}
}
size={44}
/>
{t('NEW__GROUP')}
{/* Group Type Tabs */}
{t('TYPE')}
{groupTypes.map(option => {
const isSelected = selectedOption === option.value;
return (
handleOptionPress(option)}
style={{
flex: 1,
alignItems: 'center',
paddingVertical: 8,
borderRadius: 12,
backgroundColor: isSelected
? theme.color.background1
: 'transparent',
}}>
{option.display}
);
})}
{/* Group Name Input */}
{t('NAME')}
{/* Group Password (if needed) */}
{showPasswordField && (
{t('PASSWORD')}
)}
{showError !== '' && (
{showError}
)}
{/* Create Group Button */}
{t('CREATE_GROUP')}
);
};
/**
* Bottom sheet for joining a password-protected group.
*/
interface JoinGroupBottomSheetProps {
visible: boolean;
groupToJoin: CometChat.Group | null;
onClose: () => void;
onJoinSuccess: (joinedGroup: CometChat.Group) => void;
}
export const JoinGroupBottomSheet: React.FC = ({
visible,
groupToJoin,
onClose,
onJoinSuccess,
}) => {
const theme = useTheme();
const { t } = useCometChatTranslation()
const [enteredPassword, setEnteredPassword] = useState('');
const [isPasswordErrorVisible, setIsPasswordErrorVisible] = useState(false);
useEffect(() => {
if (!isPasswordErrorVisible) return;
const timer = setTimeout(() => setIsPasswordErrorVisible(false), 3000);
return () => clearTimeout(timer);
}, [isPasswordErrorVisible]);
const joinPasswordGroup = async () => {
if (!groupToJoin || !enteredPassword.trim()) return;
try {
const joinedGroup = await CometChat.joinGroup(
groupToJoin.getGuid(),
groupToJoin.getType() as CometChat.GroupType,
enteredPassword,
);
onJoinSuccess(joinedGroup);
handleClose();
} catch (error) {
setIsPasswordErrorVisible(true);
console.log('Error joining password group:', error);
}
};
const handleClose = () => {
setEnteredPassword('');
setIsPasswordErrorVisible(false);
onClose();
};
return (
{groupToJoin && (
{/* Header */}
{t('JOIN_GROUP')}
{/* Group Info */}
{groupToJoin.getName()}
{groupToJoin.getMembersCount()} {t('MEMBERS')}
{/* Password Input */}
{t('ENTER_PASSWORD')}
{/* Incorrect Password Toast */}
{isPasswordErrorVisible && (
{t('PASSWORD_INCORRECT_GROUP')}
)}
{/* Join Button */}
{t('JOIN_GROUP')}
)}
);
};
================================================
FILE: examples/SampleApp/src/components/groups/Groups.tsx
================================================
import React, { useCallback, useEffect, useState } from 'react';
import { View } from 'react-native';
import { useFocusEffect, useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import {
CometChatGroups,
CometChatUIEventHandler,
CometChatUIEvents,
CometChatUIKit,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import { RootStackParamList } from '../../navigation/types';
import { styles } from './styles';
import {
GroupScreenAppBarOptions,
CreateGroupBottomSheet,
JoinGroupBottomSheet,
} from './GroupHelper';
import {SCREEN_CONSTANTS} from '../../utils/AppConstants';
import { useConfig } from '../../config/store';
type GroupNavigationProp = StackNavigationProp;
interface GroupsProps {
hideHeader?: boolean;
}
const Groups: React.FC = ({hideHeader = false}) => {
const createGroup = useConfig(
(state) => state.settings.chatFeatures.groupManagement.createGroup
);
const theme = useTheme();
const navigation = useNavigation();
const [pendingChat, setPendingChat] = useState(null);
// State to handle showing/hiding bottom sheets
const [isCreateGroupSheetVisible, setCreateGroupSheetVisible] =
useState(false);
const [isJoinGroupSheetVisible, setJoinGroupSheetVisible] = useState(false);
// State for the group that user wants to join
const [groupToJoin, setGroupToJoin] = useState(null);
// Condition to hide the entire screen if needed
const [shouldHide, setShouldHide] = useState(false);
useEffect(() => {
if (!isCreateGroupSheetVisible && !isJoinGroupSheetVisible && pendingChat) {
const timer = setTimeout(() => {
navigation.navigate(SCREEN_CONSTANTS.MESSAGES, { group: pendingChat });
setPendingChat(null);
}, 300);
return () => clearTimeout(timer);
}
}, [isCreateGroupSheetVisible, isJoinGroupSheetVisible, pendingChat]);
useFocusEffect(
useCallback(() => {
setShouldHide(false);
return () => {
setShouldHide(true);
};
}, [navigation]),
);
/**
* Navigates to the Messages screen after group creation or join.
*/
const handleNavigateToMessages = (group: CometChat.Group) => {
// close any open sheet first
setCreateGroupSheetVisible(false);
setJoinGroupSheetVisible(false);
// save the group – navigation will happen in a useEffect below
setPendingChat(group);
};
/**
* Handle group item press:
* - If joined, open chat
* - If public, join automatically
* - If password, show the join modal
*/
const handleGroupItemPress = (group: CometChat.Group) => {
if (group.getHasJoined()) {
handleNavigateToMessages(group);
return;
}
if (group.getType() === CometChat.GROUP_TYPE.PUBLIC) {
joinPublicGroup(group);
} else if (group.getType() === CometChat.GROUP_TYPE.PASSWORD) {
setGroupToJoin(group);
setJoinGroupSheetVisible(true);
}
// For private group, you'd have a different flow.
};
const joinPublicGroup = async (group: CometChat.Group) => {
try {
const joinedGroup = await CometChat.joinGroup(
group.getGuid(),
group.getType() as CometChat.GroupType,
'',
);
handleNavigateToMessages(joinedGroup);
CometChatUIEventHandler.emitGroupEvent(
CometChatUIEvents.ccGroupMemberJoined,
{
joinedUser: CometChatUIKit.loggedInUser,
joinedGroup: joinedGroup,
},
);
} catch (error) {
console.log('Error joining public group:', error);
}
};
if (shouldHide) return null;
return (
{/* CometChatGroups list component */}
(
setCreateGroupSheetVisible(true)}
/>
)
: undefined
}
onItemPress={handleGroupItemPress}
hideHeader={hideHeader}
/>
{/* Create Group Bottom Sheet */}
setCreateGroupSheetVisible(false)}
onGroupCreated={handleNavigateToMessages}
/>
{/* Join Group Bottom Sheet */}
{
setJoinGroupSheetVisible(false);
setGroupToJoin(null);
}}
onJoinSuccess={handleNavigateToMessages}
/>
);
};
export default Groups;
================================================
FILE: examples/SampleApp/src/components/groups/styles.ts
================================================
import {Platform, StyleSheet} from 'react-native';
export const styles = StyleSheet.create({
safeAreaContainer: {
flex: 1,
},
bottomSheetContainer: {
paddingHorizontal: 20,
paddingBottom: Platform.OS==='android' ? 10: 30,
flex: 1,
},
marginBottom20: {
marginBottom: 20,
},
toastContainer: {
alignItems: 'flex-start',
},
toastMessage: {
paddingVertical: 10,
},
passwordInputContainer: {
borderWidth: 1,
flexDirection: 'row',
borderRadius: 12,
paddingLeft: 10,
paddingTop: Platform.select({android: 0, ios: 8}),
paddingBottom: Platform.select({android: 0, ios: 12}),
},
avatarIconContainer: {
alignItems: 'center',
justifyContent: 'center',
width: 80,
height: 80,
borderRadius: 50,
},
optionTabsContainer: {
borderWidth: 1,
flexDirection: 'row',
borderRadius: 12,
padding: Platform.OS === 'ios' ? 5 : 2,
justifyContent: 'space-between',
alignItems: 'center',
},
createButton: {
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 20,
paddingVertical: 12,
},
joinGroupButton: {
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 20,
paddingVertical: 12,
},
joiningGroup: {
paddingHorizontal: 20,
flex: 1,
marginBottom: 20,
},
});
================================================
FILE: examples/SampleApp/src/components/login/AppCredentials.tsx
================================================
import React, { useEffect, useMemo, useState } from 'react';
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity,
Image,
useColorScheme,
Platform,
StatusBar,
Dimensions,
ScrollView,
KeyboardAvoidingView,
BackHandler,
Keyboard,
} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {
CometChatUIKit,
UIKitSettings,
useCometChatTranslation,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import { navigate, navigationRef } from '../../navigation/NavigationService';
import { SCREEN_CONSTANTS } from '../../utils/AppConstants';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import { useFocusEffect } from '@react-navigation/native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useHeaderHeight } from '@react-navigation/elements';
const AppCredentials: React.FC = () => {
const [storedAppId, setStoredAppId] = useState('');
const [storedAuthKey, setStoredAuthKey] = useState('');
const [storedRegion, setStoredRegion] = useState('US');
// These are the *editable* states bound to the TextInput fields
const [appId, setAppId] = useState('');
const [authKey, setAuthKey] = useState('');
const [selectedRegion, setSelectedRegion] = useState('US');
// Toast state for showing error messages
const [toastMessage, setToastMessage] = useState(null);
const theme = useTheme();
const { t } = useCometChatTranslation();
const { width } = Dimensions.get('window');
const mode = useColorScheme();
const insets = useSafeAreaInsets();
const headerHeight = useHeaderHeight(); // returns 0 if no header
const statusBarHeight =
Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0;
const [keyboardBehavior, setKeyboardBehavior] = useState<
'padding' | 'height' | undefined
>(Platform.OS === 'ios' ? 'padding' : 'height');
useEffect(() => {
const showListener = Keyboard.addListener('keyboardDidShow', () => {
setKeyboardBehavior(Platform.OS === 'ios' ? 'padding' : 'height');
});
const hideListener = Keyboard.addListener('keyboardDidHide', () => {
setKeyboardBehavior(undefined); // Remove behavior when keyboard hides
});
return () => {
showListener.remove();
hideListener.remove();
};
}, []);
// For iOS use insets.top (not status bar) + header height
const keyboardVerticalOffset = useMemo(() => {
return Platform.OS === 'ios'
? (insets.top || 0) + (headerHeight || 0)
: (statusBarHeight || 0) + (headerHeight || 0);
}, [insets.top, headerHeight, statusBarHeight]);
// Compute if form is valid (all fields provided)
const isFormValid =
appId.trim().length > 0 &&
authKey.trim().length > 0 &&
selectedRegion.trim().length > 0;
// Load existing credentials if any
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
navigationRef.goBack();
return true; // Prevent default behavior
};
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
async function loadCredentials() {
try {
const credentialsStr = await AsyncStorage.getItem('appCredentials');
if (credentialsStr) {
const credentials = JSON.parse(credentialsStr);
// Update 'stored' states
setStoredAppId(credentials.appId || '');
setStoredAuthKey(credentials.authKey || '');
setStoredRegion(credentials.region || 'US');
// Also set the fields so user sees them pre-populated
setAppId(credentials.appId || '');
setAuthKey(credentials.authKey || '');
setSelectedRegion(credentials.region || 'US');
}
} catch (error) {
console.log('Error loading stored credentials:', error);
}
}
loadCredentials();
return () => backHandler.remove();
}, []),
);
const showToast = (message: string) => {
setToastMessage(message);
setTimeout(() => {
setToastMessage(null);
}, 2500);
};
const handleContinue = async (): Promise => {
// Validate the inputs.
// If the user has modified a field and cleared it (i.e. the input becomes empty),
// show a toast message.
if (!appId.trim()) {
showToast('Please enter App ID');
return;
}
if (!authKey.trim()) {
showToast('Please enter Auth Key');
return;
}
if (!selectedRegion.trim()) {
showToast('Please select a region');
return;
}
// Since all fields are non-empty, use their current values.
const newAppId = appId.trim();
const newAuthKey = authKey.trim();
const newRegion = selectedRegion.trim();
try {
const credentials = {
region: newRegion,
appId: newAppId,
authKey: newAuthKey,
};
console.log('Saving credentials:', credentials);
await AsyncStorage.setItem('appCredentials', JSON.stringify(credentials));
// Re-initialize with updated credentials
await CometChatUIKit.init({
appId: newAppId,
authKey: newAuthKey,
region: newRegion,
subscriptionType: CometChat.AppSettings
.SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings['subscriptionType'],
});
} catch (error) {
console.error('Failed to save credentials', error);
}
// Navigate to the next screen.
navigate('BottomTabNavigator');
navigationRef.reset({
index: 0,
routes: [{ name: SCREEN_CONSTANTS.SAMPLE_USER }],
});
};
return (
{/* Header/Logo */}
{/* Title */}
{t('APP_CREDENTIALS')}
{/* Region Selector */}
{t('REGION')}
{/* US */}
setSelectedRegion('US')}
>
US
{/* EU */}
setSelectedRegion('EU')}
>
EU
{/* IN */}
setSelectedRegion('IN')}
>
IN
{/* App ID */}
APP ID
{/* Auth Key */}
Auth Key
{/* Continue Button */}
{t('CONTINUE')}
{/* Toast Message */}
{toastMessage && (
{toastMessage}
)}
);
};
export default AppCredentials;
const styles = StyleSheet.create({
logoContainer: {
alignItems: 'center',
marginTop: Platform.OS === 'android' ? 30 : 50,
marginBottom: 20,
},
inputContainer: {
width: '100%',
marginTop: 20,
},
regionRow: {
flexDirection: 'row',
justifyContent: 'space-between',
},
flagContainer: {
width: '32%',
borderWidth: 2,
borderColor: 'transparent',
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
},
flagInnerContainer: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 10,
gap: 5,
},
flagImage: {
width: 30,
height: 30,
resizeMode: 'contain',
},
input: {
paddingHorizontal: 12,
paddingVertical: 10,
borderRadius: 8,
borderWidth: 1,
},
continueButton: {
borderRadius: 8,
paddingVertical: 12,
width: '100%',
},
toastContainer: {
position: 'absolute',
bottom: '8%',
left: 20,
right: 20,
backgroundColor: '#C73C3E',
padding: 6,
borderRadius: 8,
alignItems: 'center',
},
toastText: {
color: '#fff',
fontSize: 14,
},
container: {
flex: 1,
},
contentContainer: {
flex: 1,
paddingHorizontal: 16,
},
scrollContent: {
flexGrow: 1,
paddingBottom: 80,
},
buttonWrapper: {
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 10,
},
});
================================================
FILE: examples/SampleApp/src/components/login/SampleUser.tsx
================================================
import React, { useEffect, useState } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
TextInput,
Image,
Dimensions,
useColorScheme,
Pressable,
ImageSourcePropType,
KeyboardAvoidingView,
Platform,
ScrollView,
ActivityIndicator,
StatusBar,
Keyboard,
} from 'react-native';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import {
CometChatAvatar,
CometChatUIKit,
Icon,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import Check from '../../assets/icons/CheckFill';
import { sampleData } from '../../utils/helper';
import { SCREEN_CONSTANTS } from '../../utils/AppConstants';
import { navigate, navigationRef } from '../../navigation/NavigationService';
import Skeleton from './Skeleton';
import {
SafeAreaView,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
import { useHeaderHeight } from '@react-navigation/elements';
type GridItem = CometChat.User | { dummy: true };
const LoginScreen: React.FC = () => {
const [users, setUsers] = useState([]);
const [selectedUser, setSelectedUser] = useState(null);
const [userUID, setUserUID] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [loadingUsers, setLoadingUsers] = useState(true);
const theme = useTheme();
const mode = useColorScheme();
const { width } = Dimensions.get('window');
const statusBarHeight =
Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0;
const insets = useSafeAreaInsets();
const headerHeight = useHeaderHeight(); // returns 0 if no header
const keyboardVerticalOffset =
Platform.OS === 'ios'
? (insets.top + 8 || 0) + (headerHeight || 0)
: (statusBarHeight + 6 || 0) + (headerHeight || 0);
const [keyboardBehavior, setKeyboardBehavior] = useState<
'padding' | 'height' | undefined
>(Platform.OS === 'ios' ? 'padding' : 'height');
useEffect(() => {
const showListener = Keyboard.addListener('keyboardDidShow', () => {
setKeyboardBehavior(Platform.OS === 'ios' ? 'padding' : 'height');
});
const hideListener = Keyboard.addListener('keyboardDidHide', () => {
setKeyboardBehavior(undefined); // Remove behavior when keyboard hides
});
return () => {
showListener.remove();
hideListener.remove();
};
}, []);
useEffect(() => {
(async function loadUsers(): Promise {
try {
setLoadingUsers(true);
const fetchedUsers = await fetchUsers();
setUsers(fetchedUsers);
} catch (error) {
console.error(error);
} finally {
setLoadingUsers(false);
}
})();
}, []);
const handleSelectUser = (user: CometChat.User): void => {
setSelectedUser(user.getUid());
setUserUID('');
};
const handleContinue = async () => {
if ((!selectedUser && !userUID.trim()) || isLoading) return;
setIsLoading(true);
const uid: string = userUID.trim() || selectedUser!;
try {
await CometChatUIKit.login({ uid });
navigate('BottomTabNavigator');
navigationRef.reset({
index: 0,
routes: [{ name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR }],
});
} catch (error: any) {
console.log('Login failed with exception:', error);
} finally {
setIsLoading(false);
}
};
/**
* Fetch users from a remote sample JSON file.
* Falls back to local sample data if there's an error.
*/
async function fetchUsers(): Promise {
try {
const response = await fetch(
'https://assets.cometchat.io/sampleapp/sampledata.json',
);
if (response.ok) {
const data = await response.json();
const fetchedUsers = data.users || [];
return fetchedUsers.map((user: any) => new CometChat.User(user));
} else {
throw new Error('Failed to load users');
}
} catch (error) {
console.error('Exception while fetching users:', error);
return await getDefaultUsers();
}
}
/**
* Get users from local sample data (used in case the remote fetch fails).
*/
async function getDefaultUsers(): Promise {
const localUsers = sampleData.users || [];
return localUsers.map((user: any) => new CometChat.User(user));
}
/**
* Returns the appropriate image source object for the avatar.
*/
const getAvatarSource = (
avatar: string | ImageSourcePropType,
): ImageSourcePropType => {
if (typeof avatar === 'string') {
if (avatar.startsWith('http://') || avatar.startsWith('https://')) {
return { uri: avatar };
}
}
return avatar as ImageSourcePropType;
};
// Show skeleton if the API is still loading or if no users are available.
const showSkeleton = loadingUsers || users.length === 0;
// Compute grid data only if users are available.
let gridData: GridItem[] = [];
if (users.length > 0) {
gridData = [...users];
const numColumns = 3;
const numberOfElementsLastRow = users.length % numColumns;
if (numberOfElementsLastRow !== 0) {
for (let i = 0; i < numColumns - numberOfElementsLastRow; i++) {
gridData.push({ dummy: true });
}
}
}
const Loading = () => {
return (
);
};
return (
{/* App Logo */}
{/* Title */}
Log In
{/* Subtitle */}
Choose a Sample User
{/* Sample Users Grid */}
{showSkeleton ? (
) : (
{gridData.map((item, index) => {
// Render a blank view for dummy items
if ('dummy' in item && item.dummy) {
return (
);
}
// Otherwise, render a user
const user = item as CometChat.User;
const isSelected = selectedUser === user.getUid();
const firstName = user.getName();
return (
handleSelectUser(user)}
>
{/* Show the check icon ONLY if selected */}
{isSelected && (
}
/>
)}
{/* Display only the first name */}
{firstName}
{user.getUid()}
);
})}
)}
{/* Horizontal divider with "Or" in the middle */}
Or
{/* UID Input */}
Enter Your UID
{
setUserUID(text);
setSelectedUser(null);
}}
/>
{/* Bottom container with "Continue" button and "Change App Credentials" */}
{isLoading ? (
) : (
Continue
)}
Change{' '}
{
navigationRef.navigate(SCREEN_CONSTANTS.APP_CRED);
}}
>
App Credentials
);
};
export default LoginScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
},
keyboardAvoidingContainer: {
flex: 1,
},
scrollContainer: {
paddingHorizontal: 16,
paddingTop: 16,
},
logoContainer: {
alignItems: 'center',
marginBottom: 16,
},
logInTitle: {
marginBottom: 16,
alignSelf: 'center',
},
subtitle: {
marginBottom: 6,
},
usersContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-around',
},
userCard: {
position: 'relative',
width: '30%',
borderRadius: 8,
paddingVertical: 16,
paddingHorizontal: 8,
marginBottom: 12,
alignItems: 'center',
overflow: 'hidden',
},
checkIconContainer: {
position: 'absolute',
top: 0,
right: 0,
borderBottomLeftRadius: 10,
borderTopRightRadius: 7,
width: '27%',
height: '22%',
backgroundColor: '#7367F0',
alignItems: 'center',
justifyContent: 'center',
zIndex: 2,
},
firstNameText: {
marginTop: 8,
textAlign: 'center',
},
uidText: {
marginTop: 4,
textAlign: 'center',
},
dividerRow: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 16,
gap: 10,
},
divider: {
flex: 1,
height: 1,
borderWidth: 0.5,
},
uidLabel: {
paddingBottom: 5,
},
uidInput: {
borderWidth: 1,
borderRadius: 8,
padding: 10,
marginBottom: 24,
},
bottomContainer: {
paddingHorizontal: 16,
backgroundColor: 'transparent',
},
continueButton: {
paddingVertical: 12,
borderRadius: 6,
marginBottom: 12,
},
continueButtonText: {
alignSelf: 'center',
},
changeCredentialsWrapper: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 10,
},
changeCredentialsContainer: {
alignItems: 'center',
justifyContent: 'center',
},
userGridWrapper: {
minHeight: 240,
justifyContent: 'center',
alignItems: 'center',
},
});
================================================
FILE: examples/SampleApp/src/components/login/Skeleton.tsx
================================================
import React, { useEffect, useRef, useState } from "react";
import { Animated, Dimensions, Easing, StyleSheet, useColorScheme, View } from "react-native";
import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg";
// import { useThemeInternal } from "../../../theme/hook";
const { width: screenWidth } = Dimensions.get("window");
const SkeletonBox = ({ index, boxSize, gradientColors }: any) => {
// Determine if the box is the last in its row
const isLastInRow = (index + 1) % 3 === 0;
return (
);
};
export const Skeleton = () => {
const animatedValue = useRef(new Animated.Value(0)).current;
const [isLoading, setIsLoading] = useState(true);
const mode = useColorScheme();
// Define static colors
const color = {
staticBlack: "#000000",
staticWhite: "#FFFFFF",
};
// Define skeletonStyle based on the theme mode
const skeletonStyle =
mode === "light"
? {
linearGradientColors: ["#E8E8E8", "#F5F5F5"],
shimmerBackgroundColor: color.staticBlack,
shimmerOpacity: 0.01,
speed: 1,
}
: {
linearGradientColors: ["#383838", "#272727"],
shimmerBackgroundColor: color.staticWhite,
shimmerOpacity: 0.01,
speed: 1,
};
const { linearGradientColors, shimmerBackgroundColor, shimmerOpacity, speed } =
skeletonStyle;
useEffect(() => {
const startShimmer = () => {
animatedValue.setValue(0);
Animated.loop(
Animated.timing(animatedValue, {
toValue: 1,
duration: (1 / speed) * 1000,
easing: Easing.linear,
useNativeDriver: false,
})
).start();
};
startShimmer();
// Simulate a loading time of 3 seconds
const loadData = setTimeout(() => {
setIsLoading(false);
}, 3000);
return () => clearTimeout(loadData);
}, [animatedValue, speed]);
const shimmerTranslateX = animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [-screenWidth, screenWidth],
});
if (!isLoading) {
return null;
}
const boxSize = (screenWidth - 22) / 3;
const shimmerWidth = screenWidth;
const shimmerHeight = boxSize + 16;
return (
{new Array(6).fill(0).map((_, index) => (
))}
);
};
const styles = StyleSheet.create({
container: {
position: "relative",
borderRadius: 16,
overflow: "hidden",
},
grid: {
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "space-between",
},
skeletonBox: {
marginBottom: 10,
marginHorizontal: -10,
},
animatedShimmer: {
position: "absolute",
top: 0,
left: 0,
},
});
export default Skeleton;
================================================
FILE: examples/SampleApp/src/components/users/Users.tsx
================================================
import { CometChat } from '@cometchat/chat-sdk-react-native';
import { CometChatUsers, useTheme } from '@cometchat/chat-uikit-react-native';
import React, { useCallback } from 'react';
import { View } from 'react-native';
import { useFocusEffect, useNavigation } from '@react-navigation/native';
import { RootStackParamList } from '../../navigation/types';
import { StackNavigationProp } from '@react-navigation/stack';
type UserNavigationProp = StackNavigationProp;
const Users: React.FC = () => {
const theme = useTheme();
const navigation = useNavigation();
const [shouldHide, setShouldHide] = React.useState(false);
useFocusEffect(
useCallback(() => {
setShouldHide(false);
return () => {
setShouldHide(true);
};
}, []),
);
return shouldHide ? null : (
{
navigation.navigate('Messages', {
user: user,
});
}}
usersRequestBuilder={new CometChat.UsersRequestBuilder()
.setLimit(30)
.hideBlockedUsers(false)
// .setRoles(['@agentic'])
.friendsOnly(false)
.setStatus('')
.setTags([])
.sortBy('name')
.setUIDs([])}
/>
);
};
export default Users;
================================================
FILE: examples/SampleApp/src/config/config.json
================================================
{
"builderId": "8c0c1c80-b08e-4960-80d4-686396119711",
"settings": {
"chatFeatures": {
"coreMessagingExperience": {
"typingIndicator": true,
"threadConversationAndReplies": true,
"photosSharing": true,
"videoSharing": true,
"audioSharing": true,
"fileSharing": true,
"editMessage": true,
"deleteMessage": true,
"messageDeliveryAndReadReceipts": true,
"userAndFriendsPresence": true
},
"deeperUserEngagement": {
"mentions": true,
"reactions": true,
"messageTranslation": true,
"polls": true,
"collaborativeWhiteboard": true,
"collaborativeDocument": true,
"voiceNotes": true,
"emojis": true,
"stickers": true,
"userInfo": true,
"groupInfo": true
},
"aiUserCopilot": {
"conversationStarter": true,
"conversationSummary": true,
"smartReply": true
},
"groupManagement": {
"createGroup": true,
"addMembersToGroups": true,
"joinLeaveGroup": true,
"deleteGroup": true,
"viewGroupMembers": true
},
"moderatorControls": {
"kickUsers": true,
"banUsers": true,
"promoteDemoteMembers": true
},
"privateMessagingWithinGroups": {
"sendPrivateMessageToGroupMembers": true
}
},
"callFeatures": {
"voiceAndVideoCalling": {
"oneOnOneVoiceCalling": true,
"oneOnOneVideoCalling": true,
"groupVideoConference": true,
"groupVoiceConference": true
}
},
"layout": {
"withSideBar": true,
"tabs": [
"chats",
"calls",
"users",
"groups"
],
"chatType": "both",
"compactMessageComposer": true
},
"style": {
"theme": "system",
"color": {
"brandColor": "#6852D6",
"primaryTextLight": "#141414",
"primaryTextDark": "#FFFFFF",
"secondaryTextLight": "#727272",
"secondaryTextDark": "#989898"
},
"typography": {
"font": "roboto",
"size": "default"
}
},
"noCode": {
"docked": false,
"styles": {
"buttonBackGround": "#6952d6",
"buttonShape": "rounded",
"openIcon": "https://nocode-js.cometchat.io/v1/resources/docked_open_icon.svg",
"closeIcon": "https://nocode-js.cometchat.io/v1/resources/docked_close_icon.svg",
"customJs": "",
"customCss": ""
}
}
},
"name": "ttttt",
"type": "low-code",
"createdAt": 1749032525,
"updatedAt": 1749032525,
"expiresAt": 1812108125
}
================================================
FILE: examples/SampleApp/src/config/store.ts
================================================
import { create } from 'zustand';
import config from './config.json';
import AsyncStorage from '@react-native-async-storage/async-storage';
// TypeScript interfaces for the config structure
interface ColorConfig {
brandColor: string;
primaryTextLight: string;
primaryTextDark: string;
secondaryTextLight: string;
secondaryTextDark: string;
}
interface TypographyConfig {
font: string;
size: string;
}
interface StyleConfig {
theme: string;
color: ColorConfig;
typography: TypographyConfig;
}
interface NoCodeStyles {
buttonBackGround: string;
buttonShape: string;
openIcon: string;
closeIcon: string;
customJs: string;
customCss: string;
}
interface NoCodeConfig {
docked: boolean;
styles: NoCodeStyles;
}
interface LayoutConfig {
withSideBar: boolean;
tabs: string[];
chatType: string;
compactMessageComposer: boolean;
}
interface CoreMessagingConfig {
typingIndicator: boolean;
threadConversationAndReplies: boolean;
photosSharing: boolean;
videoSharing: boolean;
audioSharing: boolean;
fileSharing: boolean;
editMessage: boolean;
deleteMessage: boolean;
messageDeliveryAndReadReceipts: boolean;
userAndFriendsPresence: boolean;
}
interface DeeperEngagementConfig {
mentions: boolean;
reactions: boolean;
messageTranslation: boolean;
polls: boolean;
collaborativeWhiteboard: boolean;
collaborativeDocument: boolean;
voiceNotes: boolean;
emojis: boolean;
stickers: boolean;
userInfo: boolean;
groupInfo: boolean;
}
interface AiUserCopilotConfig {
conversationStarter: boolean;
conversationSummary: boolean;
smartReply: boolean;
}
interface GroupManagementConfig {
createGroup: boolean;
addMembersToGroups: boolean;
joinLeaveGroup: boolean;
deleteGroup: boolean;
viewGroupMembers: boolean;
}
interface ModeratorControlsConfig {
kickUsers: boolean;
banUsers: boolean;
promoteDemoteMembers: boolean;
}
interface PrivateMessagingConfig {
sendPrivateMessageToGroupMembers: boolean;
}
interface ChatFeaturesConfig {
coreMessagingExperience: CoreMessagingConfig;
deeperUserEngagement: DeeperEngagementConfig;
aiUserCopilot: AiUserCopilotConfig;
groupManagement: GroupManagementConfig;
moderatorControls: ModeratorControlsConfig;
privateMessagingWithinGroups: PrivateMessagingConfig;
}
interface VoiceVideoCallingConfig {
oneOnOneVoiceCalling: boolean;
oneOnOneVideoCalling: boolean;
groupVideoConference: boolean;
groupVoiceConference: boolean;
}
interface CallFeaturesConfig {
voiceAndVideoCalling: VoiceVideoCallingConfig;
}
interface SettingsConfig {
chatFeatures: ChatFeaturesConfig;
callFeatures: CallFeaturesConfig;
layout: LayoutConfig;
style: StyleConfig;
noCode: NoCodeConfig;
}
interface AppConfig {
builderId: string;
settings: SettingsConfig;
name: string;
type: string;
createdAt: number;
updatedAt: number;
expiresAt: number;
}
// Zustand store interface
interface ConfigStore {
config: AppConfig;
updateConfig: (newConfig: Partial) => void;
resetConfig: () => void;
}
// Initialize config from storage
const initializeConfig = async (): Promise => {
try {
const savedConfig = await AsyncStorage.getItem('@app_config');
if (savedConfig) {
const parsedConfig = JSON.parse(savedConfig);
// Handle both old format (direct config) and new API response format (nested in data)
let actualConfig: AppConfig;
if (parsedConfig.data && parsedConfig.data.settings) {
// New API response format - extract data property
actualConfig = parsedConfig.data as AppConfig;
} else if (parsedConfig.settings && parsedConfig.builderId) {
// Old format - direct config
actualConfig = parsedConfig as AppConfig;
} else {
// Invalid structure, use default
return config as AppConfig;
}
// Validate that the config has the expected structure
if (actualConfig && actualConfig.settings && actualConfig.builderId) {
return actualConfig;
}
}
} catch (error) {
console.error('Error loading config from storage:', error);
}
return config as AppConfig;
};
// Create the Zustand store
export const useConfigStore = create((set, _get) => ({
// Initialize with the config.json data
config: config as AppConfig,
// Update the entire config
updateConfig: (newConfig) =>
set((state) => ({
config: { ...state.config, ...newConfig },
})),
// Reset to original config
resetConfig: () =>
set({
config: config as AppConfig,
}),
}));
// Initialize the store with saved config if available
initializeConfig().then((initialConfig) => {
useConfigStore.setState({ config: initialConfig });
}).catch((error) => {
console.error('Error initializing config:', error);
// Fallback to default config from JSON file
useConfigStore.setState({ config: config as AppConfig });
});
export const useConfig = (selector: (state: AppConfig) => T) =>
useConfigStore((state) => selector(state.config));
================================================
FILE: examples/SampleApp/src/declarations.d.ts
================================================
declare module '*.png' {
const value: any;
export default value;
}
================================================
FILE: examples/SampleApp/src/hooks/useGroupMemberStatus.ts
================================================
import { useState, useEffect, useRef } from 'react';
//@ts-ignore
import { CometChat } from '@cometchat/chat-sdk-react-native';
import { CometChatUIEventHandler } from '@cometchat/chat-uikit-react-native';
/**
* Hook that detects whether the logged-in user is no longer a member
* of the given group (kicked or banned).
*
* - On mount: checks current membership via CometChat.getGroup()
* - Real-time: listens to SDK + UI events for kicked/banned/re-added
*
* @param group - The CometChat group object (pass undefined for 1-on-1 chats)
* @returns `true` when the current user is no longer a member
*/
export const useGroupMemberStatus = (group?: CometChat.Group): boolean => {
const [isNoLongerMember, setIsNoLongerMember] = useState(false);
const loggedInUser = useRef(null);
// Fetch logged-in user once
useEffect(() => {
CometChat.getLoggedinUser().then((u: CometChat.User | null) => {
if (u) loggedInUser.current = u;
});
}, []);
// Check membership on mount by fetching fresh group data
useEffect(() => {
if (!group) return;
CometChat.getGroup(group.getGuid())
.then((freshGroup: CometChat.Group) => {
if (!freshGroup.getHasJoined()) {
setIsNoLongerMember(true);
}
})
.catch(() => {
setIsNoLongerMember(true);
});
}, [group]);
useEffect(() => {
if (!group) return;
const uiListenerId = 'composer_group_status_' + new Date().getTime();
const sdkListenerId = 'composer_sdk_group_status_' + new Date().getTime();
CometChatUIEventHandler.addGroupListener(uiListenerId, {
ccGroupMemberKicked: ({ kickedUser }: any) => {
if (kickedUser?.getUid?.() === loggedInUser.current?.getUid?.()) {
setIsNoLongerMember(true);
}
},
ccGroupMemberBanned: ({ kickedUser, bannedUser }: any) => {
const affected = bannedUser || kickedUser;
if (affected?.getUid?.() === loggedInUser.current?.getUid?.()) {
setIsNoLongerMember(true);
}
},
ccGroupMemberAdded: ({ usersAdded }: any) => {
if (Array.isArray(usersAdded)) {
const wasReAdded = usersAdded.some(
(u: any) =>
u?.getUid?.() === loggedInUser.current?.getUid?.() ||
u?.uid === loggedInUser.current?.getUid?.()
);
if (wasReAdded) {
setIsNoLongerMember(false);
}
}
},
});
CometChat.addGroupListener(
sdkListenerId,
new CometChat.GroupListener({
onGroupMemberKicked: (
_message: any,
kickedUser: any,
_kickedBy: any,
kickedFrom: any
) => {
if (
kickedFrom?.getGuid?.() === group.getGuid() &&
kickedUser?.getUid?.() === loggedInUser.current?.getUid?.()
) {
setIsNoLongerMember(true);
}
},
onGroupMemberBanned: (
_message: any,
bannedUser: any,
_bannedBy: any,
bannedFrom: any
) => {
if (
bannedFrom?.getGuid?.() === group.getGuid() &&
bannedUser?.getUid?.() === loggedInUser.current?.getUid?.()
) {
setIsNoLongerMember(true);
}
},
onMemberAddedToGroup: (
_message: any,
addedUser: any,
_addedBy: any,
addedTo: any
) => {
if (
addedTo?.getGuid?.() === group.getGuid() &&
addedUser?.getUid?.() === loggedInUser.current?.getUid?.()
) {
setIsNoLongerMember(false);
}
},
})
);
return () => {
CometChatUIEventHandler.removeGroupListener(uiListenerId);
CometChat.removeGroupListener(sdkListenerId);
};
}, [group]);
return isNoLongerMember;
};
================================================
FILE: examples/SampleApp/src/hooks/useIsKeyboardVisible.ts
================================================
import { useState, useEffect } from 'react';
import { Keyboard, KeyboardEvent } from 'react-native';
/**
* Custom hook that returns whether the keyboard is currently visible.
* Uses React Native's Keyboard API directly, independent of ChatUiKit.
* @returns {boolean} True if keyboard is visible, false otherwise.
*/
export const useIsKeyboardVisible = (): boolean => {
const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
useEffect(() => {
const showSubscription = Keyboard.addListener('keyboardDidShow', () => {
setIsKeyboardVisible(true);
});
const hideSubscription = Keyboard.addListener('keyboardDidHide', () => {
setIsKeyboardVisible(false);
});
return () => {
showSubscription.remove();
hideSubscription.remove();
};
}, []);
return isKeyboardVisible;
};
================================================
FILE: examples/SampleApp/src/navigation/AuthContext.tsx
================================================
import React from 'react';
export interface AuthContextProps {
isLoggedIn: boolean;
setIsLoggedIn: React.Dispatch>;
}
export const AuthContext = React.createContext({
isLoggedIn: false,
setIsLoggedIn: () => {},
});
================================================
FILE: examples/SampleApp/src/navigation/BottomTabNavigator.tsx
================================================
import React from 'react';
import {
StyleSheet,
Platform,
View,
TouchableWithoutFeedback,
Text,
} from 'react-native';
import {
createBottomTabNavigator,
BottomTabBarButtonProps,
} from '@react-navigation/bottom-tabs';
import {useTheme, Icon, useCometChatTranslation } from '@cometchat/chat-uikit-react-native';
import {SCREEN_CONSTANTS} from '../utils/AppConstants';
import { useIsKeyboardVisible } from '../hooks/useIsKeyboardVisible';
import ChatFill from '../assets/icons/Chatfill';
import Chat from '../assets/icons/Chat';
import PersonFill from '../assets/icons/PersonFill';
import Person from '../assets/icons/Person';
import GroupFill from '../assets/icons/GroupFill';
import CallFill from '../assets/icons/CallFill';
import Call from '../assets/icons/Call';
import Group from '../assets/icons/Group';
import Conversations from '../components/conversations/screens/Conversations';
import Calls from '../components/calls/Calls';
import Users from '../components/users/Users';
import Groups from '../components/groups/Groups';
import {BottomTabParamList} from './types';
import { useConfig } from '../config/store';
// Create the tab navigator.
const Tab = createBottomTabNavigator();
// Define a type for icon components that accept color, height, and width props.
type IconComponentType = React.ComponentType<{
color?: string;
height?: number;
width?: number;
}>;
// Update the icons mapping to use the imported image components.
const icons: Record<
string,
{active: IconComponentType; inactive: IconComponentType}
> = {
Chats: {active: ChatFill, inactive: Chat},
Users: {active: PersonFill, inactive: Person},
Calls: {active: CallFill, inactive: Call},
Groups: {active: GroupFill, inactive: Group},
};
const CustomTabBarButton = ({children, onPress}: BottomTabBarButtonProps) => (
{children}
);
const BottomTabNavigator = () => {
const theme = useTheme();
const tabs = useConfig(state => state.settings.layout.tabs);
const { t } = useCometChatTranslation();
// Use the custom hook to track keyboard visibility
const isKeyboardVisible = useIsKeyboardVisible();
// Map tab keys to screen names and components
const TAB_COMPONENTS: Record }> = {
chats: { name: SCREEN_CONSTANTS.CHATS, component: Conversations },
calls: { name: SCREEN_CONSTANTS.CALLS, component: Calls },
users: { name: SCREEN_CONSTANTS.USERS, component: Users },
groups: { name: SCREEN_CONSTANTS.GROUPS, component: Groups },
};
return (
({
headerShown: false,
// Hide tab bar when keyboard is visible
tabBarStyle: isKeyboardVisible
? { display: 'none' }
: styles.tabBar,
animation: 'none',
tabBarIcon: ({focused}) => {
const iconSet = icons[route.name];
if (!iconSet) return null;
const IconComponent = focused ? iconSet.active : iconSet.inactive;
const iconColor = focused
? theme.color.primary
: theme.color.iconSecondary;
return (
}
/>
);
},
tabBarShowLabel: true,
tabBarLabel: ({focused}) =>
focused ? (
{t(route.name.toUpperCase())}
) : null,
tabBarButton: props => ,
tabBarBackground: () => (
),
})}
>
{tabs.map(tabKey => {
const tab = TAB_COMPONENTS[tabKey.toLowerCase()];
return tab ? (
) : null;
})}
);
};
const styles = StyleSheet.create({
tabBar: {
height: Platform.OS === 'ios' ? 60 : 70,
paddingBottom: Platform.OS === 'ios' ? 0 : 10,
paddingTop: 15,
borderTopWidth: 0,
elevation: 5,
shadowColor: '#000',
shadowOffset: {width: 0, height: -2},
shadowOpacity: 0.1,
shadowRadius: 3,
},
tabLabel: {
fontSize: 12,
marginBottom: 5,
},
tabButton: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default BottomTabNavigator;
================================================
FILE: examples/SampleApp/src/navigation/NavigationService.ts
================================================
import {createNavigationContainerRef} from '@react-navigation/native';
import {RootStackParamList} from './types';
export const navigationRef = createNavigationContainerRef();
interface PendingNavigation {
name: keyof RootStackParamList;
params?: any;
}
let pendingNavigation: PendingNavigation | null = null;
export function navigate(
name: RouteName,
params?: RootStackParamList[RouteName] extends undefined
? undefined
: RootStackParamList[RouteName],
) {
if (navigationRef.isReady()) {
// navigationRef.navigate(name as never);
navigationRef.navigate(name as any, params as any);
} else {
// Save the navigation intent for later processing
pendingNavigation = {name, params};
}
}
export function processPendingNavigation() {
if (pendingNavigation && navigationRef.isReady()) {
const {name, params} = pendingNavigation;
navigationRef.navigate(name as any, params as any);
pendingNavigation = null;
}
}
================================================
FILE: examples/SampleApp/src/navigation/RootStackNavigator.tsx
================================================
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { NavigationContainer, DefaultTheme } from '@react-navigation/native';
import BottomTabNavigator from './BottomTabNavigator';
import OngoingCallScreen from '../components/conversations/screens/OngoingCallScreen';
import { SCREEN_CONSTANTS } from '../utils/AppConstants';
import { useTheme } from '@cometchat/chat-uikit-react-native';
import { RootStackParamList } from './types';
import { navigationRef, processPendingNavigation } from './NavigationService';
import SampleUser from '../components/login/SampleUser';
import AppCredentials from '../components/login/AppCredentials';
import { StatusBar, useColorScheme } from 'react-native';
import Conversations from '../components/conversations/screens/Conversations';
import CreateConversation from '../components/conversations/screens/CreateConversation';
import Messages from '../components/conversations/screens/Messages';
import ThreadView from '../components/conversations/screens/ThreadView';
import UserInfo from '../components/conversations/screens/UserInfo';
import AddMember from '../components/conversations/screens/AddMember';
import BannedMember from '../components/conversations/screens/BannedMember';
import ViewMembers from '../components/conversations/screens/ViewMembers';
import GroupInfo from '../components/conversations/screens/GroupInfo';
import TransferOwnership from '../components/conversations/screens/TransferOwnership';
import Calls from '../components/calls/Calls';
import { CallDetails } from '../components/calls/CallDetails';
import Users from '../components/users/Users';
import Groups from '../components/groups/Groups';
import AIAgents from '../components/AIAgent/AIAgents';
import QRScreen from '../components/conversations/screens/qr_screen';
import SearchMessages from '../components/conversations/screens/SearchMessages';
type Props = {
isLoggedIn: boolean;
hasValidAppCredentials: boolean;
};
const Stack = createNativeStackNavigator();
const RootStackNavigator = ({isLoggedIn, hasValidAppCredentials: _hasValidAppCredentials}: Props) => {
const theme = useTheme();
const NavigationTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: theme.color.background1 as string,
},
};
const isDark = useColorScheme() === 'dark';
const backgroundColor = theme.color.background2;
const barStyle = isDark ? 'light-content' : 'dark-content';
return (
<>
{
processPendingNavigation();
}}
theme={NavigationTheme}
>
{/* Auth Screens */}
{/* Tab Screens */}
{/* Chat Screens */}
{/* Info Screens */}
{/* Call Screens */}
>
);
};
export default RootStackNavigator;
================================================
FILE: examples/SampleApp/src/navigation/types.ts
================================================
import { NavigatorScreenParams } from '@react-navigation/native';
import { CometChat } from '@cometchat/chat-sdk-react-native';
export type CallType = 'audio' | 'video';
export type RootStackParamList = {
Login: undefined;
BottomTabNavigator: NavigatorScreenParams;
OngoingCallScreen: { sessionId: string; callType?: CallType } | { call: any };
AppCredentials: undefined;
SampleUser: undefined;
Conversation: undefined;
CreateConversation: undefined;
Messages: {
user?: CometChat.User;
group?: CometChat.Group;
fromMention?: boolean;
fromMessagePrivately?: boolean;
parentMessageId?: string;
messageId?: string;
searchKeyword?: string;
navigatedFromSearch?: boolean;
};
SearchMessages: {
user?: CometChat.User;
group?: CometChat.Group;
};
AIAgents: undefined;
BannedMembers: undefined;
UserInfo: {
user: CometChat.User;
};
GroupInfo: {
group: CometChat.Group;
};
ThreadView: {
message: CometChat.BaseMessage;
user?: CometChat.User;
group?: CometChat.Group;
highlightMessageId?: string;
};
AddMember: {
group: CometChat.Group;
};
TransferOwnershipSection: {
group: CometChat.Group;
};
BannedMember: {
group: CometChat.Group;
};
ViewMembers: {
group: CometChat.Group;
};
CallLogs: undefined;
CallDetails: {
call: any;
};
Users: undefined;
Groups: undefined;
QRScreen: undefined;
};
export type BottomTabParamList = {
Chats: undefined;
Calls: undefined;
Users: undefined;
Groups: undefined;
};
================================================
FILE: examples/SampleApp/src/utils/ActiveChatContext.tsx
================================================
import React, { createContext, useContext, useState } from 'react';
type ActiveChat = {
type: 'user' | 'group';
id: string; // userUID or groupID
} | null;
type ActiveChatContextType = {
activeChat: ActiveChat;
setActiveChat: React.Dispatch>;
};
const ActiveChatContext = createContext(undefined);
export function ActiveChatProvider({ children }: { children: React.ReactNode }) {
const [activeChat, setActiveChat] = useState(null);
return (
{children}
);
}
// Hook for using in any component
export function useActiveChat() {
const context = useContext(ActiveChatContext);
if (!context) {
throw new Error('useActiveChat must be used within an ActiveChatProvider');
}
return context;
}
================================================
FILE: examples/SampleApp/src/utils/AppConstants.tsx
================================================
export const AppConstants = {
fcmProviderId: '',
apnsProviderId: '',
authKey: '',
appId: '',
region: '',
subscriptionType: 'ALL_USERS',
versionNumber: 'V5.3.4',
webClientId:
'',
iosClientId:
'',
};
export const SCREEN_CONSTANTS = {
LOGIN: 'Login',
APP_CRED: 'AppCredentials',
SAMPLE_USER: 'SampleUser',
ONGOING_CALL_SCREEN: 'OngoingCallScreen',
BOTTOM_TAB_NAVIGATOR: 'BottomTabNavigator',
CHATS: 'Chats',
CALLS: 'Calls',
USERS: 'Users',
GROUPS: 'Groups',
CONVERSATION: 'Conversation',
CREATE_CONVERSATION: 'CreateConversation',
MESSAGES: 'Messages',
SEARCH_MESSAGES: 'SearchMessages',
THREAD_VIEW: 'ThreadView',
USER_INFO: 'UserInfo',
GROUP_INFO: 'GroupInfo',
ADD_MEMBER: 'AddMember',
TRANSFER_OWNERSHIP: 'TransferOwnershipSection',
BANNED_MEMBER: 'BannedMember',
VIEW_MEMBER: 'ViewMembers',
CALL_LOGS: 'CallLogs',
CALL_DETAILS: 'CallDetails',
QR_SCREEN: 'QRScreen',
AI_AGENTS: 'AIAgents',
} as const;
================================================
FILE: examples/SampleApp/src/utils/CommonUtils.ts
================================================
export class CommonUtils {
static clone(arg: T): T {
/*
If there are additional properties attached to a function or an array object other than the standard properties, those properties will be ignored
Cannot copy private properties (those that start with a "#" symbol inside a class block)
Functions are copied by reference
*/
if (typeof arg !== 'object' || !arg) {
return arg;
}
let res;
if (Array.isArray(arg)) {
// arg is an array, there's no hatch to fool the Array.isArray method, so lets create an array
res = [];
for (const value of arg) {
res.push(CommonUtils.clone(value));
}
return res as T;
} else {
// arg is an object
res = {};
const descriptor = Object.getOwnPropertyDescriptors(arg);
for (const k of Reflect.ownKeys(descriptor)) {
const curDescriptor = descriptor[k as any];
if (curDescriptor.hasOwnProperty('value')) {
// Property is a data property
Object.defineProperty(res, k, {
...curDescriptor,
value: CommonUtils.clone(curDescriptor['value']),
});
} else {
// Property is an accessor property
Object.defineProperty(res, k, curDescriptor);
}
}
Object.setPrototypeOf(res, Object.getPrototypeOf(arg));
}
return res as T;
}
}
================================================
FILE: examples/SampleApp/src/utils/TooltipMenu.tsx
================================================
import { Icon, useTheme } from "@cometchat/chat-uikit-react-native";
import { JSX, useMemo } from "react";
import {
ColorValue,
Dimensions,
ImageSourcePropType,
Modal,
StyleSheet,
Text,
TouchableOpacity,
TouchableWithoutFeedback,
View,
} from "react-native";
const { width: screenWidth, height: screenHeight } = Dimensions.get("window");
type CometChatTooltipMenuProps = {
visible?: boolean;
onDismiss?: () => void;
onClose?: () => void;
event: {
nativeEvent: {
pageX: number;
pageY: number;
};
};
menuItems: {
text: string;
onPress: () => void;
textColor?: ColorValue;
iconColor?: ColorValue;
icon?: ImageSourcePropType | JSX.Element;
}[];
};
export const TooltipMenu = (props: CometChatTooltipMenuProps) => {
const { visible = false, onDismiss = () => null, onClose = () => null, event, menuItems } = props;
const theme = useTheme();
const position = useMemo(() => {
let x = event.nativeEvent.pageX;
let y = event.nativeEvent.pageY;
const position: {
left?: number;
right?: number;
top?: number;
bottom?: number;
} = {};
if (x <= screenWidth / 3) {
position.left = x + 10;
} else {
position.right = 12;
}
if (y <= screenHeight / 2) {
position.top = y + 20;
} else if (y >= screenHeight / 2) {
position.bottom = Math.max(screenHeight - y + 10, 40);
}
return position;
}, [event]);
return (
{menuItems.map((item, i) => {
return (
{
item.onPress();
onClose();
}}
style={[
{
flexDirection: "row",
alignItems: "center",
paddingVertical: 10,
paddingHorizontal: 16,
gap: 8,
backgroundColor: theme.color.background1,
minWidth: 160,
},
i === 0
? {
borderTopLeftRadius: theme.spacing.radius.r2,
borderTopRightRadius: theme.spacing.radius.r2,
}
: {},
i === menuItems.length - 1
? {
borderBottomLeftRadius: theme.spacing.radius.r2,
borderBottomRightRadius: theme.spacing.radius.r2,
}
: {},
]}
>
{item.text}
);
})}
);
};
const tooltipStyles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: "transparent",
justifyContent: "center",
alignItems: "center",
},
menu: {
position: "absolute",
shadowOffset: {
width: 0,
height: 8,
},
shadowOpacity: 0.025,
shadowRadius: 4,
elevation: 3,
},
menuItem: {
fontSize: 16,
paddingVertical: 5,
},
});
================================================
FILE: examples/SampleApp/src/utils/helper.ts
================================================
import {Platform, PermissionsAndroid} from 'react-native';
import {CometChat} from '@cometchat/chat-sdk-react-native';
import Ironman from '../assets/icons/ironman.png';
import Captainamerica from '../assets/icons/captainamerica.png';
import Wolverine from '../assets/icons/wolverine.png';
import Spiderman from '../assets/icons/spiderman.png';
import Cyclops from '../assets/icons/cyclops.png';
import {
CometChatUIEventHandler,
CometChatUIEvents,
CometChatUIKit,
} from '@cometchat/chat-uikit-react-native';
import {
NavigationContainerRefWithCurrent,
StackActions,
} from '@react-navigation/native';
import {RootStackParamList} from '../navigation/types';
import {SCREEN_CONSTANTS} from './AppConstants';
interface Translations {
lastSeen: string;
minutesAgo: (minutes: number) => string;
hoursAgo: (hours: number) => string;
}
interface NotifeeData {
receiverType?: 'user' | 'group';
conversationId?: string;
sender?: string;
messageId?: string;
parentId?: string;
[key: string]: any;
}
/**
* Request common Android permissions (notifications, camera, etc.)
* Only needed on Android.
*/
export async function requestAndroidPermissions() {
if (Platform.OS !== 'android') return;
try {
await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.CAMERA,
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
]);
} catch (err) {
console.warn('Android permissions error:', err);
}
}
/**
* getLastSeenTime UserInfoSection.
*/
export function getLastSeenTime(
timestamp: number | null,
translations: Translations,
): string {
if (timestamp === null) {
return `${translations.lastSeen} Unknown`;
}
// If timestamp is in seconds (length = 10), convert to milliseconds.
if (String(timestamp).length === 10) {
timestamp *= 1000;
}
const now = new Date();
const lastSeen = new Date(timestamp);
// Calculate the time differences
const diffInMillis = now.getTime() - lastSeen.getTime();
const diffInMinutes = Math.floor(diffInMillis / (1000 * 60));
const diffInHours = Math.floor(diffInMillis / (1000 * 60 * 60));
// Check if within last hour
if (diffInMinutes === 0) {
return `${translations.lastSeen} ${translations.minutesAgo(1)}`;
} else if (diffInMinutes < 60) {
return `${translations.lastSeen} ${translations.minutesAgo(diffInMinutes)}`;
}
// Check if within the last 24 hours
if (diffInHours < 24) {
return `${translations.lastSeen} ${translations.hoursAgo(diffInHours)}`;
}
// Determine if timestamp is within the current year
const isSameYear = lastSeen.getFullYear() === now.getFullYear();
// Options for date formatting
const dateOptions: Intl.DateTimeFormatOptions = {
day: '2-digit',
month: 'short',
...(isSameYear ? {} : { year: 'numeric' }),
};
// Options for time formatting
const timeOptions: Intl.DateTimeFormatOptions = {
hour: '2-digit',
minute: '2-digit',
hour12: true,
};
const formattedDate = lastSeen.toLocaleDateString(undefined, dateOptions);
const formattedTime = lastSeen.toLocaleTimeString(undefined, timeOptions);
if (formattedDate === 'Invalid Date' || formattedTime === 'Invalid Date') {
return `Offline`;
}
return `${translations.lastSeen} ${formattedDate} at ${formattedTime}`;
}
/**
* UNBLOCK
*/
export const unblock = async (
uid: string,
user: CometChat.User,
setBlocked: React.Dispatch>,
setUserObj: React.Dispatch>,
): Promise => {
try {
const response = await CometChat.unblockUsers([uid]);
const unBlockedUser = await CometChat.getUser(uid);
if (response) {
CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserUnBlocked, {
user: unBlockedUser,
});
setBlocked(false);
setUserObj(unBlockedUser);
} else {
console.log(
`Failed to unblock user with UID ${uid}. Response:`,
response,
);
}
} catch (error) {
console.error('Error unblocking user:', error);
}
};
/**
* BLOCK
*/
export const blockUser = async (
uid: string,
user: CometChat.User,
setBlocked: React.Dispatch>,
): Promise => {
try {
const response = await CometChat.blockUsers([uid]);
if (response) {
user.setBlockedByMe(true);
setBlocked(true);
CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserBlocked, {
user,
});
} else {
console.log(`Failed to block user with UID ${uid}. Response:`, response);
}
} catch (error) {
console.error('Error blocking user:', error);
}
};
/**
* LEAVE GROUP
*/
export const leaveGroup = (
group: CometChat.Group,
navigation: any,
pop: number,
) => {
if (group) {
const groupID = group.getGuid();
CometChat.leaveGroup(groupID).then(
() => {
let actionMessage: CometChat.Action = new CometChat.Action(
groupID,
CometChat.MESSAGE_TYPE.TEXT,
CometChat.RECEIVER_TYPE.GROUP,
CometChat.CATEGORY_ACTION as CometChat.MessageCategory,
);
actionMessage.setMessage(
`${CometChatUIKit.loggedInUser!.getName()} has left`,
);
// Initialize data to prevent crash when SDK accesses getData().metadata during render
actionMessage.setData({ metadata: {} });
CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupLeft, {
message: actionMessage, //Note: Add Action message after discussion
leftUser: CometChatUIKit.loggedInUser,
leftGroup: group,
});
navigation.pop(pop);
},
error => {
console.log('Group leaving failed:', error);
},
);
} else {
console.log('Group is not defined');
}
};
/**
* Sample Users Data
*/
export const sampleData = {
users: [
{uid: 'superhero1', name: 'Iron Man', avatar: Ironman},
{uid: 'superhero2', name: 'Captain America', avatar: Captainamerica},
{uid: 'superhero3', name: 'Spiderman', avatar: Spiderman},
{uid: 'superhero4', name: 'Wolverine', avatar: Wolverine},
{uid: 'superhero5', name: 'Cyclops', avatar: Cyclops},
],
};
/**
* Navigate to conversation based on notification data.
*/
export async function navigateToConversation(
navigationRef: NavigationContainerRefWithCurrent,
data?: NotifeeData,
) {
if (!data || !navigationRef.current) {
return;
}
try {
// Handle group
if (data.receiverType === 'group') {
const extractedId =
typeof data.conversationId === 'string'
? data.conversationId.split('_').slice(1).join('_')
: '';
const group = await CometChat.getGroup(extractedId);
// Navigate with parent message ID if available (for agentic conversations)
const params: any = {group};
if (data.parentId) {
params.parentMessageId = data.parentId;
}
navigationRef.current?.dispatch(StackActions.push(SCREEN_CONSTANTS.MESSAGES, params));
}
// Handle user
else if (data.receiverType === 'user') {
const ccUser = await CometChat.getUser(data.sender);
// Navigate with parent message ID if available (for agentic conversations)
const params: any = {user: ccUser};
if (data.parentId) {
params.parentMessageId = data.parentId;
}
navigationRef.current?.dispatch(
StackActions.push(SCREEN_CONSTANTS.MESSAGES, params),
);
}
} catch (error) {
console.log('Error in navigateToConversation:', error);
}
}
================================================
FILE: examples/SampleApp/src/utils/themeTypography.ts
================================================
import { Platform } from 'react-native';
export type TypographyVariant = {
regular: { fontFamily: string };
medium: { fontFamily: string };
bold: { fontFamily: string };
};
export type Typography = {
fontFamily: string;
link: { fontFamily: string };
} & Record<
| "title"
| "heading1"
| "heading2"
| "heading3"
| "heading4"
| "body"
| "caption1"
| "caption2"
| "button",
TypographyVariant
>;
// Simple font mapping - response name to file names (without extension)
const FONT_MAP: Record = {
'times new roman': {
regular: Platform.OS === 'ios' ? 'TimesNewRomanPSMT' : 'times_new_roman_regular',
medium: Platform.OS === 'ios' ? 'TimesNewRomanPSMT' : 'times_new_roman_medium',
bold: Platform.OS === 'ios' ? 'TimesNewRomanPS-BoldMT' : 'times_new_roman_bold',
},
'inter': {
regular: Platform.OS === 'ios' ? 'Inter-Regular' : 'inter_regular',
medium: Platform.OS === 'ios' ? 'Inter-Medium' : 'inter_medium',
bold: Platform.OS === 'ios' ? 'Inter-Bold' : 'inter_bold',
},
'roboto': {
regular: Platform.OS === 'ios' ? 'Roboto-Regular' : 'roboto_regular',
medium: Platform.OS === 'ios' ? 'Roboto-Medium' : 'roboto_medium',
bold: Platform.OS === 'ios' ? 'Roboto-Bold' : 'roboto_bold',
},
};
/**
* Creates a complete Typography object for CometChat theme.
* Normalizes font name, provides fallback, and builds all variants.
*
* @param font - The font name from backend (e.g., 'inter', 'roboto', 'times new roman')
* @returns Complete Typography object with all variants configured
*/
export const createTypography = (font: string): Typography => {
const types = [
"title",
"heading1",
"heading2",
"heading3",
"heading4",
"body",
"caption1",
"caption2",
"button",
] as const;
// Normalize font name from backend and provide fallback
const fontKey = font ? font.toLowerCase().trim() : '';
const fontVariants = FONT_MAP[fontKey] || FONT_MAP['times new roman'];
// Defensive: ensure all variants exist
const baseStyle: TypographyVariant = {
regular: { fontFamily: fontVariants.regular },
medium: { fontFamily: fontVariants.medium },
bold: { fontFamily: fontVariants.bold },
};
const typography: Typography = {
fontFamily: fontVariants.regular,
link: { fontFamily: fontVariants.regular },
} as Typography;
types.forEach((type) => {
typography[type] = { ...baseStyle };
});
return typography;
};
================================================
FILE: examples/SampleApp/tsconfig.json
================================================
{
"extends": "@react-native/typescript-config",
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["**/node_modules", "**/Pods"]
}
================================================
FILE: examples/SampleAppAI/.bundle/config
================================================
BUNDLE_PATH: "vendor/bundle"
BUNDLE_FORCE_RUBY_PLATFORM: 1
================================================
FILE: examples/SampleAppAI/.eslintrc.js
================================================
module.exports = {
root: true,
extends: '@react-native',
};
================================================
FILE: examples/SampleAppAI/.gitignore
================================================
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
**/.xcode.env.local
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
.cxx/
*.keystore
!debug.keystore
.kotlin/
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
**/fastlane/test_output
# Bundle artifact
*.jsbundle
# Ruby / CocoaPods
**/Pods/
/vendor/bundle/
# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*
# testing
/coverage
# Yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
================================================
FILE: examples/SampleAppAI/.prettierrc.js
================================================
module.exports = {
arrowParens: 'avoid',
singleQuote: true,
trailingComma: 'all',
};
================================================
FILE: examples/SampleAppAI/.watchmanconfig
================================================
{}
================================================
FILE: examples/SampleAppAI/App.tsx
================================================
import { StatusBar, useColorScheme } from 'react-native';
import './gesture-handler';
import React, { useState, useEffect, useRef } from 'react';
import {
Platform,
View,
PlatformColor,
AppState,
AppStateStatus,
} from 'react-native';
import { enableScreens } from 'react-native-screens';
enableScreens();
import {
CometChatIncomingCall,
CometChatThemeProvider,
CometChatUIEventHandler,
CometChatUIEvents,
CometChatUIKit,
UIKitSettings,
} from '@cometchat/chat-uikit-react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import RootStackNavigator from './src/navigation/RootStackNavigator';
import { AppConstants } from './src/utils/AppConstants';
import { requestAndroidPermissions } from './src/utils/helper';
import AsyncStorage from '@react-native-async-storage/async-storage';
function App() {
const isDarkMode = useColorScheme() === 'dark';
return (
);
}
const AppContent = (): React.ReactElement => {
const [callReceived, setCallReceived] = useState(false);
const incomingCall = useRef(
null,
);
const [isInitializing, setIsInitializing] = useState(true);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userLoggedIn, setUserLoggedIn] = useState(false);
const [hasValidAppCredentials, setHasValidAppCredentials] = useState(false);
// Listener ID for registering and removing CometChat listeners.
const listenerId = 'app';
/**
* Initialize CometChat UIKit and configure Google Sign-In.
* Retrieves credentials from AsyncStorage and uses fallback constants if needed.
*/
useEffect(() => {
async function init() {
try {
// Retrieve stored app credentials or default to an empty object.
const AppData = (await AsyncStorage.getItem('appCredentials')) || '{}';
const storedCredentials = JSON.parse(AppData);
// Determine the final credentials (from AsyncStorage or AppConstants).
const finalAppId = storedCredentials.appId || AppConstants.appId;
const finalAuthKey = storedCredentials.authKey || AppConstants.authKey;
const finalRegion = storedCredentials.region || AppConstants.region;
// Set hasValidAppCredentials based on whether all values are available.
if (finalAppId && finalAuthKey && finalRegion) {
setHasValidAppCredentials(true);
} else {
setHasValidAppCredentials(false);
}
await CometChatUIKit.init({
appId: finalAppId,
authKey: finalAuthKey,
region: finalRegion,
subscriptionType: CometChat.AppSettings
.SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings['subscriptionType'],
});
// If a user is already logged in, update the state.
const loggedInUser = CometChatUIKit.loggedInUser;
if (loggedInUser) {
setIsLoggedIn(true);
}
} catch (error) {
console.log('Error during initialization', error);
} finally {
// Mark initialization as complete.
setIsInitializing(false);
}
}
init();
}, []);
/**
* Monitor app state changes to verify the logged-in status and clear notifications.
* When the app becomes active, it cancels Android notifications and checks the login status.
*/
useEffect(() => {
if (Platform.OS === 'android') {
// Request required Android permissions for notifications.
requestAndroidPermissions();
}
const handleAppStateChange = async (nextState: AppStateStatus) => {
if (nextState === 'active') {
try {
// Verify if there is a valid logged-in user.
const chatUser = await CometChat.getLoggedinUser();
setIsLoggedIn(!!chatUser);
} catch (error) {
console.log('Error verifying CometChat user on resume:', error);
}
}
};
const subscription = AppState.addEventListener(
'change',
handleAppStateChange,
);
return () => subscription.remove();
}, []);
/**
* Attach CometChat login listener to handle login and logout events.
* Updates user login status accordingly.
*/
useEffect(() => {
CometChat.addLoginListener(
listenerId,
new CometChat.LoginListener({
loginSuccess: () => {
setUserLoggedIn(true);
},
loginFailure: (e: CometChat.CometChatException) => {
console.log('LoginListener :: loginFailure', e.message);
},
logoutSuccess: () => {
setUserLoggedIn(false);
},
logoutFailure: (e: CometChat.CometChatException) => {
console.log('LoginListener :: logoutFailure', e.message);
},
}),
);
// Clean up the login listener on component unmount.
return () => {
CometChat.removeLoginListener(listenerId);
};
}, []);
/**
* Attach CometChat call listeners to handle incoming, outgoing, and cancelled call events.
* Also handles UI events for call end.
*/
useEffect(() => {
// Listener for call events.
CometChat.addCallListener(
listenerId,
new CometChat.CallListener({
onIncomingCallReceived: (call: CometChat.Call) => {
// Check if there's already an active call
try {
const activeCall = CometChat.getActiveCall();
if (activeCall) {
// If there's an active call, reject the incoming call with busy status
setTimeout(() => {
CometChat.rejectCall(
call.getSessionId(),
CometChat.CALL_STATUS.BUSY,
)
.then(() => {
console.log('Incoming call rejected due to active call');
})
.catch(error => {
console.error(
'Error rejecting call with busy status:',
error,
);
});
}, 2000);
} else {
// No active call, proceed with normal incoming call handling
// Hide any bottom sheet UI before showing the incoming call screen.
CometChatUIEventHandler.emitUIEvent(
CometChatUIEvents.ccToggleBottomSheet,
{
isBottomSheetVisible: false,
},
);
// Store the incoming call and update state.
incomingCall.current = call;
setCallReceived(true);
}
} catch (error) {
console.error('Error getting active call:', error);
// If error getting active call, proceed with normal handling
CometChatUIEventHandler.emitUIEvent(
CometChatUIEvents.ccToggleBottomSheet,
{
isBottomSheetVisible: false,
},
);
incomingCall.current = call;
setCallReceived(true);
}
},
onOutgoingCallRejected: () => {
// Clear the call state if outgoing call is rejected.
incomingCall.current = null;
setCallReceived(false);
},
onIncomingCallCancelled: () => {
// Clear the call state if the incoming call is cancelled.
incomingCall.current = null;
setCallReceived(false);
},
}),
);
// Additional listener to handle call end events.
CometChatUIEventHandler.addCallListener(listenerId, {
ccCallEnded: () => {
incomingCall.current = null;
setCallReceived(false);
},
});
// Remove call listeners on cleanup.
return () => {
CometChatUIEventHandler.removeCallListener(listenerId);
CometChat.removeCallListener(listenerId);
};
}, [userLoggedIn]);
// Show a blank/splash screen while the app is initializing.
if (isInitializing) {
return (
);
}
// Once initialization is complete, render the main app UI.
return (
{/* Render the incoming call UI if the user is logged in and a call is received */}
{isLoggedIn && callReceived && incomingCall.current ? (
{
// Handle call decline by clearing the incoming call state.
incomingCall.current = null;
setCallReceived(false);
}}
style={{
containerStyle: {
marginTop: Platform.OS === 'android' ? 40 : 0,
},
}}
/>
) : null}
{/* Render the main navigation stack, passing the login status as a prop */}
);
};
export default App;
================================================
FILE: examples/SampleAppAI/AppErrorBoundary.tsx
================================================
import React from 'react';
import { View, Text, StyleSheet, Appearance, Button } from 'react-native';
interface State {
hasError: boolean;
error: Error | null;
colorScheme: 'light' | 'dark' | null;
}
interface Props {
children: React.ReactNode;
}
class AppErrorBoundary extends React.Component {
constructor(props: Props) {
super(props);
const colorScheme = Appearance.getColorScheme() ?? null;
this.state = { hasError: false, error: null, colorScheme };
}
static getDerivedStateFromError(error: Error) {
// Update state so the next render shows the fallback UI.
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// Log error to an error reporting service here if needed.
console.log('ErrorBoundary caught an error', error, errorInfo);
}
componentDidMount() {
// Listen for changes in color scheme.
Appearance.addChangeListener(({ colorScheme }) => {
this.setState({ colorScheme: colorScheme ?? null });
});
}
// Reset error state to allow retrying
handleRetry = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
const styles = createStyles(this.state.colorScheme);
return (
Something went wrong
{/* Uncomment the next line to show the error message */}
{/*
{this.state.error ? this.state.error.toString() : 'Unknown error'}
*/}
);
}
return this.props.children;
}
}
const createStyles = (colorScheme: 'light' | 'dark' | null) => {
const isDark = colorScheme === 'dark';
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: isDark ? '#121212' : '#f2f2f2',
justifyContent: 'center',
alignItems: 'center',
padding: 16,
},
card: {
backgroundColor: isDark ? '#1e1e1e' : '#ffffff',
padding: 24,
borderRadius: 12,
alignItems: 'center',
shadowColor: isDark ? '#000000' : '#aaa',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 5,
width: '100%',
maxWidth: 400,
},
title: {
fontSize: 20,
fontWeight: '700',
marginBottom: 12,
color: isDark ? '#ffffff' : '#333333',
},
errorText: {
fontSize: 16,
textAlign: 'center',
color: isDark ? '#cccccc' : '#666666',
marginBottom: 20,
},
buttonContainer: {
width: '100%',
},
});
};
export default AppErrorBoundary;
================================================
FILE: examples/SampleAppAI/Gemfile
================================================
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby ">= 2.6.10"
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
gem 'xcodeproj', '< 1.26.0'
gem 'concurrent-ruby', '< 1.3.4'
# Ruby 3.4.0 has removed some libraries from the standard library.
gem 'bigdecimal'
gem 'logger'
gem 'benchmark'
gem 'mutex_m'
================================================
FILE: examples/SampleAppAI/README.md
================================================
# React Native AI Agents Sample App by CometChat
This reference application demonstrates how to seamlessly integrate CometChat’s [React Native UI Kit](https://www.cometchat.com/docs/ui-kit/react-native/5.0/overview) into a React Native project. It showcases the implementation of AI-powered chat agents using the UI Kit, highlighting how developers can leverage its prebuilt components to create rich, interactive messaging experiences with minimal effort.
## Prerequisites
Sign up for a [CometChat](https://app.cometchat.com/) account to obtain your app credentials: _`App ID`_, _`Region`_, and _`Auth Key`_
- **Node.js** 18 or higher
- **React Native** Version 0.77 or later (up to the latest version)
**iOS**
- XCode
- Pod (CocoaPods) for iOS
- An iOS device or emulator with iOS 12.0 or above.
- Ensure that you have configured the provisioning profile in Xcode to run the app on a physical device.
**Android**
- Android Studio
- Android device or emulator with Android version 5.0 or above.
## Installation
1. Clone the repository:
```sh
git clone https://github.com/cometchat/cometchat-uikit-react-native.git
```
1. Change into the specific app's directory (e.g., SampleAppAI).
```sh
cd examples/SampleAppAI
```
1. Run `npm install` to install the dependencies.
1. `[Optional]` Configure CometChat credentials:
- Open the `AppConstants.tsx` file located at `examples/SampleApp/src/utils/AppConstants.tsx` and enter your CometChat _`appId`_, _`region`_, and _`authKey`_:
```ts
export const AppConstants = {
appId: 'YOUR_APP_ID',
authKey: 'YOUR_AUTH_KEY',
region: 'REGION',
//other properties
}
```
1. For iOS, install dependencies after navigating to ios:
```sh
cd ios
pod install
```
1. Run the app on a device or emulator from the repo root.
```sh
npm start
npm run android
npm run ios
```
## Help and Support
For issues running the project or integrating with our UI Kits, consult our [documentation](https://www.cometchat.com/docs/ui-kit/react-native/5.0/getting-started) or create a [support ticket](https://help.cometchat.com/hc/en-us). You can also access real-time support via the [CometChat Dashboard](http://app.cometchat.com/).
================================================
FILE: examples/SampleAppAI/android/app/build.gradle
================================================
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '../..'
// root = file("../../")
// The folder where the react-native NPM package is. Default is ../../node_modules/react-native
// reactNativeDir = file("../../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
// codegenDir = file("../../node_modules/@react-native/codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js
// cliFile = file("../../node_modules/react-native/cli.js")
/* Variants */
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]
//
// The command to run when bundling. By default is 'bundle'
// bundleCommand = "ram-bundle"
//
// The path to the CLI configuration file. Default is empty.
// bundleConfig = file(../rn-cli.config.js)
//
// The name of the generated asset file containing your JS bundle
// bundleAssetName = "MyApplication.android.bundle"
//
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
// entryFile = file("../js/MyApplication.android.js")
//
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
//
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-O", "-output-source-map"]
/* Autolinking */
autolinkLibrariesWithApp()
}
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
android {
ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion
namespace "com.cometchat.ai.sampleapp"
defaultConfig {
applicationId "com.cometchat.ai.sampleapp"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "5.3.4"
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
implementation 'com.facebook.fresco:animated-gif:3.6.0' //gif android
}
================================================
FILE: examples/SampleAppAI/android/app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
================================================
FILE: examples/SampleAppAI/android/app/src/main/AndroidManifest.xml
================================================
================================================
FILE: examples/SampleAppAI/android/app/src/main/java/com/cometchat/ai/sampleapp/MainActivity.kt
================================================
package com.cometchat.ai.sampleapp
import android.os.Bundle
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
class MainActivity : ReactActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// REQUIRED for react-native-screens
super.onCreate(null)
}
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
*/
override fun getMainComponentName(): String = "ai_sample_app"
/**
* Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
* which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
*/
override fun createReactActivityDelegate(): ReactActivityDelegate =
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
}
================================================
FILE: examples/SampleAppAI/android/app/src/main/java/com/cometchat/ai/sampleapp/MainApplication.kt
================================================
package com.cometchat.ai.sampleapp
import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
}
override fun getJSMainModuleName(): String = "index"
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}
override val reactHost: ReactHost
get() = getDefaultReactHost(applicationContext, reactNativeHost)
override fun onCreate() {
super.onCreate()
loadReactNative(this)
}
}
================================================
FILE: examples/SampleAppAI/android/app/src/main/res/drawable/rn_edit_text_material.xml
================================================
================================================
FILE: examples/SampleAppAI/android/app/src/main/res/values/colors.xml
================================================
#FFFFFF
================================================
FILE: examples/SampleAppAI/android/app/src/main/res/values/strings.xml
================================================
AI Sample App
================================================
FILE: examples/SampleAppAI/android/app/src/main/res/values/styles.xml
================================================
================================================
FILE: examples/SampleAppAI/android/build.gradle
================================================
buildscript {
ext {
buildToolsVersion = "36.0.0"
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 36
ndkVersion = "27.1.12297006"
kotlinVersion = "2.1.20"
version = "V5.3.4"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.1.4")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
}
}
apply plugin: "com.facebook.react.rootproject"
================================================
FILE: examples/SampleAppAI/android/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: examples/SampleAppAI/android/gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew -PreactNativeArchitectures=x86_64
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=true
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=true
# Use this property to enable edge-to-edge display support.
# This allows your app to draw behind system bars for an immersive UI.
# Note: Only works with ReactActivity and should not be used with custom Activity.
edgeToEdgeEnabled=false
================================================
FILE: examples/SampleAppAI/android/gradlew
================================================
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
================================================
FILE: examples/SampleAppAI/android/gradlew.bat
================================================
@REM Copyright (c) Meta Platforms, Inc. and affiliates.
@REM
@REM This source code is licensed under the MIT license found in the
@REM LICENSE file in the root directory of this source tree.
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: examples/SampleAppAI/android/settings.gradle
================================================
pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
plugins { id("com.facebook.react.settings") }
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
rootProject.name = 'SampleApp'
include ':app'
includeBuild('../node_modules/@react-native/gradle-plugin')
================================================
FILE: examples/SampleAppAI/app.json
================================================
{
"name": "ai_sample_app",
"displayName": "AI Sample App"
}
================================================
FILE: examples/SampleAppAI/babel.config.js
================================================
module.exports = {
presets: ['module:@react-native/babel-preset'],
};
================================================
FILE: examples/SampleAppAI/gesture-handler.js
================================================
================================================
FILE: examples/SampleAppAI/gesture-handler.native.js
================================================
// Only import react-native-gesture-handler on native platforms
import 'react-native-gesture-handler';
================================================
FILE: examples/SampleAppAI/index.js
================================================
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import AppErrorBoundary from './AppErrorBoundary';
import {ActiveChatProvider} from './src/utils/ActiveChatContext';
if (global?.ErrorUtils) {
const defaultHandler = global.ErrorUtils.getGlobalHandler();
function globalErrorHandler(error, isFatal) {
console.log(
'[GlobalErrorHandler]:',
isFatal ? 'Fatal:' : 'Non-Fatal:',
error,
);
defaultHandler?.(error, isFatal);
}
global.ErrorUtils.setGlobalHandler(globalErrorHandler);
}
if (typeof process === 'object' && process.on) {
process.on('unhandledRejection', (reason, promise) => {
console.log('[Unhandled Promise Rejection]:', reason);
});
}
const Root = () => (
);
AppRegistry.registerComponent(appName, () => Root);
================================================
FILE: examples/SampleAppAI/ios/.xcode.env
================================================
# This `.xcode.env` file is versioned and is used to source the environment
# used when running script phases inside Xcode.
# To customize your local environment, you can create an `.xcode.env.local`
# file that is not versioned.
# NODE_BINARY variable contains the PATH to the node executable.
#
# Customize the NODE_BINARY variable here.
# For example, to use nvm with brew, add the following line
# . "$(brew --prefix nvm)/nvm.sh" --no-use
export NODE_BINARY=$(command -v node)
================================================
FILE: examples/SampleAppAI/ios/Podfile
================================================
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
"react-native/scripts/react_native_pods.rb",
{paths: [process.argv[1]]},
)', __dir__]).strip
platform :ios, min_ios_version_supported
prepare_react_native_project!
# Specify the Xcode project
project 'SampleApp.xcodeproj'
use_frameworks! :linkage => :static
target 'SampleApp' do
config = use_native_modules!
pod 'SPTPersistentCache', :modular_headers => true
pod 'DVAssetLoaderDelegate', :modular_headers => true
use_react_native!(
:path => config[:reactNativePath],
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
post_install do |installer|
# https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false,
# :ccache_enabled => true
)
# Fix fmt consteval compilation error with Xcode 26+
# https://github.com/expo/expo/issues/44229
fmt_base = File.join(installer.sandbox.pod_dir('fmt'), 'include', 'fmt', 'base.h')
if File.exist?(fmt_base)
content = File.read(fmt_base)
patched = content.gsub(/^#\s*define FMT_USE_CONSTEVAL 1$/, '# define FMT_USE_CONSTEVAL 0')
if patched != content
File.chmod(0644, fmt_base)
File.write(fmt_base, patched)
end
end
end
end
================================================
FILE: examples/SampleAppAI/ios/SampleApp/AppDelegate.swift
================================================
import UIKit
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var reactNativeDelegate: ReactNativeDelegate?
var reactNativeFactory: RCTReactNativeFactory?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
let delegate = ReactNativeDelegate()
let factory = RCTReactNativeFactory(delegate: delegate)
delegate.dependencyProvider = RCTAppDependencyProvider()
reactNativeDelegate = delegate
reactNativeFactory = factory
window = UIWindow(frame: UIScreen.main.bounds)
factory.startReactNative(
withModuleName: "ai_sample_app",
in: window,
launchOptions: launchOptions
)
return true
}
}
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
override func sourceURL(for bridge: RCTBridge) -> URL? {
self.bundleURL()
}
override func bundleURL() -> URL? {
#if DEBUG
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
}
================================================
FILE: examples/SampleAppAI/ios/SampleApp/Images.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"filename" : "CometChat_React_Native_40x40.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "CometChat_React_Native_60x60.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "CometChat_React_Native_58x58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "CometChat_React_Native_87x87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "CometChat_React_Native_80x80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "CometChat_React_Native_120x120 1.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "CometChat_React_Native_120x120.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "CometChat_React_Native_180x180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "CometChat React native.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: examples/SampleAppAI/ios/SampleApp/Images.xcassets/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: examples/SampleAppAI/ios/SampleApp/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleDisplayName
AI Sample App
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
APPL
CFBundleShortVersionString
$(MARKETING_VERSION)
CFBundleSignature
????
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
LSRequiresIPhoneOS
NSAppTransportSecurity
NSAllowsArbitraryLoads
NSAllowsLocalNetworking
NSCameraUsageDescription
This is for Camera permission
NSLocationWhenInUseUsageDescription
NSMicrophoneUsageDescription
This is for Mic permission
RCTNewArchEnabled
UILaunchStoryboardName
LaunchScreen
UIRequiredDeviceCapabilities
arm64
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UIViewControllerBasedStatusBarAppearance
================================================
FILE: examples/SampleAppAI/ios/SampleApp/LaunchScreen.storyboard
================================================
================================================
FILE: examples/SampleAppAI/ios/SampleApp/PrivacyInfo.xcprivacy
================================================
NSPrivacyAccessedAPITypes
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategoryFileTimestamp
NSPrivacyAccessedAPITypeReasons
C617.1
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategoryUserDefaults
NSPrivacyAccessedAPITypeReasons
CA92.1
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategorySystemBootTime
NSPrivacyAccessedAPITypeReasons
35F9.1
NSPrivacyCollectedDataTypes
NSPrivacyTracking
================================================
FILE: examples/SampleAppAI/ios/SampleApp.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
3FB2022EB49561EBA8A32491 /* Pods_SampleApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 578B0B2D70ABC3C90153065B /* Pods_SampleApp.framework */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
BDCB18E08DA1EE3CCF31234A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1080499FF01EB6C0E2E655F6 /* Pods-SampleApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleApp.debug.xcconfig"; path = "Target Support Files/Pods-SampleApp/Pods-SampleApp.debug.xcconfig"; sourceTree = ""; };
13B07F961A680F5B00A75B9A /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = SampleApp/Images.xcassets; sourceTree = ""; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = SampleApp/Info.plist; sourceTree = ""; };
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = SampleApp/PrivacyInfo.xcprivacy; sourceTree = ""; };
24B03C26D609B5A540B05A65 /* Pods-SampleApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleApp.release.xcconfig"; path = "Target Support Files/Pods-SampleApp/Pods-SampleApp.release.xcconfig"; sourceTree = ""; };
578B0B2D70ABC3C90153065B /* Pods_SampleApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SampleApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = SampleApp/AppDelegate.swift; sourceTree = ""; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = SampleApp/LaunchScreen.storyboard; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3FB2022EB49561EBA8A32491 /* Pods_SampleApp.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
13B07FAE1A68108700A75B9A /* SampleApp */ = {
isa = PBXGroup;
children = (
13B07FB51A68108700A75B9A /* Images.xcassets */,
761780EC2CA45674006654EE /* AppDelegate.swift */,
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,
);
name = SampleApp;
sourceTree = "";
};
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
578B0B2D70ABC3C90153065B /* Pods_SampleApp.framework */,
);
name = Frameworks;
sourceTree = "";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
);
name = Libraries;
sourceTree = "";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
13B07FAE1A68108700A75B9A /* SampleApp */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
BBD78D7AC51CEA395F1C20DB /* Pods */,
);
indentWidth = 2;
sourceTree = "";
tabWidth = 2;
usesTabs = 0;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* SampleApp.app */,
);
name = Products;
sourceTree = "";
};
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
1080499FF01EB6C0E2E655F6 /* Pods-SampleApp.debug.xcconfig */,
24B03C26D609B5A540B05A65 /* Pods-SampleApp.release.xcconfig */,
);
path = Pods;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
13B07F861A680F5B00A75B9A /* SampleApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "SampleApp" */;
buildPhases = (
93A7E3EE15C66A560927690C /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
B26D7914AEF39D3B5D13EA59 /* [CP] Embed Pods Frameworks */,
313738ECC7D8394BE4D3F3F3 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = SampleApp;
productName = SampleApp;
productReference = 13B07F961A680F5B00A75B9A /* SampleApp.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1210;
TargetAttributes = {
13B07F861A680F5B00A75B9A = {
LastSwiftMigration = 1120;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "SampleApp" */;
compatibilityVersion = "Xcode 12.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* SampleApp */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
BDCB18E08DA1EE3CCF31234A /* PrivacyInfo.xcprivacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"$(SRCROOT)/.xcode.env.local",
"$(SRCROOT)/.xcode.env",
);
name = "Bundle React Native code and images";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
};
313738ECC7D8394BE4D3F3F3 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources.sh\"\n";
showEnvVarsInLog = 0;
};
93A7E3EE15C66A560927690C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-SampleApp-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
B26D7914AEF39D3B5D13EA59 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
13B07F871A680F5B00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1080499FF01EB6C0E2E655F6 /* Pods-SampleApp.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = T9F7QDSC2S;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = SampleApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = V5.3.4;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.cometchat.ai.sampleapp;
PRODUCT_NAME = SampleApp;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 24B03C26D609B5A540B05A65 /* Pods-SampleApp.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = T9F7QDSC2S;
INFOPLIST_FILE = SampleApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = V5.3.4;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.cometchat.ai.sampleapp;
PRODUCT_NAME = SampleApp;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
);
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"\"$(SDKROOT)/usr/lib/swift\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
USE_HERMES = true;
};
name = Debug;
};
83CBBA211A601CBA00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
);
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"\"$(SDKROOT)/usr/lib/swift\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "SampleApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */,
13B07F951A680F5B00A75B9A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "SampleApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,
83CBBA211A601CBA00E9B192 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}
================================================
FILE: examples/SampleAppAI/ios/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme
================================================
================================================
FILE: examples/SampleAppAI/ios/SampleApp.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: examples/SampleAppAI/ios/SampleApp.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
================================================
FILE: examples/SampleAppAI/jest.config.js
================================================
module.exports = {
preset: 'react-native',
};
================================================
FILE: examples/SampleAppAI/metro.config.js
================================================
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
/**
* Metro configuration
* https://reactnative.dev/docs/metro
*
* @type {import('@react-native/metro-config').MetroConfig}
*/
const config = {};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
================================================
FILE: examples/SampleAppAI/package.json
================================================
{
"name": "ai_sample_app",
"version": "5.3.4",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest"
},
"dependencies": {
"@cometchat/calls-sdk-react-native": "^4.4.0",
"@cometchat/chat-sdk-react-native": "^4.0.21",
"@cometchat/chat-uikit-react-native": "^5.3.4",
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-clipboard/clipboard": "^1.16.3",
"@react-native-community/datetimepicker": "^8.4.5",
"@react-native-community/netinfo": "^11.4.1",
"@react-native/new-app-screen": "0.81.4",
"@react-navigation/bottom-tabs": "^7.4.7",
"@react-navigation/native": "^7.1.17",
"@react-navigation/native-stack": "^7.14.12",
"@react-navigation/stack": "^7.4.8",
"dayjs": "^1.11.18",
"react": "19.1.0",
"react-native": "0.81.4",
"react-native-background-timer": "^2.4.1",
"react-native-callstats": "^3.73.22",
"react-native-gesture-handler": "^2.28.0",
"react-native-localize": "^3.5.4",
"react-native-safe-area-context": "^5.5.2",
"react-native-screens": "^4.16.0",
"react-native-svg": "^15.13.0",
"react-native-video": "^6.16.1",
"react-native-webrtc": "^124.0.6"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli": "20.0.0",
"@react-native-community/cli-platform-android": "20.0.0",
"@react-native-community/cli-platform-ios": "20.0.0",
"@react-native/babel-preset": "0.81.4",
"@react-native/eslint-config": "0.81.4",
"@react-native/metro-config": "0.81.4",
"@react-native/typescript-config": "0.81.4",
"@types/jest": "^29.5.13",
"@types/react": "^19.1.0",
"@types/react-test-renderer": "^19.1.0",
"eslint": "^8.19.0",
"jest": "^29.6.3",
"prettier": "2.8.8",
"react-test-renderer": "19.1.0",
"typescript": "^5.8.3"
},
"engines": {
"node": ">=20"
}
}
================================================
FILE: examples/SampleAppAI/src/assets/icons/AccountCircle.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/AddComment.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/ArrowBack.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/Block.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/Call.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/CallFill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/Chat.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/Chatfill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/CheckFill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/Close.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/Delete.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/Group.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/GroupAdd.tsx
================================================
import type { SvgProps } from "react-native-svg";
import Svg, { Path } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/GroupFill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/Info.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/InfoIcon.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/Logout.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/Person.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/PersonAdd.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/PersonFill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/PersonOff.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/UserEmptyIcon.tsx
================================================
import type { SvgProps } from "react-native-svg";
import Svg, { G, Mask, Path, Rect } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/assets/icons/VideoCam.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppAI/src/components/AIAgents.tsx
================================================
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {CometChatUsers, useTheme, CometChatUIKit, CometChatAvatar} from '@cometchat/chat-uikit-react-native';
import React, {useLayoutEffect, useState, useRef, useCallback, useContext} from 'react';
import {StyleSheet, TouchableOpacity, View} from 'react-native';
import {useNavigation, CommonActions} from '@react-navigation/native';
import {RootStackParamList} from '../navigation/types';
import {StackNavigationProp} from '@react-navigation/stack';
import {TooltipMenu} from '../utils/TooltipMenu';
import {AppConstants} from '../utils/AppConstants';
import Logout from '../assets/icons/Logout';
import Info from '../assets/icons/Info';
import {AuthContext} from '../navigation/AuthContext';
import {navigationRef} from '../navigation/NavigationService';
import AccountCircle from '../assets/icons/AccountCircle';
type AIAgentNavigationProp = StackNavigationProp;
const AppBarOptions: React.FC<{
onPress: () => void;
loggedInUser: CometChat.User;
avatarContainerRef: React.RefObject;
}> = ({ onPress, loggedInUser, avatarContainerRef }) => {
return (
);
};
const AIAgents: React.FC = () => {
const theme = useTheme();
const navigation = useNavigation();
const {setIsLoggedIn: setLogout} = useContext(AuthContext);
const [tooltipVisible, setTooltipVisible] = useState(false);
const [isLoggingOut, setIsLoggingOut] = useState(false);
const tooltipPosition = useRef({pageX: 0, pageY: 0});
const avatarContainerRef = useRef(null);
const loggedInUser = useRef(
CometChatUIKit.loggedInUser!,
).current;
// Configure header with back button
useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
title: 'AI Assistants',
headerStyle: {
backgroundColor: theme.color.background1,
},
headerTitleStyle: {
color: theme.color.textPrimary,
},
});
}, [navigation, theme]);
const handleUserPress = (user: CometChat.User) => {
navigation.navigate('Messages', { user });
};
const handleAvatarPress = useCallback(() => {
try {
if (avatarContainerRef.current) {
avatarContainerRef.current.measureInWindow((x, y, width, height) => {
// Add spacing from the avatar - move tooltip to the right and down
tooltipPosition.current = {
pageX: x + width + 20, // 20px to the right of the avatar
pageY: y + height + 10 // 10px below the avatar
};
});
setTooltipVisible(true);
}
} catch (error) {
console.error('Error while handling avatar press:', error);
}
}, [avatarContainerRef, tooltipPosition, setTooltipVisible]);
const handleLogout = async () => {
if (isLoggingOut) return;
setIsLoggingOut(true);
// Step 1: Logout from CometChat
try {
await CometChat.logout();
} catch (error) {
console.error('CometChat logout failed:', error);
setIsLoggingOut(false);
return; // Exit if CometChat logout fails
}
// If all operations succeed, navigate to the LoginScreen
setIsLoggingOut(false);
setLogout(false);
// navigate('Login');
navigationRef.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'SampleUser' }],
}),
);
};
const AppBarWrapper = React.useMemo(
() => () => (
),
[handleAvatarPress, loggedInUser, avatarContainerRef]
);
return (
{
setTooltipVisible(false);
}}
onDismiss={() => {
setTooltipVisible(false);
}}
event={{
nativeEvent: tooltipPosition.current,
}}
menuItems={[
{
text: loggedInUser?.getName() || 'User',
onPress: () => {
setTooltipVisible(false);
},
icon: (
),
textColor: theme.color.textPrimary,
iconColor: theme.color.textPrimary,
},
{
text: AppConstants.versionNumber,
onPress: () => {},
icon: (
),
},
{
text: 'Logout',
onPress: () => {
handleLogout();
},
icon: (
),
textColor: theme.color.error,
iconColor: theme.color.error,
},
]}
/>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
appBarWrapper: {
marginRight: 15,
},
});
export default AIAgents;
================================================
FILE: examples/SampleAppAI/src/components/Messages.tsx
================================================
import React, {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
TouchableOpacity,
View,
StyleSheet,
Text,
BackHandler,
Platform,
Modal,
Animated,
Dimensions,
} from 'react-native';
import {
CometChatUIKit,
CometChatMessageHeader,
CometChatMessageList,
CometChatCompactMessageComposer,
useTheme,
CometChatUIEventHandler,
CometChatUIEvents,
CometChatAIAssistantChatHistory,
CometChatAIAssistantTools,
CometChatThemeProvider,
useCometChatTranslation
} from '@cometchat/chat-uikit-react-native';
import {StackScreenProps} from '@react-navigation/stack';
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {CommonUtils} from '../utils/CommonUtils';
import {useActiveChat} from '../utils/ActiveChatContext';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {RootStackParamList} from '../navigation/types';
const {width} = Dimensions.get('window');
type Props = StackScreenProps;
const Messages: React.FC = ({route, navigation}) => {
const {user, group, parentMessageId: routeParentMessageId} = route.params;
const loggedInUser = useRef(
CometChatUIKit.loggedInUser!,
).current;
const theme = useTheme();
const { t } = useCometChatTranslation();
const themeRef = useRef(theme);
const navigationRef = useRef(navigation);
const routeRef = useRef(route);
const userListenerId = 'app_messages' + new Date().getTime();
const openmessageListenerId = 'message_' + new Date().getTime();
const [localUser, setLocalUser] = useState(user);
const [messageListKey, setMessageListKey] = useState(0);
const [messageComposerKey, setMessageComposerKey] = useState(0);
const [showHistoryModal, setShowHistoryModal] = useState(false);
// Manage parentMessageId in parent component
const [parentMessageId, setParentMessageId] = useState(routeParentMessageId);
const {setActiveChat} = useActiveChat();
const insets = useSafeAreaInsets();
// Add ref to track streaming state
const messageComposerRef = useRef(null);
/** Animation state for drawer */
const slideAnim = useRef(new Animated.Value(width)).current;
useEffect(() => {
if (showHistoryModal) {
Animated.timing(slideAnim, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start();
} else {
Animated.timing(slideAnim, {
toValue: width,
duration: 300,
useNativeDriver: true,
}).start();
}
}, [showHistoryModal, slideAnim]);
/** Agentic user check */
const isAgenticUser = useCallback((): boolean => {
if (localUser) {
return localUser.getRole?.() === '@agentic';
}
return false;
}, [localUser]);
const agentic = isAgenticUser();
/** Active chat setup */
useEffect(() => {
// if it's a user chat
if (user) {
setActiveChat({type: 'user', id: user.getUid()});
} else if (group) {
setActiveChat({type: 'group', id: group.getGuid()});
}
// Cleanup on unmount => setActiveChat(null)
return () => {
setActiveChat(null);
// Reset streaming state when leaving chat
if (messageComposerRef.current?.resetStreaming) {
messageComposerRef.current.resetStreaming();
}
};
}, [user, group, setActiveChat]);
useEffect(() => {
themeRef.current = theme;
navigationRef.current = navigation;
routeRef.current = route;
}, [theme, navigation, route]);
const toggleTab = useCallback(() => {
// Simple toggle function without dependency on external toggleBottomTab
return () => {};
}, []);
useLayoutEffect(() => {
const cleanup = toggleTab();
return () => {
cleanup();
};
}, [toggleTab]);
useEffect(() => {
const backAction = () => {
// Reset streaming state when navigating back
if (messageComposerRef.current?.resetStreaming) {
messageComposerRef.current.resetStreaming();
}
navigation.popToTop();
return true;
};
// Add event listener for hardware back press
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
backAction,
);
return () => backHandler.remove();
}, [navigation]);
useEffect(() => {
CometChatUIEventHandler.addUserListener(userListenerId, {
ccUserBlocked: (item: {user: CometChat.User}) =>
handleccUserBlocked(item),
ccUserUnBlocked: (item: {user: CometChat.User}) =>
handleccUserUnBlocked(item),
});
const statusListenerId = 'user_status_messages_' + new Date().getTime();
if (localUser) {
CometChat.addUserListener(
statusListenerId,
new CometChat.UserListener({
onUserOnline: (onlineUser: CometChat.User) => {
if (onlineUser.getUid() === localUser.getUid()) {
console.log('🚀 ~ onUserOnline ~ onlineUser:', onlineUser);
setLocalUser(onlineUser);
}
},
onUserOffline: (offlineUser: CometChat.User) => {
console.log('🚀 ~ onUserOffline ~ offlineUser:', offlineUser);
if (offlineUser.getUid() === localUser.getUid()) {
setLocalUser(offlineUser);
}
},
}),
);
}
CometChatUIEventHandler.addUIListener(openmessageListenerId, {
openChat: ({user}) => {
if (user != undefined) {
navigation.push('Messages', {
user,
});
}
},
});
return () => {
CometChatUIEventHandler.removeUserListener(userListenerId);
CometChat.removeUserListener(statusListenerId);
CometChatUIEventHandler.removeUIListener(openmessageListenerId);
};
}, [localUser]);
const handleccUserBlocked = ({user}: {user: CometChat.User}) => {
setLocalUser(CommonUtils.clone(user));
};
const handleccUserUnBlocked = ({user}: {user: CometChat.User}) => {
setLocalUser(CommonUtils.clone(user));
};
const unblock = async (userToUnblock: CometChat.User) => {
let uid = userToUnblock.getUid();
try {
const response = await CometChat.unblockUsers([uid]);
const unBlockedUser = await CometChat.getUser(uid);
if (response) {
setLocalUser(unBlockedUser);
// Optionally emit an event or let the server call do the job
CometChatUIEventHandler.emitUserEvent(
CometChatUIEvents.ccUserUnBlocked,
{
user: unBlockedUser,
},
);
}
} catch (error) {
console.error('Error unblocking user:', error);
}
};
/** Reset messages */
const handleNewChatClick = useCallback(() => {
if (messageComposerRef.current?.resetStreaming) {
messageComposerRef.current.resetStreaming();
}
setParentMessageId(undefined);
setMessageListKey(prev => prev + 1);
setMessageComposerKey(prev => prev + 1);
setShowHistoryModal(false);
navigation.replace('Messages', {
user,
group,
});
}, [navigation, user, group]);
/** Open chat history modal */
const handleChatHistoryClick = useCallback(() => {
setShowHistoryModal(true);
}, []);
/** Handle history message click */
const handleHistoryMessageClick = useCallback(
(message: CometChat.BaseMessage) => {
if (messageComposerRef.current && messageComposerRef.current.stopStreaming) {
messageComposerRef.current.stopStreaming();
}
setShowHistoryModal(false);
setParentMessageId(message.getId().toString());
setMessageListKey(prev => prev + 1);
setMessageComposerKey(prev => prev + 1);
},
[],
);
/** Handle history error */
const handleChatHistoryError = useCallback(
(_error: CometChat.CometChatException) => {},
[],
);
/** Theme override for agentic outgoing bubble */
const providerTheme = useMemo(() => {
const defaultOutgoingBg =
theme?.messageListStyles?.outgoingMessageBubbleStyles?.containerStyle?.backgroundColor;
const defaultTextColor =
theme?.messageListStyles?.outgoingMessageBubbleStyles?.textBubbleStyles?.textStyle?.color;
const outgoingOverride = {
messageComposerStyles: {
containerStyle: {
backgroundColor: agentic
? theme.color.background3
: theme?.messageComposerStyles?.containerStyle?.backgroundColor,
},
},
messageListStyles: {
outgoingMessageBubbleStyles: {
containerStyle: {
backgroundColor: agentic
? theme.color.background4
: defaultOutgoingBg,
},
textBubbleStyles: {
textStyle: {
color: agentic
? theme.color.textPrimary
: defaultTextColor,
},
},
dateStyles: {
textStyle: {
color: agentic
? theme.color.textSecondary
: defaultTextColor,
},
},
},
},
};
return {
light: outgoingOverride,
dark: outgoingOverride,
mode: "auto" as "auto",
};
}, [agentic, theme]);
return (
navigation.popToTop()}
showBackButton
hideChatHistoryButton={false}
hideNewChatButton={false}
onChatHistoryButtonClick={handleChatHistoryClick}
onNewChatButtonClick={handleNewChatClick}
/>
console.log('Weather args', args),
})}
streamingSpeed={10}
/>
{/* Chat History Drawer */}
{agentic && (
setShowHistoryModal(false)}>
setShowHistoryModal(false)}
onMessageClicked={handleHistoryMessageClick}
onError={handleChatHistoryError}
onNewChatButtonClick={handleNewChatClick}
/>
)}
{localUser?.getBlockedByMe() ? (
{t('BLOCKED_USER_DESC')}
unblock(localUser)}
style={[styles.button, { borderColor: theme.color.borderDefault }]}
>
{t('UNBLOCK')}
) : (
)}
);
};
const styles = StyleSheet.create({
flexOne: {
flex: 1,
},
blockedContainer: {
alignItems: 'center',
height: 90,
paddingVertical: 10,
},
button: {
flex: 1,
justifyContent: 'center',
borderWidth: 2,
width: '90%',
borderRadius: 8,
},
buttontext: {
paddingVertical: 5,
textAlign: 'center',
alignContent: 'center',
},
appBarContainer: {
flexDirection: 'row',
marginLeft: 16,
},
});
const drawerStyles = StyleSheet.create({
backdrop: {
justifyContent: 'flex-start',
alignItems: 'flex-end',
},
drawer: {
width: '100%',
height: '100%',
overflow: 'hidden',
},
});
export default Messages;
================================================
FILE: examples/SampleAppAI/src/components/login/AppCredentials.tsx
================================================
import React, {useState} from 'react';
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity,
Image,
useColorScheme,
Platform,
Dimensions,
SafeAreaView,
ScrollView,
KeyboardAvoidingView,
BackHandler,
} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {
CometChatUIKit,
UIKitSettings,
useCometChatTranslation,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import {navigate, navigationRef} from '../../navigation/NavigationService';
import {SCREEN_CONSTANTS} from '../../utils/AppConstants';
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {useFocusEffect} from '@react-navigation/native';
const AppCredentials: React.FC = () => {
const [storedAppId, setStoredAppId] = useState('');
const [storedAuthKey, setStoredAuthKey] = useState('');
const [storedRegion, setStoredRegion] = useState('US');
// These are the *editable* states bound to the TextInput fields
const [appId, setAppId] = useState('');
const [authKey, setAuthKey] = useState('');
const [selectedRegion, setSelectedRegion] = useState('US');
// Toast state for showing error messages
const [toastMessage, setToastMessage] = useState(null);
const theme = useTheme();
const { t } = useCometChatTranslation();
const {width} = Dimensions.get('window');
const mode = useColorScheme();
// Compute if form is valid (all fields provided)
const isFormValid =
appId.trim().length > 0 &&
authKey.trim().length > 0 &&
selectedRegion.trim().length > 0;
// Load existing credentials if any
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
navigationRef.goBack();
return true; // Prevent default behavior
};
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
async function loadCredentials() {
try {
const credentialsStr = await AsyncStorage.getItem('appCredentials');
if (credentialsStr) {
const credentials = JSON.parse(credentialsStr);
// Update 'stored' states
setStoredAppId(credentials.appId || '');
setStoredAuthKey(credentials.authKey || '');
setStoredRegion(credentials.region || 'US');
// Also set the fields so user sees them pre-populated
setAppId(credentials.appId || '');
setAuthKey(credentials.authKey || '');
setSelectedRegion(credentials.region || 'US');
}
} catch (error) {
console.log('Error loading stored credentials:', error);
}
}
loadCredentials();
return () => backHandler.remove();
}, []),
);
const showToast = (message: string) => {
setToastMessage(message);
setTimeout(() => {
setToastMessage(null);
}, 2500);
};
const handleContinue = async (): Promise => {
// Validate the inputs.
// If the user has modified a field and cleared it (i.e. the input becomes empty),
// show a toast message.
if (!appId.trim()) {
showToast('Please enter App ID');
return;
}
if (!authKey.trim()) {
showToast('Please enter Auth Key');
return;
}
if (!selectedRegion.trim()) {
showToast('Please select a region');
return;
}
// Since all fields are non-empty, use their current values.
const newAppId = appId.trim();
const newAuthKey = authKey.trim();
const newRegion = selectedRegion.trim();
try {
const credentials = {
region: newRegion,
appId: newAppId,
authKey: newAuthKey,
};
console.log('Saving credentials:', credentials);
await AsyncStorage.setItem('appCredentials', JSON.stringify(credentials));
// Re-initialize with updated credentials
await CometChatUIKit.init({
appId: newAppId,
authKey: newAuthKey,
region: newRegion,
subscriptionType: CometChat.AppSettings
.SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings['subscriptionType'],
});
} catch (error) {
console.error('Failed to save credentials', error);
}
// Navigate to the next screen.
navigate('BottomTabNavigator');
navigationRef.reset({
index: 0,
routes: [{name: SCREEN_CONSTANTS.SAMPLE_USER}],
});
};
return (
{/* Header/Logo */}
{/* Title */}
{t('APP_CREDENTIALS')}
{/* Region Selector */}
{t('REGION')}
{/* US */}
setSelectedRegion('US')}>
US
{/* EU */}
setSelectedRegion('EU')}>
EU
{/* IN */}
setSelectedRegion('IN')}>
IN
{/* App ID */}
APP ID
{/* Auth Key */}
Auth Key
{/* Continue Button */}
{t('CONTINUE')}
{/* Toast Message */}
{toastMessage && (
{toastMessage}
)}
);
};
export default AppCredentials;
const styles = StyleSheet.create({
container: {
flex: 1,
},
contentContainer: {
flex: 1,
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingBottom: 16,
},
scrollContent: {
paddingBottom: 16,
},
logoContainer: {
alignItems: 'center',
marginTop: Platform.OS === 'android' ? 30 : 50,
marginBottom: 20,
},
inputContainer: {
width: '100%',
marginTop: 20,
},
regionRow: {
flexDirection: 'row',
justifyContent: 'space-between',
},
flagContainer: {
width: '32%',
borderWidth: 2,
borderColor: 'transparent',
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
},
flagInnerContainer: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 10,
gap: 5,
},
flagImage: {
width: 30,
height: 30,
resizeMode: 'contain',
},
input: {
paddingHorizontal: 12,
paddingVertical: 10,
borderRadius: 8,
borderWidth: 1,
},
continueButton: {
borderRadius: 8,
paddingVertical: 12,
width: '100%',
},
toastContainer: {
position: 'absolute',
bottom: '8%',
left: 20,
right: 20,
backgroundColor: '#C73C3E',
padding: 6,
borderRadius: 8,
alignItems: 'center',
},
toastText: {
color: '#fff',
fontSize: 14,
},
});
================================================
FILE: examples/SampleAppAI/src/components/login/SampleUser.tsx
================================================
import React, {useEffect, useState} from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
TextInput,
Image,
SafeAreaView,
Dimensions,
useColorScheme,
Pressable,
ImageSourcePropType,
KeyboardAvoidingView,
Platform,
ScrollView,
ActivityIndicator,
} from 'react-native';
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {
CometChatAvatar,
CometChatUIKit,
Icon,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import Check from '../../assets/icons/CheckFill';
import {sampleData} from '../../utils/helper';
import {SCREEN_CONSTANTS} from '../../utils/AppConstants';
import {navigate, navigationRef} from '../../navigation/NavigationService';
import Skeleton from './Skeleton'
type GridItem = CometChat.User | {dummy: true};
const LoginScreen: React.FC = () => {
const [users, setUsers] = useState([]);
const [selectedUser, setSelectedUser] = useState(null);
const [userUID, setUserUID] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [loadingUsers, setLoadingUsers] = useState(true);
const theme = useTheme();
const mode = useColorScheme();
const {width} = Dimensions.get('window');
useEffect(() => {
(async function loadUsers(): Promise {
try {
setLoadingUsers(true);
const fetchedUsers = await fetchUsers();
setUsers(fetchedUsers);
} catch (error) {
console.error(error);
} finally {
setLoadingUsers(false);
}
})();
}, []);
const handleSelectUser = (user: CometChat.User): void => {
setSelectedUser(user.getUid());
setUserUID('');
};
const handleContinue = async () => {
if ((!selectedUser && !userUID.trim()) || isLoading) return;
setIsLoading(true);
const uid: string = userUID.trim() || selectedUser!;
try {
await CometChatUIKit.login({uid});
navigate('BottomTabNavigator');
navigationRef.reset({
index: 0,
routes: [{name: SCREEN_CONSTANTS.BOTTOM_TAB_NAVIGATOR}],
});
} catch (error: any) {
console.log('Login failed with exception:', error);
} finally {
setIsLoading(false);
}
};
/**
* Fetch users from a remote sample JSON file.
* Falls back to local sample data if there's an error.
*/
async function fetchUsers(): Promise {
try {
const response = await fetch(
'https://assets.cometchat.io/sampleapp/sampledata.json',
);
if (response.ok) {
const data = await response.json();
const fetchedUsers = data.users || [];
return fetchedUsers.map((user: any) => new CometChat.User(user));
} else {
throw new Error('Failed to load users');
}
} catch (error) {
console.error('Exception while fetching users:', error);
return await getDefaultUsers();
}
}
/**
* Get users from local sample data (used in case the remote fetch fails).
*/
async function getDefaultUsers(): Promise {
const localUsers = sampleData.users || [];
return localUsers.map((user: any) => new CometChat.User(user));
}
/**
* Returns the appropriate image source object for the avatar.
*/
const getAvatarSource = (
avatar: string | ImageSourcePropType,
): ImageSourcePropType => {
if (typeof avatar === 'string') {
if (avatar.startsWith('http://') || avatar.startsWith('https://')) {
return {uri: avatar};
}
}
return avatar as ImageSourcePropType;
};
// Show skeleton if the API is still loading or if no users are available.
const showSkeleton = loadingUsers || users.length === 0;
// Compute grid data only if users are available.
let gridData: GridItem[] = [];
if (users.length > 0) {
gridData = [...users];
const numColumns = 3;
const numberOfElementsLastRow = users.length % numColumns;
if (numberOfElementsLastRow !== 0) {
for (let i = 0; i < numColumns - numberOfElementsLastRow; i++) {
gridData.push({dummy: true});
}
}
}
const Loading = () => {
return (
);
};
return (
{/* App Logo */}
{/* Title */}
Log In
{/* Subtitle */}
Choose a Sample User
{/* Sample Users Grid */}
{showSkeleton ? (
) : (
{gridData.map((item, index) => {
// Render a blank view for dummy items
if ('dummy' in item && item.dummy) {
return ;
}
// Otherwise, render a user
const user = item as CometChat.User;
const isSelected = selectedUser === user.getUid();
const firstName = user.getName();
return (
handleSelectUser(user)}>
{/* Show the check icon ONLY if selected */}
{isSelected && (
}
/>
)}
{/* Display only the first name */}
{firstName}
{user.getUid()}
);
})}
)}
{/* Horizontal divider with "Or" in the middle */}
Or
{/* UID Input */}
Enter Your UID
{
setUserUID(text);
setSelectedUser(null);
}}
/>
{/* Bottom container with "Continue" button and "Change App Credentials" */}
{isLoading ? (
) : (
Continue
)}
Change
{
navigationRef.navigate(SCREEN_CONSTANTS.APP_CRED);
}}>
App Credentials
);
};
export default LoginScreen;
const styles = StyleSheet.create({
keyboardAvoidingContainer: {
flex: 1,
},
container: {
flex: 1,
justifyContent: 'space-between',
},
scrollContainer: {
flexGrow: 1,
paddingHorizontal: 16,
},
logoContainer: {
alignItems: 'center',
marginBottom: 16,
},
logInTitle: {
marginBottom: 16,
alignSelf: 'center',
},
subtitle: {
marginBottom: 6,
},
usersContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-around',
},
userCard: {
position: 'relative',
width: '30%',
borderRadius: 8,
paddingVertical: 16,
paddingHorizontal: 8,
marginBottom: 12,
alignItems: 'center',
overflow: 'hidden',
},
checkIconContainer: {
position: 'absolute',
top: 0,
right: 0,
borderBottomLeftRadius: 10,
borderTopRightRadius: 7,
width: '27%',
height: '22%',
backgroundColor: '#7367F0',
alignItems: 'center',
justifyContent: 'center',
zIndex: 2,
},
firstNameText: {
marginTop: 8,
textAlign: 'center',
},
uidText: {
marginTop: 4,
textAlign: 'center',
},
dividerRow: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 16,
gap: 10,
},
divider: {
flex: 1,
height: 1,
borderWidth: 0.5,
},
uidLabel: {
paddingBottom: 5,
},
uidInput: {
borderWidth: 1,
borderRadius: 8,
padding: 10,
marginBottom: 16,
},
bottomContainer: {
paddingBottom: 20,
paddingHorizontal: 16,
},
continueButton: {
paddingVertical: 12,
borderRadius: 6,
marginBottom: 12,
},
continueButtonText: {
alignSelf: 'center',
},
changeCredentialsWrapper: {
flexDirection: 'row',
alignItems: 'center',
gap: 5,
justifyContent: 'center',
},
changeCredentialsContainer: {
alignItems: 'center',
justifyContent: 'center',
},
userGridWrapper: {
minHeight: 240,
justifyContent: 'center',
alignItems: 'center',
},
});
================================================
FILE: examples/SampleAppAI/src/components/login/Skeleton.tsx
================================================
import React, { useEffect, useRef, useState } from "react";
import { Animated, Dimensions, Easing, StyleSheet, useColorScheme, View } from "react-native";
import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg";
// import { useThemeInternal } from "../../../theme/hook";
const { width: screenWidth } = Dimensions.get("window");
const SkeletonBox = ({ index, boxSize, gradientColors }: any) => {
// Determine if the box is the last in its row
const isLastInRow = (index + 1) % 3 === 0;
return (
);
};
export const Skeleton = () => {
const animatedValue = useRef(new Animated.Value(0)).current;
const [isLoading, setIsLoading] = useState(true);
const mode = useColorScheme();
// Define static colors
const color = {
staticBlack: "#000000",
staticWhite: "#FFFFFF",
};
// Define skeletonStyle based on the theme mode
const skeletonStyle =
mode === "light"
? {
linearGradientColors: ["#E8E8E8", "#F5F5F5"],
shimmerBackgroundColor: color.staticBlack,
shimmerOpacity: 0.01,
speed: 1,
}
: {
linearGradientColors: ["#383838", "#272727"],
shimmerBackgroundColor: color.staticWhite,
shimmerOpacity: 0.01,
speed: 1,
};
const { linearGradientColors, shimmerBackgroundColor, shimmerOpacity, speed } =
skeletonStyle;
useEffect(() => {
const startShimmer = () => {
animatedValue.setValue(0);
Animated.loop(
Animated.timing(animatedValue, {
toValue: 1,
duration: (1 / speed) * 1000,
easing: Easing.linear,
useNativeDriver: false,
})
).start();
};
startShimmer();
// Simulate a loading time of 3 seconds
const loadData = setTimeout(() => {
setIsLoading(false);
}, 3000);
return () => clearTimeout(loadData);
}, [animatedValue, speed]);
const shimmerTranslateX = animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [-screenWidth, screenWidth],
});
if (!isLoading) {
return null;
}
const boxSize = (screenWidth - 22) / 3;
const shimmerWidth = screenWidth;
const shimmerHeight = boxSize + 16;
return (
{new Array(6).fill(0).map((_, index) => (
))}
);
};
const styles = StyleSheet.create({
container: {
position: "relative",
borderRadius: 16,
overflow: "hidden",
},
grid: {
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "space-between",
},
skeletonBox: {
marginBottom: 10,
marginHorizontal: -10,
},
animatedShimmer: {
position: "absolute",
top: 0,
left: 0,
},
});
export default Skeleton;
================================================
FILE: examples/SampleAppAI/src/declarations.d.ts
================================================
declare module '*.png' {
const value: any;
export default value;
}
================================================
FILE: examples/SampleAppAI/src/navigation/AuthContext.tsx
================================================
import React from 'react';
export interface AuthContextProps {
isLoggedIn: boolean;
setIsLoggedIn: React.Dispatch>;
}
export const AuthContext = React.createContext({
isLoggedIn: false,
setIsLoggedIn: () => {},
});
================================================
FILE: examples/SampleAppAI/src/navigation/BottomTabNavigator.tsx
================================================
import React from 'react';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {SCREEN_CONSTANTS} from '../utils/AppConstants';
import AIAgents from '../components/AIAgents';
import {BottomTabParamList} from './types';
// Create the tab navigator.
const Tab = createBottomTabNavigator();
const BottomTabNavigator = () => {
return (
);
};
export default BottomTabNavigator;
================================================
FILE: examples/SampleAppAI/src/navigation/NavigationService.ts
================================================
import {createNavigationContainerRef} from '@react-navigation/native';
import {RootStackParamList} from './types';
export const navigationRef = createNavigationContainerRef();
interface PendingNavigation {
name: keyof RootStackParamList;
params?: any;
}
let pendingNavigation: PendingNavigation | null = null;
export function navigate(
name: RouteName,
params?: RootStackParamList[RouteName] extends undefined
? undefined
: RootStackParamList[RouteName],
) {
if (navigationRef.isReady()) {
// navigationRef.navigate(name as never);
navigationRef.navigate(name as any, params as any);
} else {
// Save the navigation intent for later processing
pendingNavigation = {name, params};
}
}
export function processPendingNavigation() {
if (pendingNavigation && navigationRef.isReady()) {
const {name, params} = pendingNavigation;
navigationRef.navigate(name as any, params as any);
pendingNavigation = null;
}
}
================================================
FILE: examples/SampleAppAI/src/navigation/RootStackNavigator.tsx
================================================
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { NavigationContainer, DefaultTheme } from '@react-navigation/native';
import BottomTabNavigator from './BottomTabNavigator';
import { SCREEN_CONSTANTS } from '../utils/AppConstants';
import { useTheme } from '@cometchat/chat-uikit-react-native';
import { RootStackParamList } from './types';
import { navigationRef, processPendingNavigation } from './NavigationService';
import SampleUser from '../components/login/SampleUser';
import AppCredentials from '../components/login/AppCredentials';
import { StatusBar, useColorScheme } from 'react-native';
import Messages from '../components/Messages';
type Props = {
isLoggedIn: boolean;
hasValidAppCredentials: boolean;
};
const Stack = createNativeStackNavigator();
const RootStackNavigator = ({ isLoggedIn, hasValidAppCredentials }: Props) => {
const theme = useTheme();
const NavigationTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: theme.color.background1 as string,
},
};
const isDark = useColorScheme() === 'dark';
const backgroundColor = theme.color.background2;
const barStyle = isDark ? 'light-content' : 'dark-content';
return (
<>
{
processPendingNavigation();
}}
theme={NavigationTheme}
>
{/* Auth Screens */}
{/* Tab Screens */}
>
);
};
export default RootStackNavigator;
================================================
FILE: examples/SampleAppAI/src/navigation/types.ts
================================================
import {NavigatorScreenParams} from '@react-navigation/native';
import {CometChat} from '@cometchat/chat-sdk-react-native';
export type RootStackParamList = {
Login: undefined;
BottomTabNavigator: NavigatorScreenParams;
AppCredentials: undefined;
SampleUser: undefined;
Messages: {
user?: CometChat.User;
group?: CometChat.Group;
fromMention?: boolean;
parentMessageId?: string;
};
};
export type BottomTabParamList = {
Agents: undefined;
};
================================================
FILE: examples/SampleAppAI/src/utils/ActiveChatContext.tsx
================================================
import React, { createContext, useContext, useState } from 'react';
type ActiveChat = {
type: 'user' | 'group';
id: string; // userUID or groupID
} | null;
type ActiveChatContextType = {
activeChat: ActiveChat;
setActiveChat: React.Dispatch>;
};
const ActiveChatContext = createContext(undefined);
export function ActiveChatProvider({ children }: { children: React.ReactNode }) {
const [activeChat, setActiveChat] = useState(null);
return (
{children}
);
}
// Hook for using in any component
export function useActiveChat() {
const context = useContext(ActiveChatContext);
if (!context) {
throw new Error('useActiveChat must be used within an ActiveChatProvider');
}
return context;
}
================================================
FILE: examples/SampleAppAI/src/utils/AppConstants.tsx
================================================
export const AppConstants = {
fcmProviderId: '',
apnsProviderId: '',
authKey: '',
appId: '',
region: '',
subscriptionType: 'ALL_USERS',
versionNumber: 'V5.3.4',
webClientId:
'',
iosClientId:
'',
};
export const SCREEN_CONSTANTS = {
LOGIN: 'Login',
APP_CRED: 'AppCredentials',
SAMPLE_USER: 'SampleUser',
BOTTOM_TAB_NAVIGATOR: 'BottomTabNavigator',
AGENTS: 'Agents',
MESSAGES: 'Messages',
} as const;
================================================
FILE: examples/SampleAppAI/src/utils/CommonUtils.ts
================================================
export class CommonUtils {
static clone(arg: T): T {
/*
If there are additional properties attached to a function or an array object other than the standard properties, those properties will be ignored
Cannot copy private properties (those that start with a "#" symbol inside a class block)
Functions are copied by reference
*/
if (typeof arg !== 'object' || !arg) {
return arg;
}
let res;
if (Array.isArray(arg)) {
// arg is an array, there's no hatch to fool the Array.isArray method, so lets create an array
res = [];
for (const value of arg) {
res.push(CommonUtils.clone(value));
}
return res as T;
} else {
// arg is an object
res = {};
const descriptor = Object.getOwnPropertyDescriptors(arg);
for (const k of Reflect.ownKeys(descriptor)) {
const curDescriptor = descriptor[k as any];
if (curDescriptor.hasOwnProperty('value')) {
// Property is a data property
Object.defineProperty(res, k, {
...curDescriptor,
value: CommonUtils.clone(curDescriptor['value']),
});
} else {
// Property is an accessor property
Object.defineProperty(res, k, curDescriptor);
}
}
Object.setPrototypeOf(res, Object.getPrototypeOf(arg));
}
return res as T;
}
}
================================================
FILE: examples/SampleAppAI/src/utils/TooltipMenu.tsx
================================================
import { Icon, useTheme } from "@cometchat/chat-uikit-react-native";
import { JSX, useMemo } from "react";
import {
ColorValue,
Dimensions,
ImageSourcePropType,
Modal,
StyleSheet,
Text,
TouchableOpacity,
TouchableWithoutFeedback,
View,
} from "react-native";
const { width: screenWidth, height: screenHeight } = Dimensions.get("window");
type CometChatTooltipMenuProps = {
visible?: boolean;
onDismiss?: () => void;
onClose?: () => void;
event: {
nativeEvent: {
pageX: number;
pageY: number;
};
};
menuItems: {
text: string;
onPress: () => void;
textColor?: ColorValue;
iconColor?: ColorValue;
icon?: ImageSourcePropType | JSX.Element;
}[];
};
export const TooltipMenu = (props: CometChatTooltipMenuProps) => {
const { visible = false, onDismiss = () => null, onClose = () => null, event, menuItems } = props;
const theme = useTheme();
const position = useMemo(() => {
let x = event.nativeEvent.pageX;
let y = event.nativeEvent.pageY;
const position: {
left?: number;
right?: number;
top?: number;
bottom?: number;
} = {};
if (x <= screenWidth / 3) {
position.left = x + 10;
} else {
position.right = 12;
}
if (y <= screenHeight / 2) {
position.top = y + 20;
} else if (y >= screenHeight / 2) {
position.bottom = Math.max(screenHeight - y + 10, 40);
}
return position;
}, [event]);
return (
{menuItems.map((item, i) => {
return (
{
item.onPress();
onClose();
}}
style={[
{
flexDirection: "row",
alignItems: "center",
paddingVertical: 10,
paddingHorizontal: 16,
gap: 8,
backgroundColor: theme.color.background1,
minWidth: 160,
},
i === 0
? {
borderTopLeftRadius: theme.spacing.radius.r2,
borderTopRightRadius: theme.spacing.radius.r2,
}
: {},
i === menuItems.length - 1
? {
borderBottomLeftRadius: theme.spacing.radius.r2,
borderBottomRightRadius: theme.spacing.radius.r2,
}
: {},
]}
>
{item.text}
);
})}
);
};
const tooltipStyles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: "transparent",
justifyContent: "center",
alignItems: "center",
},
menu: {
position: "absolute",
shadowOffset: {
width: 0,
height: 8,
},
shadowOpacity: 0.025,
shadowRadius: 4,
elevation: 3,
},
menuItem: {
fontSize: 16,
paddingVertical: 5,
},
});
================================================
FILE: examples/SampleAppAI/src/utils/helper.ts
================================================
import {Platform, PermissionsAndroid} from 'react-native';
import {CometChat} from '@cometchat/chat-sdk-react-native';
import Ironman from '../assets/icons/ironman.png';
import Captainamerica from '../assets/icons/captainamerica.png';
import Wolverine from '../assets/icons/wolverine.png';
import Spiderman from '../assets/icons/spiderman.png';
import Cyclops from '../assets/icons/cyclops.png';
import {
CometChatUIEventHandler,
CometChatUIEvents,
CometChatUIKit,
} from '@cometchat/chat-uikit-react-native';
import {
NavigationContainerRefWithCurrent,
StackActions,
} from '@react-navigation/native';
import {RootStackParamList} from '../navigation/types';
import {SCREEN_CONSTANTS} from './AppConstants';
interface Translations {
lastSeen: string;
minutesAgo: (minutes: number) => string;
hoursAgo: (hours: number) => string;
}
interface NotifeeData {
receiverType?: 'user' | 'group';
conversationId?: string;
sender?: string;
[key: string]: any;
}
/**
* Request common Android permissions (notifications, camera, etc.)
* Only needed on Android.
*/
export async function requestAndroidPermissions() {
if (Platform.OS !== 'android') return;
try {
await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.CAMERA,
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
]);
} catch (err) {
console.warn('Android permissions error:', err);
}
}
/**
* getLastSeenTime UserInfoSection.
*/
export function getLastSeenTime(
timestamp: number | null,
translations: Translations,
): string {
if (timestamp === null) {
return `${translations.lastSeen} Unknown`;
}
// If timestamp is in seconds (length = 10), convert to milliseconds.
if (String(timestamp).length === 10) {
timestamp *= 1000;
}
const now = new Date();
const lastSeen = new Date(timestamp);
// Calculate the time differences
const diffInMillis = now.getTime() - lastSeen.getTime();
const diffInMinutes = Math.floor(diffInMillis / (1000 * 60));
const diffInHours = Math.floor(diffInMillis / (1000 * 60 * 60));
// Check if within last hour
if (diffInMinutes === 0) {
return `${translations.lastSeen} ${translations.minutesAgo(1)}`;
} else if (diffInMinutes < 60) {
return `${translations.lastSeen} ${translations.minutesAgo(diffInMinutes)}`;
}
// Check if within the last 24 hours
if (diffInHours < 24) {
return `${translations.lastSeen} ${translations.hoursAgo(diffInHours)}`;
}
// Determine if timestamp is within the current year
const isSameYear = lastSeen.getFullYear() === now.getFullYear();
// Options for date formatting
const dateOptions: Intl.DateTimeFormatOptions = {
day: '2-digit',
month: 'short',
...(isSameYear ? {} : {year: 'numeric'}),
};
// Options for time formatting
const timeOptions: Intl.DateTimeFormatOptions = {
hour: '2-digit',
minute: '2-digit',
hour12: true,
};
const formattedDate = lastSeen.toLocaleDateString(undefined, dateOptions);
const formattedTime = lastSeen.toLocaleTimeString(undefined, timeOptions);
if (formattedDate === 'Invalid Date' || formattedTime === 'Invalid Date') {
return `Offline`;
}
return `${translations.lastSeen} ${formattedDate} at ${formattedTime}`;
}
/**
* UNBLOCK
*/
export const unblock = async (
uid: string,
user: CometChat.User,
setBlocked: React.Dispatch>,
setUserObj: React.Dispatch>,
): Promise => {
try {
const response = await CometChat.unblockUsers([uid]);
const unBlockedUser = await CometChat.getUser(uid);
if (response) {
CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserUnBlocked, {
user: unBlockedUser,
});
setBlocked(false);
setUserObj(unBlockedUser);
} else {
console.log(
`Failed to unblock user with UID ${uid}. Response:`,
response,
);
}
} catch (error) {
console.error('Error unblocking user:', error);
}
};
/**
* BLOCK
*/
export const blockUser = async (
uid: string,
user: CometChat.User,
setBlocked: React.Dispatch>,
): Promise => {
try {
const response = await CometChat.blockUsers([uid]);
if (response) {
user.setBlockedByMe(true);
setBlocked(true);
CometChatUIEventHandler.emitUserEvent(CometChatUIEvents.ccUserBlocked, {
user,
});
} else {
console.log(`Failed to block user with UID ${uid}. Response:`, response);
}
} catch (error) {
console.error('Error blocking user:', error);
}
};
/**
* LEAVE GROUP
*/
export const leaveGroup = (
group: CometChat.Group,
navigation: any,
pop: number,
) => {
if (group) {
const groupID = group.getGuid();
CometChat.leaveGroup(groupID).then(
() => {
let actionMessage: CometChat.Action = new CometChat.Action(
groupID,
CometChat.MESSAGE_TYPE.TEXT,
CometChat.RECEIVER_TYPE.GROUP,
CometChat.CATEGORY_ACTION as CometChat.MessageCategory,
);
actionMessage.setMessage(
`${CometChatUIKit.loggedInUser!.getName()} has left`,
);
// Initialize data to prevent crash when SDK accesses getData().metadata during render
actionMessage.setData({ metadata: {} });
CometChatUIEventHandler.emitGroupEvent(CometChatUIEvents.ccGroupLeft, {
message: actionMessage, //Note: Add Action message after discussion
leftUser: CometChatUIKit.loggedInUser,
leftGroup: group,
});
navigation.pop(pop);
},
error => {
console.log('Group leaving failed:', error);
},
);
} else {
console.log('Group is not defined');
}
};
/**
* Sample Users Data
*/
export const sampleData = {
users: [
{uid: 'superhero1', name: 'Iron Man', avatar: Ironman},
{uid: 'superhero2', name: 'Captain America', avatar: Captainamerica},
{uid: 'superhero3', name: 'Spiderman', avatar: Spiderman},
{uid: 'superhero4', name: 'Wolverine', avatar: Wolverine},
{uid: 'superhero5', name: 'Cyclops', avatar: Cyclops},
],
};
/**
* Navigate to conversation based on notification data.
*/
export async function navigateToConversation(
navigationRef: NavigationContainerRefWithCurrent,
data?: NotifeeData,
) {
if (!data) return;
if (!navigationRef.current) return;
try {
// Handle group
if (data.receiverType === 'group') {
const extractedId =
typeof data.conversationId === 'string'
? data.conversationId.split('_').slice(1).join('_')
: '';
const group = await CometChat.getGroup(extractedId);
navigationRef.current?.dispatch(StackActions.push(SCREEN_CONSTANTS.MESSAGES, {group}));
}
// Handle user
else if (data.receiverType === 'user') {
const ccUser = await CometChat.getUser(data.sender);
navigationRef.current?.dispatch(
StackActions.push(SCREEN_CONSTANTS.MESSAGES, {user: ccUser}),
);
}
} catch (error) {
console.log('Error in navigateToConversation:', error);
}
}
================================================
FILE: examples/SampleAppAI/tsconfig.json
================================================
{
"extends": "@react-native/typescript-config",
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["**/node_modules", "**/Pods"]
}
================================================
FILE: examples/SampleAppExpo/.gitattributes
================================================
*.pbxproj -text
================================================
FILE: examples/SampleAppExpo/.gitignore
================================================
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
expo-env.d.ts
# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
================================================
FILE: examples/SampleAppExpo/App.tsx
================================================
import "./gesture-handler";
import React, { useState, useEffect, useRef } from "react";
import {
Platform,
View,
PlatformColor,
AppState,
AppStateStatus,
} from "react-native";
import { enableScreens } from "react-native-screens";
enableScreens();
import {
CometChatI18nProvider,
CometChatIncomingCall,
CometChatTheme,
CometChatThemeProvider,
CometChatUIEventHandler,
CometChatUIEvents,
CometChatUIKit,
UIKitSettings,
} from "@cometchat/chat-uikit-react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { CometChat } from "@cometchat/chat-sdk-react-native";
import RootStackNavigator from "./src/navigation/RootStackNavigator";
import { AppConstants } from "./src/utils/AppConstants";
import { requestAndroidPermissions } from "./src/utils/helper";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { ActiveChatProvider } from "./src/utils/ActiveChatContext";
import { useConfig } from './src/config/store';
import { DeepPartial } from '@cometchat/chat-uikit-react-native/src/shared/helper/types';
import { createTypography } from './src/utils/themeTypography';
// Listener ID for registering and removing CometChat listeners.
const listenerId = "app";
const App = (): React.ReactElement => {
const [callReceived, setCallReceived] = useState(false);
const incomingCall = useRef(
null
);
const [isInitializing, setIsInitializing] = useState(true);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userLoggedIn, setUserLoggedIn] = useState(false);
const [currentToken, setCurrentToken] = useState("");
const [isTokenRegistered, setIsTokenRegistered] = useState(false);
const [hasValidAppCredentials, setHasValidAppCredentials] = useState(false);
const styleConfig = useConfig(state => state?.settings?.style);
const theme : { light: DeepPartial; dark: DeepPartial } = {
light: {
color: {
primary: styleConfig.color.brandColor,
textPrimary: styleConfig.color.primaryTextLight,
textSecondary: styleConfig.color.secondaryTextLight,
},
typography: createTypography(styleConfig.typography.font),
},
dark: {
color: {
primary: styleConfig.color.brandColor,
textPrimary: styleConfig.color.primaryTextDark,
textSecondary: styleConfig.color.secondaryTextDark,
},
typography: createTypography(styleConfig.typography.font),
},
};
/**
* Initialize CometChat UIKit and configure Google Sign-In.
* Retrieves credentials from AsyncStorage and uses fallback constants if needed.
*/
useEffect(() => {
async function init() {
try {
// Retrieve stored app credentials or default to an empty object.
const AppData = (await AsyncStorage.getItem("appCredentials")) || "{}";
const storedCredentials = JSON.parse(AppData);
// Determine the final credentials (from AsyncStorage or AppConstants).
const finalAppId = storedCredentials.appId || AppConstants.appId;
const finalAuthKey = storedCredentials.authKey || AppConstants.authKey;
const finalRegion = storedCredentials.region || AppConstants.region;
// Set hasValidAppCredentials based on whether all values are available.
if (finalAppId && finalAuthKey && finalRegion) {
setHasValidAppCredentials(true);
} else {
setHasValidAppCredentials(false);
}
await CometChatUIKit.init({
appId: finalAppId,
authKey: finalAuthKey,
region: finalRegion,
subscriptionType: CometChat.AppSettings
.SUBSCRIPTION_TYPE_ALL_USERS as UIKitSettings["subscriptionType"],
});
// If a user is already logged in, update the state.
const loggedInUser = CometChatUIKit.loggedInUser;
if (loggedInUser) {
setIsLoggedIn(true);
}
} catch (error) {
console.log("Error during initialization", error);
} finally {
// Mark initialization as complete.
setIsInitializing(false);
}
}
init();
}, []);
/**
* Monitor app state changes to verify the logged-in status and clear notifications.
* When the app becomes active, it cancels Android notifications and checks the login status.
*/
useEffect(() => {
if (Platform.OS === "android") {
// Request required Android permissions for notifications.
requestAndroidPermissions();
}
const handleAppStateChange = async (nextState: AppStateStatus) => {
if (nextState === "active") {
try {
// Verify if there is a valid logged-in user.
const chatUser = await CometChat.getLoggedinUser();
setIsLoggedIn(!!chatUser);
} catch (error) {
console.log("Error verifying CometChat user on resume:", error);
}
}
};
const subscription = AppState.addEventListener(
"change",
handleAppStateChange
);
return () => subscription.remove();
}, []);
/**
* Attach CometChat login listener to handle login and logout events.
* Updates user login status accordingly.
*/
useEffect(() => {
CometChat.addLoginListener(
listenerId,
new CometChat.LoginListener({
loginSuccess: () => {
setUserLoggedIn(true);
},
loginFailure: (e: CometChat.CometChatException) => {
console.log("LoginListener :: loginFailure", e.message);
},
logoutSuccess: () => {
setUserLoggedIn(false);
},
logoutFailure: (e: CometChat.CometChatException) => {
console.log("LoginListener :: logoutFailure", e.message);
},
})
);
// Clean up the login listener on component unmount.
return () => {
CometChat.removeLoginListener(listenerId);
};
}, []);
/**
* Attach CometChat call listeners to handle incoming, outgoing, and cancelled call events.
* Also handles UI events for call end.
*/
useEffect(() => {
// Listener for call events.
CometChat.addCallListener(
listenerId,
new CometChat.CallListener({
onIncomingCallReceived: (call: CometChat.Call) => {
// Check if there's already an active call
try {
const activeCall = CometChat.getActiveCall();
if (activeCall) {
// If there's an active call, reject the incoming call with busy status
setTimeout(() => {
CometChat.rejectCall(
call.getSessionId(),
CometChat.CALL_STATUS.BUSY
)
.then(() => {
console.log("Incoming call rejected due to active call");
})
.catch((error) => {
console.error(
"Error rejecting call with busy status:",
error
);
});
}, 2000);
} else {
// No active call, proceed with normal incoming call handling
// Hide any bottom sheet UI before showing the incoming call screen.
CometChatUIEventHandler.emitUIEvent(
CometChatUIEvents.ccToggleBottomSheet,
{
isBottomSheetVisible: false,
}
);
// Store the incoming call and update state.
incomingCall.current = call;
setCallReceived(true);
}
} catch (error) {
console.error("Error getting active call:", error);
// If error getting active call, proceed with normal handling
CometChatUIEventHandler.emitUIEvent(
CometChatUIEvents.ccToggleBottomSheet,
{
isBottomSheetVisible: false,
}
);
incomingCall.current = call;
setCallReceived(true);
}
},
onOutgoingCallRejected: () => {
// Clear the call state if outgoing call is rejected.
incomingCall.current = null;
setCallReceived(false);
},
onIncomingCallCancelled: () => {
// Clear the call state if the incoming call is cancelled.
incomingCall.current = null;
setCallReceived(false);
},
})
);
// Additional listener to handle call end events.
CometChatUIEventHandler.addCallListener(listenerId, {
ccCallEnded: () => {
incomingCall.current = null;
setCallReceived(false);
},
});
// Remove call listeners on cleanup.
return () => {
CometChatUIEventHandler.removeCallListener(listenerId);
CometChat.removeCallListener(listenerId);
};
}, [userLoggedIn]);
// Show a blank/splash screen while the app is initializing.
if (isInitializing) {
return (
);
}
// Once initialization is complete, render the main app UI.
return (
{/* Render the incoming call UI if the user is logged in and a call is received */}
{isLoggedIn && callReceived && incomingCall.current ? (
{
// Handle call decline by clearing the incoming call state.
incomingCall.current = null;
setCallReceived(false);
}}
/>
) : null}
{/* Render the main navigation stack, passing the login status as a prop */}
);
};
export default App;
================================================
FILE: examples/SampleAppExpo/README.md
================================================
# React Native Sample App Expo by CometChat
This is a reference application showcasing the integration of [CometChat's React Native UI Kit](https://www.cometchat.com/docs/ui-kit/react-native/5.0/overview) in a React Native Expo project. It demonstrates how to implement real-time messaging and voice/video calling features with ease in your React Native Expo App.
## Prerequisites
Sign up for a [CometChat](https://app.cometchat.com/) account to obtain your app credentials: _`App ID`_, _`Region`_, and _`Auth Key`_
- **Node.js** 18 or higher
- **React Native** Version 0.77 or later (up to the latest version)
**iOS**
- XCode
- Pod (CocoaPods) for iOS
- An iOS device or emulator with iOS 12.0 or above.
- Ensure that you have configured the provisioning profile in Xcode to run the app on a physical device.
**Android**
- Android Studio
- Android device or emulator with Android version 5.0 or above.
## Installation
1. Clone the repository:
```sh
git clone https://github.com/cometchat/cometchat-uikit-react-native.git
```
1. Change into the specific app's directory (e.g., SampleApp).
```sh
cd examples/SampleAppExpo
```
1. Run `npm install` to install the dependencies in the root folder.
1. `[Optional]` Configure CometChat credentials:
- Open the `AppConstants.tsx` file located at `examples/SampleAppExpo/src/utils/AppConstants.tsx` and enter your CometChat _`appId`_, _`region`_, and _`authKey`_:
```ts
export const AppConstants = {
appId: 'YOUR_APP_ID',
authKey: 'YOUR_AUTH_KEY',
region: 'REGION',
//other properties
}
```
1. Generate the native Android and iOS project folders for the Expo app. This step is required for the "custom dev client" and "bare workflow" pipeline:
```sh
npx expo prebuild
```
1. Run the app on a connected device or emulator:
```sh
npx expo run:android
npx expo run:ios
```
> **⚠️ Compatibility:**
> The React Native UI Kit is **not compatible** with Expo Go because it requires custom native modules. Expo Go is not recommended for production-grade apps.
> **Development Builds:** To use the UI Kit in an Expo app, you must use development builds. Refer to the official Expo guide for more details.
## Help and Support
For issues running the project or integrating with our UI Kits, consult our [documentation](https://www.cometchat.com/docs/ui-kit/react-native/5.0/getting-started) or create a [support ticket](https://help.cometchat.com/hc/en-us). You can also access real-time support via the [CometChat Dashboard](http://app.cometchat.com/).
================================================
FILE: examples/SampleAppExpo/app.json
================================================
{
"expo": {
"name": "SampleAppExpo",
"slug": "SampleAppExpo",
"version": "5.3.4",
"orientation": "portrait",
"icon": "./assets/icon.png",
"newArchEnabled": true,
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.cometchat.internal.reactnative.ios",
"userInterfaceStyle": "automatic",
"infoPlist": {
"NSCameraUsageDescription": "This is for Camera permission",
"NSMicrophoneUsageDescription": "This is for Microphone permission"
}
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"configChanges": [
"keyboard",
"keyboardHidden",
"orientation",
"screenLayout",
"screenSize",
"smallestScreenSize",
"uiMode",
"fontScale"
],
"edgeToEdgeEnabled": true,
"package": "com.cometchat.sampleapp.reactnative.android",
"userInterfaceStyle": "automatic",
"permissions": [
"android.permission.INTERNET",
"android.permission.CAMERA",
"android.permission.MODIFY_AUDIO_SETTINGS",
"android.permission.RECORD_AUDIO",
"android.permission.ACCESS_NETWORK_STATE"
]
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": [
[
"expo-font",
{
"fonts": [
"./assets/fonts/inter_regular.ttf",
"./assets/fonts/inter_medium.ttf",
"./assets/fonts/inter_bold.ttf",
"./assets/fonts/roboto_regular.ttf",
"./assets/fonts/roboto_medium.ttf",
"./assets/fonts/roboto_bold.ttf",
"./assets/fonts/times_new_roman_regular.ttf",
"./assets/fonts/times_new_roman_medium.ttf",
"./assets/fonts/times_new_roman_bold.ttf"
]
}
]
]
}
}
================================================
FILE: examples/SampleAppExpo/gesture-handler.js
================================================
================================================
FILE: examples/SampleAppExpo/gesture-handler.native.js
================================================
// Only import react-native-gesture-handler on native platforms
import 'react-native-gesture-handler';
================================================
FILE: examples/SampleAppExpo/index.js
================================================
import { registerRootComponent } from 'expo';
import App from './App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);
================================================
FILE: examples/SampleAppExpo/metro.config.js
================================================
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('expo/metro-config');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
module.exports = config;
================================================
FILE: examples/SampleAppExpo/package.json
================================================
{
"name": "sampleappexpo",
"version": "5.3.4",
"main": "index.js",
"scripts": {
"start": "expo start --dev-client",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web"
},
"dependencies": {
"@cometchat/calls-sdk-react-native": "^4.4.0",
"@cometchat/chat-sdk-react-native": "^4.0.21",
"@cometchat/chat-uikit-react-native": "^5.3.4",
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-clipboard/clipboard": "^1.16.3",
"@react-native-community/datetimepicker": "^8.4.5",
"@react-native-community/netinfo": "^11.4.1",
"@react-navigation/bottom-tabs": "^7.4.7",
"@react-navigation/native": "^7.1.18",
"@react-navigation/native-stack": "^7.14.12",
"@react-navigation/stack": "^7.4.8",
"dayjs": "^1.11.18",
"expo": "~54.0.12",
"expo-audio": "^1.0.13",
"expo-camera": "^17.0.8",
"expo-dev-client": "^6.0.13",
"expo-font": "~14.0.9",
"expo-navigation-bar": "^5.0.8",
"expo-status-bar": "~3.0.8",
"expo-system-ui": "^6.0.7",
"react": "19.1.0",
"react-native": "0.81.4",
"react-native-background-timer": "^2.4.1",
"react-native-callstats": "^3.73.22",
"react-native-gesture-handler": "^2.28.0",
"react-native-localize": "^3.5.3",
"react-native-safe-area-context": "^5.6.1",
"react-native-screens": "^4.16.0",
"react-native-svg": "^15.13.0",
"react-native-video": "^6.16.1",
"react-native-vision-camera": "^4.7.2",
"react-native-webrtc": "^124.0.7",
"zustand": "^5.0.8"
},
"private": true,
"devDependencies": {
"@types/react": "~19.1.10",
"typescript": "~5.9.2"
}
}
================================================
FILE: examples/SampleAppExpo/src/assets/icons/AccountCircle.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/AddComment.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/AiIcon.tsx
================================================
import Svg, { Path, Defs, LinearGradient, Stop } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const AiIcon = ({ width = 24, height = 24 }: SvgProps) => (
);
export default AiIcon;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/ArrowBack.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Block.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Builder.tsx
================================================
import React from "react";
import Svg, { Path, SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Call.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/CallFill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Chat.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Chatfill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/CheckFill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Close.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Delete.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Flash.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ width = 24, height = 24, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Group.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/GroupAdd.tsx
================================================
import type { SvgProps } from "react-native-svg";
import Svg, { Path } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/GroupFill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Info.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/InfoIcon.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Logout.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Person.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/PersonAdd.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/PersonFill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/PersonOff.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/Reset.tsx
================================================
import React from "react";
import Svg, { Path, SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/UserEmptyIcon.tsx
================================================
import type { SvgProps } from "react-native-svg";
import Svg, { G, Mask, Path, Rect } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/assets/icons/VideoCam.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/AIAgent/AIAgents.tsx
================================================
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {CometChatUsers, useTheme} from '@cometchat/chat-uikit-react-native';
import React, {useCallback, useLayoutEffect} from 'react';
import {SafeAreaView} from 'react-native';
import {useFocusEffect, useNavigation} from '@react-navigation/native';
import {RootStackParamList} from '../../navigation/types';
import {StackNavigationProp} from '@react-navigation/stack';
type AIAgentNavigationProp = StackNavigationProp;
const AIAgents: React.FC = () => {
const theme = useTheme();
const navigation = useNavigation();
const [shouldHide, setShouldHide] = React.useState(false);
// Focus effect to manage component visibility
useFocusEffect(
useCallback(() => {
setShouldHide(false);
return () => {
setShouldHide(true);
};
}, []),
);
// Configure header with back button
useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
title: 'AI Assistants',
headerStyle: {
backgroundColor: theme.color.background1,
},
headerTitleStyle: {
color: theme.color.textPrimary,
},
});
}, [navigation, theme]);
const handleUserPress = (user: CometChat.User) => {
navigation.navigate('Messages', { user });
};
return shouldHide ? null : (
navigation.goBack()}
usersRequestBuilder={new CometChat.UsersRequestBuilder()
.setLimit(30)
.hideBlockedUsers(false)
.setRoles(['@agentic'])
.friendsOnly(false)
.setStatus('')
.setTags([])
.sortBy('name')
.setUIDs([])}
/>
);
};
export default AIAgents;
================================================
FILE: examples/SampleAppExpo/src/components/calls/CallDetailHelper.tsx
================================================
import {CometChatTheme, CometChatUIKit} from '@cometchat/chat-uikit-react-native';
import {CallMade, CallMissedOutgoingFill, CallReceived} from './icons';
import {JSX} from 'react';
import { getCometChatTranslation } from '@cometchat/chat-uikit-react-native';
const t = getCometChatTranslation();
type CallDirection = 'incoming' | 'outgoing';
export type CallStatus =
| 'incoming'
| 'outgoing'
| 'incomingCallEnded'
| 'outgoingCallEnded'
| 'cancelledByMe'
| 'cancelledByThem'
| 'incomingRejected'
| 'outgoingRejected'
| 'incomingBusy'
| 'outgoingBusy'
| 'unansweredByMe'
| 'unansweredByThem';
export class CallDetailHelper {
static getFormattedInitiatedAt = (call: any): string => {
const date = new Date(call.getInitiatedAt() * 1000);
const now = new Date();
// Extracting parts
const day = date.getDate();
const month = new Intl.DateTimeFormat('en-US', {month: 'long'}).format(
date,
);
const year = date.getFullYear();
const time = date.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true,
});
// Determine if the year should be included
const includeYear = now.getFullYear() !== year;
return `${day} ${month}${includeYear ? `, ${year}` : ''}, ${time}`;
};
/** Returns the UI-facing callStatus plus the direction */
static getCallType = (
call: any,
): {type: CallDirection; callStatus: CallStatus} => {
const myUid = CometChatUIKit.loggedInUser?.getUid();
const type: CallDirection =
call.getInitiator().getUid() === myUid ? 'outgoing' : 'incoming';
const statusMap: Record<
string,
{incoming: CallStatus; outgoing: CallStatus}
> = {
ended: {
incoming: 'incomingCallEnded',
outgoing: 'outgoingCallEnded',
},
rejected: {
incoming: 'incomingRejected',
outgoing: 'outgoingRejected',
},
cancelled: {
incoming: 'unansweredByMe',
outgoing: 'cancelledByMe',
},
unanswered: {
incoming: 'unansweredByMe',
outgoing: 'unansweredByThem',
},
initiated: {
incoming: 'incoming',
outgoing: 'outgoing',
},
busy: {
incoming: 'incomingBusy',
outgoing: 'outgoingBusy',
},
};
return {
type,
callStatus:
statusMap[call.getStatus() as keyof typeof statusMap]?.[type] ??
(type === 'incoming' ? 'incoming' : 'outgoing'),
};
};
/** Which SVG to render for a given callStatus */
static getCallStatusDisplayIcon = (
callStatus: CallStatus,
theme: CometChatTheme,
): JSX.Element | undefined => {
const icons: Record = {
outgoing: ,
outgoingCallEnded: (
),
cancelledByMe: (
),
outgoingRejected: (
),
outgoingBusy: (
),
unansweredByThem: (
),
incoming: (
),
incomingCallEnded: (
),
cancelledByThem: (
),
incomingRejected: (
),
incomingBusy: (
),
unansweredByMe: (
),
};
return icons[callStatus];
};
static getCallStatusDisplayText = (callStatus: CallStatus): string => {
const labels: Record = {
outgoing: t('OUTGOING_CALL'),
outgoingCallEnded: t('OUTGOING_CALL'),
cancelledByMe: t('OUTGOING_CALL'),
outgoingRejected: t('OUTGOING_CALL'),
outgoingBusy: t('OUTGOING_CALL'),
unansweredByThem: t('OUTGOING_CALL'),
incoming: t('INCOMING_CALL'),
incomingCallEnded: t('INCOMING_CALL'),
cancelledByThem: t('MISSED_CALL'),
incomingRejected: t('INCOMING_CALL'),
incomingBusy: t('MISSED_CALL'),
unansweredByMe: t('MISSED_CALL'),
};
return labels[callStatus];
};
}
================================================
FILE: examples/SampleAppExpo/src/components/calls/CallDetails.tsx
================================================
import React, {
JSX,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {View, TouchableOpacity, Text, TextStyle, ViewStyle} from 'react-native';
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {
CometChatListItem,
useCometChatTranslation,
useTheme,
useLocalizedDate,
LocalizedDateHelper
} from '@cometchat/chat-uikit-react-native';
import {CallHistory} from './CallHistory';
import {CallLogDetailHeader} from './CallLogDetailHeader';
import {Icon} from '@cometchat/chat-uikit-react-native';
import {CallParticipants} from './CallParticipants';
import {CallDetailHelper, CallStatus} from './CallDetailHelper';
import {CallRecordings} from './CallRecordings';
import {StackScreenProps} from '@react-navigation/stack';
import {ICONS} from '@cometchat/chat-uikit-react-native/src/shared/icons/icon-mapping';
import {RootStackParamList} from '../../navigation/types';
const listenerId = 'userListener_' + new Date().getTime();
const TABS = {
DETAILS: 'Details',
PARTICIPANTS: 'Participants',
RECORDINGS: 'Recordings',
HISTORY: 'History',
};
type Props = StackScreenProps;
export const CallDetails: React.FC = ({route, navigation}) => {
const {call} = route.params;
const theme = useTheme();
const {t, language} = useCometChatTranslation()
const {formatDate}= useLocalizedDate()
const [group, setGroup] = useState(null);
const [user, setUser] = useState(null);
const loggedInUser = useRef(null);
const [selectedTab, setSelectedTab] = useState(TABS.PARTICIPANTS);
const [tabStyle, setTableStyle] = useState<{
containerStyle: ViewStyle;
itemStyle: ViewStyle;
selectedItemStyle: ViewStyle;
itemEmojiStyle: TextStyle;
selectedItemEmojiStyle: TextStyle;
itemTextStyle: TextStyle;
selectedItemTextStyle: TextStyle;
}>();
const BackIcon = ICONS['arrow-back'];
const [toastMessage, setToastMessage] = useState(null);
useEffect(() => {
console.log('CALL RECEIVER: ', call);
CometChat.getLoggedinUser().then((loggedUser: CometChat.User | any) => {
loggedInUser.current = loggedUser;
let user =
call?.getReceiverType() == 'user'
? loggedInUser.current?.getUid() === call?.getInitiator()?.getUid()
? call.getReceiver()
: call?.getInitiator()
: undefined;
let group =
call?.getReceiverType() == 'group'
? loggedInUser.current?.getUid() === call?.getInitiator()?.getUid()
? call.getReceiver()
: call?.getInitiator()
: undefined;
if (user) {
CometChat.getUser(user.getUid()).then((userObject: CometChat.User) => {
setUser(userObject);
});
return;
}
CometChat.getGroup(group.getGuid()).then(
(groupObject: CometChat.Group) => {
setGroup(groupObject);
},
);
});
}, [call]);
useEffect(() => {
CometChat.addUserListener(
listenerId,
new CometChat.UserListener({
onUserOnline: (userDetails: any) => {
if (user?.getUid() === userDetails?.getUid()) {
setUser(userDetails);
}
},
onUserOffline: (userDetails: any) => {
if (user?.getUid() === userDetails?.getUid()) {
setUser(userDetails);
}
},
}),
);
return () => {
CometChat.removeUserListener(listenerId);
};
}, [user]);
useEffect(() => {
setTableStyle({
containerStyle: {
//flex: 1,
backgroundColor: theme.color.background1,
flexDirection: 'row',
//justifyContent: 'space-evenly', // ✅ Ensures even spacing
alignItems: 'center', // Optional, ensures vertical alignment
width: '100%', // ✅ Ensures full width for proper spacing
borderBottomWidth: 1,
borderColor: theme.color.borderDefault,
justifyContent: 'space-evenly',
},
itemStyle: {
// paddingHorizontal: theme.spacing.padding.p4,
paddingVertical: theme.spacing.padding.p2,
flexDirection: 'row',
borderBottomWidth: theme.spacing.spacing.s0_5,
borderBottomColor: 'transparent',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
},
selectedItemStyle: {
// paddingHorizontal: theme.spacing.padding.p4,
paddingVertical: theme.spacing.padding.p2,
flexDirection: 'row',
borderBottomWidth: theme.spacing.spacing.s0_5,
borderBottomColor: theme.color.primary,
alignItems: 'center',
justifyContent: 'center',
flex: 1,
},
itemEmojiStyle: {
color: theme.color.textSecondary,
borderColor: 'transparent',
...theme.typography.body.medium,
},
selectedItemEmojiStyle: {
color: theme.color.textSecondary,
borderColor: 'transparent',
...theme.typography.body.medium,
},
itemTextStyle: {
color: theme.color.textSecondary,
marginLeft: theme.spacing.margin.m1,
...theme.typography.body.medium,
},
selectedItemTextStyle: {
color: theme.color.primary,
marginLeft: theme.spacing.margin.m1,
...theme.typography.body.medium,
},
});
}, [theme]);
const getUserToFetchHistory = useCallback(() => {
if (call?.getInitiator().getUid() === loggedInUser.current.getUid()) {
return call?.getReceiver();
}
call?.getInitiator();
}, [call]);
const callTypeAndStatus = useMemo(
(): {
type: 'incoming' | 'outgoing';
callStatus: CallStatus;
} => CallDetailHelper.getCallType(call),
[call],
);
/** Busy call toast: only when attempting a call and target is busy */
useEffect(() => {
const callListener = new CometChat.CallListener({
onOutgoingCallRejected: (rejectedCall: any) => {
try {
const status = rejectedCall?.getStatus?.() || rejectedCall?.status;
if (
status &&
status.toLowerCase() === CometChat.CALL_STATUS.BUSY.toLowerCase()
) {
setToastMessage(t('CALL_BUSY'));
setTimeout(() => setToastMessage(null), 3000);
}
} catch {}
},
});
CometChat.addCallListener(listenerId, callListener);
return () => {
CometChat.removeCallListener(listenerId);
};
}, [t]);
const _style = useMemo(() => {
return {
headerContainerStyle: {
alignItems: 'flex-start',
justifyContent: 'center',
width: '100%',
borderRadius: 0,
paddingHorizontal: 0,
},
titleSeparatorStyle: {
borderBottomWidth: 1,
borderBottomColor: theme.color.borderLight,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
},
containerStyle: {
backgroundColor: theme.color.background2,
flex: 1,
},
itemStyle: {
containerStyle: {
flexDirection: 'row' as const,
paddingRight: theme.spacing.padding.p4,
paddingLeft: theme.spacing.padding.p2,
paddingVertical: theme.spacing.padding.p2,
gap: theme.spacing.spacing.s3,
flex: 1,
},
titleStyle: {
color: theme.color.textPrimary,
...theme.typography.heading4.bold,
},
subtitleStyle: {
color: theme.color.textSecondary,
...theme.typography.caption1.regular,
},
tailViewTextStyle: {
color: theme.color.textPrimary,
...theme.typography.caption1.bold,
},
avatarStyle: {
containerStyle: {},
textStyle: {},
imageStyle: {
height: 48,
width: 48,
},
},
},
};
}, [theme]);
const convertMinutesToTime = useCallback((decimalMinutes: number) => {
const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds
const minutes = Math.floor(totalSeconds / 60); // Get whole minutes
const seconds = totalSeconds % 60; // Get remaining seconds
return `${minutes} min ${seconds} sec`;
}, []);
const getFormattedInitiatedAt = useCallback(() => {
if (!call || !call.getInitiatedAt()) return '';
return formatDate(
call.getInitiatedAt() * 1000,
LocalizedDateHelper.patterns.dayDateTimeFormat
);
}, [call]);
const callStatusDisplayString = useMemo(() => {
return CallDetailHelper.getCallStatusDisplayText(
callTypeAndStatus.callStatus,
);
}, [callTypeAndStatus]);
const CallStatusIcon = useMemo(
() =>
CallDetailHelper.getCallStatusDisplayIcon(
callTypeAndStatus.callStatus,
theme,
),
[callTypeAndStatus, theme],
);
/** Local toast view component (shows only when toastMessage set; auto hides after 3s from effect) */
const ToastView = () => {
if (!toastMessage) return null;
return (
{toastMessage}
);
}
return (
navigation.goBack()}>
}
/>
{t('CALL_DETAILS')}
{(user || group) && (
{getFormattedInitiatedAt()}
}
TrailingView={
{convertMinutesToTime(call.getTotalDurationInMinutes())}
}
/>
{[
{ key: TABS.PARTICIPANTS, title: t('PARTICIPANT') },
{ key: TABS.RECORDINGS, title: t('RECORDING') },
{ key: TABS.HISTORY, title: t('HISTORY') }
].map((tab) => (
setSelectedTab(tab.key)}>
{tab.title}
))}
{selectedTab === TABS.PARTICIPANTS && (
)}
{selectedTab === TABS.HISTORY && (
)}
{selectedTab === TABS.RECORDINGS &&
call.getRecordings()?.length && (
)}
)}
);
};
================================================
FILE: examples/SampleAppExpo/src/components/calls/CallHistory.tsx
================================================
import { CometChat } from '@cometchat/chat-sdk-react-native';
import {
CallingPackage,
CometChatListItem,
useTheme,
useCometChatTranslation,
useLocalizedDate,
LocalizedDateHelper
} from '@cometchat/chat-uikit-react-native';
import React, { JSX, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { ActivityIndicator, FlatList, Text, View } from 'react-native';
import { CallDetailHelper } from './CallDetailHelper';
import { Icon } from '@cometchat/chat-uikit-react-native';
const CometChatCalls = CallingPackage.CometChatCalls;
export const CallHistory = (props: { user?: any; group?: any }) => {
const { user, group } = props;
const theme = useTheme();
const { language, t } = useCometChatTranslation();
const { formatDate } = useLocalizedDate();
const [list, setList] = useState([]);
const [loading, setLoading] = useState(true);
const [isFetchingMore, setIsFetchingMore] = useState(false);
const loggedInUser = useRef(null);
const callRequestBuilderRef = useRef(null);
function setRequestBuilder() {
callRequestBuilderRef.current;
let builder = new CometChatCalls.CallLogRequestBuilder()
.setLimit(30)
.setAuthToken(loggedInUser.current?.getAuthToken() || '')
.setCallCategory('call');
if (user) {
builder = builder.setUid(user?.getUid());
} else if (group) {
builder = builder.setGuid(group?.getGuid());
}
callRequestBuilderRef.current = builder.build();
}
const fetchCallLogHistory = () => {
if (!callRequestBuilderRef.current || isFetchingMore) {
return;
}
setIsFetchingMore(true);
callRequestBuilderRef.current
.fetchNext()
.then((CallLogHistory: any) => {
if (CallLogHistory.length > 0) {
setList(prev => [...prev, ...CallLogHistory]);
}
})
.catch((err: any) => {
// onError && onError(err);
})
.finally(() => {
setLoading(false);
setIsFetchingMore(false);
});
};
useEffect(() => {
CometChat.getLoggedinUser()
.then((u: any) => {
loggedInUser.current = u;
setRequestBuilder();
fetchCallLogHistory();
})
.catch((e: any) => {
// onError && onError(e);
setLoading(false);
});
}, []);
const _style = useMemo(() => {
return {
headerContainerStyle: {
alignItems: 'flex-start',
justifyContent: 'center',
width: '100%',
borderRadius: 0,
paddingHorizontal: 0,
},
titleSeparatorStyle: {
borderBottomWidth: 1,
borderBottomColor: theme.color.borderLight,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
},
containerStyle: {
backgroundColor: theme.color.background1,
flex: 1,
},
itemStyle: {
containerStyle: {
flexDirection: 'row' as const,
marginHorizontal: theme.spacing.margin.m4,
paddingVertical: theme.spacing.padding.p2,
gap: theme.spacing.spacing.s3,
flex: 1,
},
titleStyle: {
color: theme.color.textPrimary,
flex: 1,
...theme.typography.heading4.bold,
},
subtitleStyle: {
color: theme.color.textSecondary,
...theme.typography.caption1.regular,
},
tailViewTextStyle: {
color: theme.color.textPrimary,
...theme.typography.caption1.bold,
},
avatarStyle: {
containerStyle: {},
textStyle: {},
imageStyle: {
height: 48,
width: 48,
},
},
},
};
}, [theme]);
// Updated to use proper localization
const getFormattedInitiatedAt = useCallback((call: any) => {
if (!call || !call.getInitiatedAt()) return '';
// Use localizedDateHelper to format the date with proper localization
return formatDate(
call.getInitiatedAt() * 1000,
LocalizedDateHelper.patterns.dayDateTimeFormat
);
}, [formatDate]);
const getCallType = useCallback((call: any) => {
return CallDetailHelper.getCallType(call);
}, []);
const getCallStatusIcon = useCallback((item: any): JSX.Element => {
const CallStatusIcon = CallDetailHelper.getCallStatusDisplayIcon(
getCallType(item).callStatus,
theme,
);
return CallStatusIcon || ;
}, []);
const convertMinutesToTime = useCallback((decimalMinutes: number) => {
const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds
const minutes = Math.floor(totalSeconds / 60); // Get whole minutes
const seconds = totalSeconds % 60; // Get remaining seconds
return `${minutes} min ${seconds} sec`;
}, []);
// Added to get localized call status text
const getCallStatusText = useCallback((callStatus: any) => {
// Get translation key for this status
const translationKeys: Record = {
outgoing: 'OUTGOING_CALL',
outgoingCallEnded: 'OUTGOING_CALL',
cancelledByMe: 'OUTGOING_CALL',
outgoingRejected: 'OUTGOING_CALL',
outgoingBusy: 'OUTGOING_CALL',
unansweredByThem: 'OUTGOING_CALL',
incoming: 'INCOMING_CALL',
incomingCallEnded: 'INCOMING_CALL',
cancelledByThem: 'MISSED_CALL',
incomingRejected: 'INCOMING_CALL',
incomingBusy: 'MISSED_CALL',
unansweredByMe: 'MISSED_CALL',
};
const key = translationKeys[callStatus] || 'UNKNOWN_CALL';
return t(key);
}, [t]);
const _render = ({ item, index }: any) => {
return (
{getFormattedInitiatedAt(item)}
}
TrailingView={
{convertMinutesToTime(item.getTotalDurationInMinutes())}
}
/>
);
};
return (
item.sessionId + '_' + index}
renderItem={_render}
onEndReached={fetchCallLogHistory}
onEndReachedThreshold={0.5}
ListEmptyComponent={
loading ? (
) : null
}
/>
);
};
================================================
FILE: examples/SampleAppExpo/src/components/calls/CallLogDetailHeader.tsx
================================================
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {
useTheme,
CometChatAvatar,
CometChatCallButtons,
useCometChatTranslation,
} from '@cometchat/chat-uikit-react-native';
import {
GroupTypeConstants,
UserStatusConstants,
} from '@cometchat/chat-uikit-react-native/src/shared/constants/UIKitConstants';
import {CometChatCompThemeProvider} from '@cometchat/chat-uikit-react-native/src/theme/provider';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {Text, View} from 'react-native';
import { useConfig } from '../../config/store';
export type CallLogDetailHeaderInterface = {
user?: CometChat.User;
/**
*
* @type {CometChat.Group}
* To pass group object
*/
group?: CometChat.Group;
};
export const CallLogDetailHeader = (props: CallLogDetailHeaderInterface) => {
const theme = useTheme();
const {t}= useCometChatTranslation()
const {user, group} = props;
const [groupObj, setGroupObj] = useState(group);
const [userStatus, setUserStatus] = useState(
user &&
!(user.getBlockedByMe() || user.getHasBlockedMe()) &&
user?.getStatus
? user?.getStatus()
: '',
);
const oneOnOneVoiceCalling = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling
);
const oneOnOneVideoCalling = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling
);
const groupVideoConference = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference
);
const groupVoiceConference = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference
);
const receiverTypeRef = useRef(
user
? CometChat.RECEIVER_TYPE.USER
: group
? CometChat.RECEIVER_TYPE.GROUP
: null,
);
useEffect(() => {
setGroupObj(group);
}, [group]);
useEffect(() => {
setUserStatus(
user && !(user.getBlockedByMe() || user.getHasBlockedMe())
? user?.getStatus()
: '',
);
}, [user]);
const messageHeaderStyles = useMemo(() => {
return theme.messageHeaderStyles;
}, [theme.messageHeaderStyles]);
const statusIndicatorType = useMemo(() => {
if (groupObj?.getType() === GroupTypeConstants.password) {
return 'protected';
} else if (groupObj?.getType() === GroupTypeConstants.private) {
return 'private';
} else if (userStatus === 'online') {
return 'online';
}
return '';
}, [userStatus, groupObj]);
const AvatarWithStatusView = useCallback(() => {
return (
);
}, [user, groupObj, statusIndicatorType]);
const SubtitleViewFnc = () => {
const statusTytle =
receiverTypeRef.current === CometChat.RECEIVER_TYPE.GROUP &&
(groupObj?.['membersCount'] || groupObj?.['membersCount'] === 0)
? `${groupObj['membersCount']} ${t('MEMBERS')}`
: receiverTypeRef.current === CometChat.RECEIVER_TYPE.USER
? userStatus === UserStatusConstants.online
? t('ONLINE')
: userStatus === UserStatusConstants.offline
? t('OFFLINE')
: ''
: '';
if(!statusTytle) return <>>;
return (
{statusTytle}
);
};
return (
{user ? user.getName() : groupObj ? groupObj.getName() : ''}
{ }
);
};
================================================
FILE: examples/SampleAppExpo/src/components/calls/CallParticipants.tsx
================================================
import { CometChatListItem, useTheme, useCometChatTranslation, localizedDateHelperInstance, useLocalizedDate, LocalizedDateHelper } from '@cometchat/chat-uikit-react-native';
import React, { useCallback, useMemo } from 'react';
import { View, FlatList, Text } from 'react-native';
import { CallDetailHelper } from './CallDetailHelper';
export const CallParticipants = (props: {
/**
* Participant list
*/
data: any[];
call: any;
}) => {
const { call, data } = props;
const theme = useTheme();
const { language, t } = useCometChatTranslation();
const { formatDate } = useLocalizedDate();
const getCallDetails = (item: any) => {
return {
title: item['name'],
avatarUrl: item['avatar'],
};
};
// Updated to use proper localization
const formattedInitiatedAt = useMemo(() => {
if (!call || !call.getInitiatedAt()) return '';
// Use formatDate for proper localization of date/time
return formatDate(
call.getInitiatedAt() * 1000,
LocalizedDateHelper.patterns.dayDateTimeFormat
);
}, [call, formatDate]);
const _style = useMemo(() => {
return {
headerContainerStyle: {
alignItems: 'flex-start',
justifyContent: 'center',
width: '100%',
borderRadius: 0,
paddingHorizontal: 0,
},
titleSeparatorStyle: {
borderBottomWidth: 1,
borderBottomColor: theme.color.borderLight,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
},
containerStyle: {
backgroundColor: theme.color.background1,
flex: 1,
},
itemStyle: {
containerStyle: {
flexDirection: 'row' as const,
paddingHorizontal: theme.spacing.padding.p4,
paddingVertical: theme.spacing.padding.p2,
gap: theme.spacing.spacing.s3,
},
titleStyle: {
color: theme.color.textPrimary,
flex: 1,
...theme.typography.heading4.medium,
},
subtitleStyle: {
color: theme.color.textSecondary,
...theme.typography.body.regular,
},
tailViewTextStyle: {
color: theme.color.textPrimary,
...theme.typography.caption1.medium,
},
avatarStyle: {
containerStyle: {},
textStyle: {},
imageStyle: {
height: 48,
width: 48,
},
},
},
};
}, [theme]);
const convertMinutesToTime = useCallback((decimalMinutes: number) => {
const totalSeconds = Math.round(decimalMinutes * 60); // Convert to seconds
const minutes = Math.floor(totalSeconds / 60); // Get whole minutes
const seconds = totalSeconds % 60; // Get remaining seconds
return `${minutes} min ${seconds} sec`;
}, []);
const _render = ({ item, index }: any) => {
const { title, avatarUrl } = getCallDetails(item);
return (
{formattedInitiatedAt}
}
// avatarName={title}
title={title}
// subtitle={'8 August, 8:14 pm'}
avatarURL={avatarUrl}
TrailingView={
{convertMinutesToTime(item.getTotalDurationInMinutes())}
}
/>
);
};
return (
{data.length && (
item.sessionId + '_' + index}
renderItem={_render}
/>
)}
);
};
================================================
FILE: examples/SampleAppExpo/src/components/calls/CallRecordings.tsx
================================================
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {useTheme, CometChatListItem} from '@cometchat/chat-uikit-react-native';
import React, {useRef, useEffect, useMemo, useState} from 'react';
import {
View,
FlatList,
Text,
Pressable,
NativeEventEmitter,
NativeModules,
Platform,
} from 'react-native';
import {PlayArrow} from './icons';
import {Icon} from '@cometchat/chat-uikit-react-native';
import {CallDetailHelper} from './CallDetailHelper';
const {FileManager} = NativeModules;
const eventEmitter = new NativeEventEmitter(FileManager);
export const CallRecordings = (props: {call: any}) => {
const {call} = props;
const [data, setData] = useState(call.getRecordings());
const theme = useTheme();
const loggedInUser = useRef(null);
const downloadIdRef = useRef(0);
const [processing, setProcessing] = React.useState(false);
const [fileExists, setFileExists] = React.useState(false); // State to track if the file exists
let listener: any = useRef(undefined);
const [fileUrl, setFileUrl] = useState();
const [reOpenCount, setReopenCount] = useState(0);
useEffect(() => {
CometChat.getLoggedinUser()
.then((u: any) => {
loggedInUser.current = u;
})
.catch((e: any) => {
// onError && onError(e);
});
}, []);
useEffect(() => {
if (Platform.OS == 'android') {
listener.current = eventEmitter.addListener(
'downloadComplete',
(data: {downloadId: number}) => {
if (
data.downloadId &&
downloadIdRef.current &&
data.downloadId == downloadIdRef.current
) {
setProcessing(false);
setFileExists(true);
openFile(fileUrl);
}
},
);
}
return () => {
if (Platform.OS == 'android') {
listener.current.remove();
}
};
}, []);
useEffect(() => {
if (!fileUrl) return;
const fileName = getFileName(fileUrl);
setProcessing(true);
FileManager.doesFileExist(fileName, (result: string) => {
setProcessing(false);
if (JSON.parse(result).exists) {
setFileExists(true);
openFile(fileUrl);
} else {
downloadFile(fileUrl);
}
});
}, [fileUrl, reOpenCount]);
const downloadFile = (fileUrl: any) => {
if (processing || fileExists) return; // Do not process if file already exists
if (!fileUrl) return;
setProcessing(true);
FileManager.checkAndDownload(
fileUrl,
getFileName(fileUrl),
(result: string) => {
if (Platform.OS == 'ios') {
let parsedResult = JSON.parse(result);
if (parsedResult.success == true) {
setProcessing(false);
setFileExists(true);
}
openFile(fileUrl);
} else if (Platform.OS == 'android') {
downloadIdRef.current = JSON.parse(result).downloadId;
}
},
);
};
const openFile = (fileUrl: any) => {
if (processing) return;
if (!fileUrl) return;
setProcessing(true);
FileManager.openFileWithOption(getFileName(fileUrl), (isOpened: string) => {
setProcessing(false);
});
};
const getFileName = (fileUrl: any) => {
return fileUrl.substring(fileUrl.lastIndexOf('/') + 1).replace(' ', '_');
};
const _style = useMemo(() => {
return {
headerContainerStyle: {
alignItems: 'flex-start',
justifyContent: 'center',
width: '100%',
borderRadius: 0,
paddingHorizontal: 0,
},
titleSeparatorStyle: {
borderBottomWidth: 1,
borderBottomColor: theme.color.borderLight,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
},
containerStyle: {
backgroundColor: theme.color.background1,
flex: 1,
},
itemStyle: {
containerStyle: {
flexDirection: 'row' as const,
paddingHorizontal: theme.spacing.padding.p4,
paddingVertical: theme.spacing.padding.p2,
gap: theme.spacing.spacing.s3,
minHeight: 72,
},
titleStyle: {
color: theme.color.textPrimary,
flex: 1,
...theme.typography.heading4.medium,
},
subtitleStyle: {
color: theme.color.textSecondary,
...theme.typography.body.regular,
},
tailViewTextStyle: {
color: theme.color.textPrimary,
...theme.typography.caption1.medium,
},
avatarStyle: {
containerStyle: {},
textStyle: {},
imageStyle: {
height: 48,
width: 48,
},
},
},
};
}, [theme]);
const formattedInitiatedAt = useMemo(() => {
return CallDetailHelper.getFormattedInitiatedAt(call);
}, [call]);
const _render = ({item, index}: any) => {
const title = item['rid'];
return (
{formattedInitiatedAt}
}
titleStyle={_style.itemStyle.titleStyle}
containerStyle={_style.itemStyle.containerStyle}
trailingViewContainerStyle={{
alignSelf: 'center',
}}
TrailingView={
{
setFileUrl(item.getRecordingURL());
setReopenCount(reOpenCount + 1);
}}>
}
containerStyle={{marginLeft: theme.spacing.margin.m4}}>
}
/>
);
};
return (
{data.length && (
item.sessionId + '_' + index}
renderItem={_render}
/>
)}
);
};
================================================
FILE: examples/SampleAppExpo/src/components/calls/Calls.tsx
================================================
import {CometChatCallLogs, useTheme, Icon} from '@cometchat/chat-uikit-react-native';
import {useFocusEffect, useNavigation} from '@react-navigation/native';
import React, {useCallback} from 'react';
import {View, TouchableOpacity} from 'react-native';
import {StackNavigationProp} from '@react-navigation/stack';
import {RootStackParamList} from '../../navigation/types';
import { useConfig } from '../../config/store';
type CallNavigationProp = StackNavigationProp;
const Calls: React.FC = () => {
const [shouldHide, setShouldHide] = React.useState(false);
const navigation = useNavigation();
const oneOnOneVoiceCalling = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVoiceCalling
);
const oneOnOneVideoCalling = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.oneOnOneVideoCalling
);
const groupVideoConference = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.groupVideoConference
);
const groupVoiceConference = useConfig(
(state) => state.settings.callFeatures.voiceAndVideoCalling.groupVoiceConference
);
useFocusEffect(
useCallback(() => {
setShouldHide(false);
return () => {
setShouldHide(true);
};
}, []),
);
const theme = useTheme();
const onItemPress = (item: any) => {
navigation.navigate('CallDetails', {
call: item,
});
};
// Create TrailingView that respects visibility configuration
const TrailingView = useCallback((call?: any, onPress?: (call: any) => void) => {
if (!call || !onPress) return (<>>);
const receiverType = call?.getReceiverType?.();
const callType = call?.getType?.();
const isUser = receiverType === 'user';
const isAudio = callType === 'audio';
// Check if button should be hidden based on configuration
const shouldHide =
(isUser && isAudio && !oneOnOneVoiceCalling) ||
(isUser && !isAudio && !oneOnOneVideoCalling) ||
(!isUser && isAudio && !groupVoiceConference) ||
(!isUser && !isAudio && !groupVideoConference);
if (shouldHide) {
return ;
}
// Return call button - styling matches CometChatCallLogs default button
return (
onPress(call)}
style={{
marginLeft: "auto",
}}
>
);
}, [oneOnOneVoiceCalling, oneOnOneVideoCalling, groupVoiceConference, groupVideoConference, theme]);
return (
{!shouldHide && (
)}
);
};
export default Calls;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-end-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-end.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-log-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-log.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-made-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-made.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-missed-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-missed-outgoing-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-missed-outgoing.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-missed.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-received-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call-received.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/call.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/cancel-fill.tsx
================================================
import Svg, { Mask, Path, G } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/index.tsx
================================================
export {default as Call} from './call';
export {default as CallEnd} from './call-end';
export {default as CallEndFill} from './call-end-fill';
export {default as CallFill} from './call-fill';
export {default as CallLog} from './call-log';
export {default as CallLogFill} from './call-log-fill';
export {default as CallMade} from './call-made';
export {default as CallMadeFill} from './call-made-fill';
export {default as CallMissed} from './call-missed';
export {default as CallMissedFill} from './call-missed-fill';
export {default as CallMissedOutgoing} from './call-missed-outgoing';
export {default as CallMissedOutgoingFill} from './call-missed-outgoing-fill';
export {default as CallReceived} from './call-received';
export {default as CallReceivedFill} from './call-received-fill';
export {default as CancelFill} from './cancel-fill';
export {default as PlayArrow} from './play-arrow';
================================================
FILE: examples/SampleAppExpo/src/components/calls/icons/play-arrow.tsx
================================================
import Svg, { Path } from "react-native-svg";
import type { SvgProps } from "react-native-svg";
const SvgComponent = ({ height, width, color }: SvgProps) => (
);
export default SvgComponent;
================================================
FILE: examples/SampleAppExpo/src/components/conversations/helper/GroupListeners.ts
================================================
import {CometChat} from '@cometchat/chat-sdk-react-native';
import {CometChatUIKit} from '@cometchat/chat-uikit-react-native';
export const listners = {
addListener: {
groupListener: ({groupListenerId, handleGroupListener}: any) =>
CometChat.addGroupListener(
groupListenerId,
new CometChat.GroupListener({
onGroupMemberKicked: (
message: any,
kickedUser: any,
kickedBy: any,
kickedFrom: any,
) => {
handleGroupListener(kickedFrom);
},
onGroupMemberBanned: (
message: any,
bannedUser: any,
bannedBy: any,
bannedFrom: any,
) => {
handleGroupListener(bannedFrom);
},
onMemberAddedToGroup: (
message: any,
userAdded: any,
userAddedBy: any,
userAddedIn: any,
) => {
handleGroupListener(userAddedIn);
},
onGroupMemberLeft: (message: any, leavingUser: any, group: any) => {
handleGroupListener(group);
},
onGroupMemberScopeChanged: (
message: any,
changedUser: CometChat.User,
newScope: any,
oldScope: any,
changedGroup: CometChat.Group,
) => {
console.log('changedGroup: ', message, changedGroup);
if (changedUser.getUid() == CometChatUIKit.loggedInUser!.getUid()) {
changedGroup.setScope(newScope);
handleGroupListener(changedGroup);
}
},
}),
),
},
removeListner: {
removeUserListener: ({userStatusListenerId}: any) =>
CometChat.removeUserListener(userStatusListenerId),
removeGroupListener: ({groupListenerId}: any) =>
CometChat.removeGroupListener(groupListenerId),
},
};
================================================
FILE: examples/SampleAppExpo/src/components/conversations/screens/AddMember.tsx
================================================
import { CometChat } from '@cometchat/chat-sdk-react-native';
import React, {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
import {
View,
Text,
TouchableOpacity,
NativeModules,
Platform,
KeyboardAvoidingView,
BackHandler,
} from 'react-native';
import {
CometChatGroupsEvents,
CometChatUIEventHandler,
CometChatUIKit,
CometChatUiKitConstants,
CometChatUsers,
CometChatUsersActionsInterface,
useCometChatTranslation,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import { Icon } from '@cometchat/chat-uikit-react-native';
import {
useRoute,
useNavigation,
RouteProp,
useFocusEffect,
} from '@react-navigation/native';
import { styles } from './AddMemberStyles';
import { commonVars } from '@cometchat/chat-uikit-react-native/src/shared/base/vars';
import ArrowBack from '../../../assets/icons/ArrowBack';
import { RootStackParamList } from '../../../navigation/types';
import { useConfig } from '../../../config/store';
const { CommonUtil } = NativeModules;
const AddMember: React.FC = () => {
const route = useRoute>();
const navigation = useNavigation();
const { group } = route.params;
const theme = useTheme();
const { t } = useCometChatTranslation();
const userRef = useRef(null);
const [selectedUsers, setSelectedUsers] = useState([]);
const [errorToastVisible, setErrorToastVisible] = useState(false);
const [errorToastMessage, setErrorToastMessage] = useState('');
const errorTimeoutRef = useRef | null>(null);
const [kbOffset, setKbOffset] = React.useState(900);
const userAndFriendsPresence = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence
);
useEffect(() => {
return () => {
if (errorTimeoutRef.current) {
clearTimeout(errorTimeoutRef.current);
}
};
}, []);
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
// Navigate back to message screen (same as your onPress handler)
navigation.goBack();
return true; // Prevent default back behavior
};
const subscription = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
return () => subscription.remove();
}, [navigation]),
);
useLayoutEffect(() => {
if (Platform.OS === 'ios') {
if (Number.isInteger(commonVars.safeAreaInsets.top)) {
setKbOffset(commonVars.safeAreaInsets.top ?? 0);
return;
}
CommonUtil.getSafeAreaInsets().then((res: any) => {
if (Number.isInteger(res.top)) {
commonVars.safeAreaInsets.top = res.top;
commonVars.safeAreaInsets.bottom = res.bottom;
setKbOffset(res.top);
}
});
}
}, []);
const addMembersToGroup = useCallback(
async (users: Array) => {
try {
const membersList = users.map((item: CometChat.User) => {
const groupMember = new CometChat.GroupMember(
item.getUid(),
CometChat.GROUP_MEMBER_SCOPE.PARTICIPANT,
);
groupMember.setName(item.getName());
return groupMember;
});
const guid = group.getGuid();
const response = await CometChat.addMembersToGroup(
guid,
membersList,
[],
);
const addedUIDs = Object.entries(response)
.filter(([_, status]) => status === 'success')
.map(([uid]) => uid);
const addedMembers = membersList.filter(member =>
addedUIDs.includes(member.getUid()),
);
if (addedUIDs.length > 0) {
navigation.goBack();
} else {
setErrorToastMessage('Error, Unable to add members');
setErrorToastVisible(true);
errorTimeoutRef.current = setTimeout(() => {
setErrorToastVisible(false);
}, 3000);
}
// If all succeeded, emit individual events for each member
if (addedMembers.length > 0) {
const groupInfo = await CometChat.getGroup(group.getGuid());
group.setMembersCount(groupInfo.getMembersCount());
// Create separate action for each added member
addedMembers.forEach(member => {
const action: CometChat.Action = new CometChat.Action(
guid,
CometChatUiKitConstants.MessageTypeConstants.groupMember,
CometChat.RECEIVER_TYPE.GROUP,
CometChat.CATEGORY_ACTION as CometChat.MessageCategory,
);
action.setConversationId(guid);
action.setActionBy(CometChatUIKit.loggedInUser!);
action.setActionFor(group);
action.setSender(CometChatUIKit.loggedInUser!);
// Initialize data to prevent crash when SDK accesses getData().metadata during render
action.setData({ metadata: {} });
// Emit individual event for each member added
CometChatUIEventHandler.emitGroupEvent(
CometChatGroupsEvents.ccGroupMemberAdded,
{
addedBy: CometChatUIKit.loggedInUser,
message: action,
usersAdded: [member],
userAddedIn: group,
},
);
});
}
} catch (error) {
console.error('Something went wrong', error);
setErrorToastMessage('Error, Unable to add members');
setErrorToastVisible(true);
errorTimeoutRef.current = setTimeout(() => {
setErrorToastVisible(false);
}, 2000);
}
},
[group, navigation],
);
const handleUserSelection = useCallback((users: CometChat.User[]) => {
setSelectedUsers(users);
}, []);
return (
{/* Header */}
navigation.goBack()}
>
}
/>
{t('ADD_MEMBERS')}
{/* Users List */}
navigation.goBack()}
usersStatusVisibility={userAndFriendsPresence}
/>
{/* Add Members Button */}
addMembersToGroup(selectedUsers)}
style={styles.addMembersButton}
>
{t('ADD_MEMBERS')}
{/* Error Toast */}
{errorToastVisible && (
{errorToastMessage}
)}
);
};
export default AddMember;
================================================
FILE: examples/SampleAppExpo/src/components/conversations/screens/AddMemberStyles.tsx
================================================
import { StyleSheet } from "react-native";
export const styles = StyleSheet.create({
toastTextStyle: {
color: '#fff',
fontSize: 16,
},
toastContainer: {
position: 'absolute',
bottom: 20,
left: 20,
right: 20,
paddingVertical: 10,
borderRadius: 5,
alignItems: 'center',
},
addMemberContainer: {
paddingTop: 15,
paddingLeft: 10,
flexDirection: 'row',
paddingBottom: 15,
borderBottomWidth: 1,
},
addMemberText: {
paddingLeft: 10,
},
iconContainer: {
flexDirection: 'row',
alignItems: 'center',
},
addMembersButton: {
alignContent: 'center',
justifyContent: 'center',
paddingVertical: 1,
height: 50,
width: '100%',
alignSelf: 'center',
},
addMembersButtonContainer: {
marginHorizontal: 20,
alignSelf: 'center',
justifyContent: 'center',
borderRadius: 6,
height: '75%',
width: '95%',
},
});
================================================
FILE: examples/SampleAppExpo/src/components/conversations/screens/BannedMember.tsx
================================================
import React, { useCallback, useRef, useState } from 'react';
import {
View,
Text,
TouchableOpacity,
Modal,
TouchableWithoutFeedback,
BackHandler,
} from 'react-native';
import {
CometChatConfirmDialog,
CometChatList,
CometChatListActionsInterface,
} from '@cometchat/chat-uikit-react-native/src/shared';
import { Skeleton } from '@cometchat/chat-uikit-react-native/src/CometChatUsers/Skeleton';
import {
Icon,
useCometChatTranslation,
} from '@cometchat/chat-uikit-react-native';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import {
CometChatGroupsEvents,
CometChatUIEventHandler,
CometChatUIKit,
CometChatUiKitConstants,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import { ErrorEmptyView } from '@cometchat/chat-uikit-react-native/src/shared/views/ErrorEmptyView/ErrorEmptyView';
import { RouteProp, useFocusEffect } from '@react-navigation/native';
import { RootStackParamList } from '../../../navigation/types';
import { StackNavigationProp } from '@react-navigation/stack';
import {
getBannedMemberStyleLight,
styles,
getBannedMemberStyleDark,
} from './BannedMemberStyles';
import UserEmptyIcon from '../../../assets/icons/UserEmptyIcon';
import Close from '../../../assets/icons/Close';
import Block from '../../../assets/icons/Block';
type BannedMembersRouteProp = {
route: RouteProp;
navigation: StackNavigationProp;
};
const BannedMember: React.FC = ({
route,
navigation,
}) => {
const { group } = route.params;
const theme = useTheme();
const { t } = useCometChatTranslation();
const bannedListRef = useRef(null);
const [isModalVisible, setModalVisible] = useState(false);
const [selectedUser, setSelectedUser] = useState(null);
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
// Navigate back to message screen (same as your onPress handler)
navigation.goBack();
return true; // Prevent default back behavior
};
const subscription = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
return () => subscription.remove();
}, [navigation]),
);
/**
* --- Callbacks / Handlers ---
*/
const openUnbanModal = (user: CometChat.User) => {
setSelectedUser(user);
setModalVisible(true);
};
const closeUnbanModal = () => {
setModalVisible(false);
setSelectedUser(null);
};
const handleUnbanUser = async () => {
if (!group || !selectedUser) return;
try {
const guid = group.getGuid();
const uid = selectedUser.getUid();
// Unban the user
await CometChat.unbanGroupMember(guid, uid);
// Create and dispatch an Action message for the unban event
const actionMessage = new CometChat.Action(
guid,
CometChatUiKitConstants.MessageTypeConstants.groupMember,
CometChat.RECEIVER_TYPE.GROUP,
CometChat.CATEGORY_ACTION as CometChat.MessageCategory,
);
actionMessage.setConversationId(guid);
actionMessage.setActionFor(group);
actionMessage.setActionOn(selectedUser);
actionMessage.setActionBy(CometChatUIKit.loggedInUser!);
actionMessage.setSender(CometChatUIKit.loggedInUser!);
// Initialize data to prevent crash when SDK accesses getData().metadata during render
actionMessage.setData({ metadata: {} });
actionMessage.setMessage(
`${CometChatUIKit.loggedInUser?.getName()} ${t(
'UNBANNED',
)} ${selectedUser.getName()}`,
);
CometChatUIEventHandler.emitGroupEvent(
CometChatGroupsEvents.ccGroupMemberUnBanned,
{
unbannedBy: CometChatUIKit.loggedInUser,
userUnbanned: selectedUser,
group,
message: actionMessage,
},
);
// Remove from the banned list
bannedListRef.current?.removeItemFromList(uid);
// Close modal
closeUnbanModal();
} catch (error) {
console.error('Error unbanning user:', error);
}
};
/**
* --- Render Helpers ---
*/
const renderEmptyView = useCallback(() => {
return (
}
size={theme.spacing.spacing.s20}
containerStyle={{ marginBottom: theme.spacing.spacing.s5 }}
/>
}
containerStyle={styles.emptyViewContainer}
titleStyle={[
theme.userStyles.emptyStateStyle.titleStyle,
{ color: theme.color.textPrimary },
]}
/>
);
}, [theme]);
const renderLoadingView = () => ;
const renderTailView = (user: CometChat.User) => (
openUnbanModal(user)}>
}
/>
);
const renderUnbanModal = () => {
if (!selectedUser) return null;
return (
}
cancelButtonText={t('CANCEL')}
confirmButtonText={t('UNBAN')}
messageText={t('UNBAN_SURE') + ' ' + selectedUser.getName()}
isOpen={true}
onCancel={closeUnbanModal}
onConfirm={handleUnbanUser}
/>
);
};
/**
* --- Main JSX ---
*/
return (
<>
navigation.goBack()}
listItemKey="uid"
hideBackButton={false}
LoadingView={renderLoadingView}
EmptyView={renderEmptyView}
TrailingView={renderTailView}
listStyle={
theme.mode === 'light'
? getBannedMemberStyleLight(theme)
: getBannedMemberStyleDark(theme)
}
requestBuilder={new CometChat.BannedMembersRequestBuilder(
group.getGuid(),
).setLimit(30)}
/>
{renderUnbanModal()}
>
);
};
export default BannedMember;
================================================
FILE: examples/SampleAppExpo/src/components/conversations/screens/BannedMemberStyles.tsx
================================================
import {StyleSheet} from 'react-native';
import {
CometChatListStylesInterface,
CometChatTheme,
} from '@cometchat/chat-uikit-react-native';
import {deepMerge} from '@cometchat/chat-uikit-react-native/src/shared/helper/helperFunctions';
import {DeepPartial} from '@cometchat/chat-uikit-react-native/src/shared/helper/types';
import {ColorValue, ViewStyle} from 'react-native';
export const styles = StyleSheet.create({
flexContainer: {
flex: 1,
},
emptyViewContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: '10%',
},
/* Modal Styles */
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
modalContainer: {
position: 'absolute',
top: '30%',
left: '2%',
right: '2%',
borderRadius: 10,
padding: 20,
elevation: 5,
},
modalIconContainer: {
alignSelf: 'center',
marginBottom: 10,
width: 64,
height: 64,
borderRadius: 32,
justifyContent: 'center',
alignItems: 'center',
},
modalContentContainer: {
alignItems: 'center',
},
modalTitle: {
marginBottom: 15,
},
modalDesc: {
textAlign: 'center',
marginBottom: 20,
},
buttonContainer: {
flexDirection: 'row',
},
cancelButton: {
flex: 1,
paddingVertical: 10,
marginHorizontal: 5,
borderRadius: 5,
borderWidth: 1,
alignItems: 'center',
},
unbanButton: {
flex: 1,
paddingVertical: 10,
marginHorizontal: 5,
borderRadius: 5,
alignItems: 'center',
},
});
export type BannedMemberStyle = CometChatListStylesInterface & {
skeletonStyle: {
backgroundColor: ColorValue;
linearGradientColors: [string, string];
shimmerBackgroundColor: ColorValue;
shimmerOpacity: number;
speed: number;
};
headerContainerStyle: ViewStyle;
};
export const getBannedMemberStyleLight = (
theme: CometChatTheme,
): DeepPartial => {
const {color, spacing, typography} = theme;
return {
headerContainerStyle: {
alignItems: 'flex-start',
justifyContent: 'center',
width: '100%',
borderRadius: 0,
paddingHorizontal: 0,
},
titleSeparatorStyle: {
borderBottomWidth: 1,
borderBottomColor: color.borderLight,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
},
containerStyle: {
backgroundColor: color.background1,
flex: 1,
},
itemStyle: {
containerStyle: {
flexDirection: 'row',
paddingHorizontal: spacing.padding.p4,
paddingVertical: spacing.padding.p2,
gap: spacing.spacing.s3,
},
titleStyle: {
color: color.textPrimary,
...typography.heading4.medium,
},
subtitleStyle: {
color: color.textSecondary,
...typography.body.regular,
},
statusIndicatorStyle: {},
avatarStyle: {
containerStyle: {},
textStyle: {},
imageStyle: {},
},
headViewContainerStyle: {},
titleSubtitleContainerStyle: {
alignSelf: 'center',
},
trailingViewContainerStyle: {
alignSelf: 'center',
},
},
confirmSelectionStyle: {},
selectionCancelStyle: {},
loadingIconTint: color.primary,
sectionHeaderTextStyle: {
marginHorizontal: spacing.spacing.s5,
color: color.primary,
...typography.heading4.medium,
},
onlineStatusColor: color.success,
titleViewStyle: {
paddingVertical: spacing.spacing.s3,
paddingLeft: spacing.spacing.s3,
margin: spacing.spacing.s0,
},
titleStyle: {
color: color.textPrimary,
...typography.heading1.bold,
},
backButtonIconStyle: {
tintColor: color.iconPrimary,
height: spacing.spacing.s6,
width: spacing.spacing.s6,
},
searchStyle: {
textStyle: {
color: color.textPrimary,
...typography.heading4.regular,
textAlignVertical: 'center',
paddingVertical: 0,
height: spacing.spacing.s7,
},
containerStyle: {
backgroundColor: color.background3,
paddingVertical: spacing.spacing.s3,
marginTop: spacing.spacing.s3,
width: '95%',
gap: spacing.spacing.s1,
alignContent: 'space-around',
alignSelf: 'center',
flexDirection: 'row',
alignItems: 'center',
},
icon: undefined,
iconStyle: {
tintColor: color.iconSecondary,
},
placehodlerTextStyle: {
color: color.textTertiary,
},
},
emptyStateStyle: {
titleStyle: {
color: color.textPrimary,
...typography.heading3.bold,
marginBottom: spacing.margin.m1,
},
subTitleStyle: {
color: color.textSecondary,
textAlign: 'center' as const,
...typography.body.regular,
},
containerStyle: {
justifyContent: 'center',
display: 'none',
alignItems: 'center',
padding: spacing.padding.p3,
},
},
errorStateStyle: {
titleStyle: {
color: color.textPrimary,
...typography.heading3.bold,
marginBottom: spacing.margin.m1,
},
subTitleStyle: {
color: color.textSecondary,
textAlign: 'center' as const,
...typography.body.regular,
},
containerStyle: {
justifyContent: 'center',
alignItems: 'center',
padding: spacing.padding.p3,
},
},
skeletonStyle: {
backgroundColor: color.background3,
linearGradientColors: ['#E8E8E8', '#F5F5F5'] as [string, string],
shimmerBackgroundColor: color.staticBlack,
shimmerOpacity: 0.01,
speed: 1,
},
};
};
export const getBannedMemberStyleDark = (
theme: CometChatTheme,
): DeepPartial => {
const {color, spacing, typography} = theme;
return deepMerge(getBannedMemberStyleLight(theme), {
skeletonStyle: {
backgroundColor: color.background3,
linearGradientColors: ['#383838', '#272727'] as [string, string],
shimmerBackgroundColor: color.staticWhite,
shimmerOpacity: 0.01,
speed: 1,
},
});
};
================================================
FILE: examples/SampleAppExpo/src/components/conversations/screens/Conversations.tsx
================================================
import {CometChat} from '@cometchat/chat-sdk-react-native';
import React, {useCallback, useContext, useRef, useState} from 'react';
import {TouchableOpacity, View, Platform} from 'react-native';
import {
CometChatAvatar,
CometChatConversations,
CometChatUIKit,
useCometChatTranslation,
useTheme,
} from '@cometchat/chat-uikit-react-native';
import {AuthContext} from '../../../navigation/AuthContext';
import {
useFocusEffect,
useNavigation,
CommonActions,
} from '@react-navigation/native';
import {TooltipMenu} from '../../../utils/TooltipMenu';
import {StackNavigationProp} from '@react-navigation/stack';
import {RootStackParamList} from '../../../navigation/types';
import AccountCircle from '../../../assets/icons/AccountCircle';
import AddComment from '../../../assets/icons/AddComment';
import InfoIcon from '../../../assets/icons/InfoIcon';
import Logout from '../../../assets/icons/Logout';
import {navigate, navigationRef} from '../../../navigation/NavigationService';
import {AppConstants, SCREEN_CONSTANTS} from '../../../utils/AppConstants';
import Builder from '../../../assets/icons/Builder';
import { useConfig, useConfigStore } from '../../../config/store'; // adjust import if needed
import AsyncStorage from '@react-native-async-storage/async-storage';
import Reset from '../../../assets/icons/Reset';
import AiIcon from '../../../assets/icons/AiIcon';
type ChatNavigationProp = StackNavigationProp<
RootStackParamList,
'Conversation'
>;
const Conversations: React.FC<{}> = ({}) => {
const theme = useTheme();
const {setIsLoggedIn: setLogout} = useContext(AuthContext);
const [isLoggingOut, setIsLoggingOut] = useState(false);
const tooltipPositon = React.useRef({pageX: 0, pageY: 0});
const [tooltipVisible, setTooltipVisible] = useState(false);
const selectedConversation = useRef(null);
const navigation = useNavigation();
const avatarContainerRef = useRef(null);
const loggedInUser = useRef(
CometChatUIKit.loggedInUser,
).current;
const { t } = useCometChatTranslation();
const [isConfigUpdated, setIsConfigUpdated] = useState(false);
const messageDeliveryAndReadReceipts = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.messageDeliveryAndReadReceipts
);
useFocusEffect(
useCallback(() => {
// Check config updated flag
AsyncStorage.getItem('@config_updated').then(val => {
setIsConfigUpdated(val === 'true');
});
return () => {
setTooltipVisible(false);
};
}, []),
);
const handleResetConfig = async () => {
useConfigStore.getState().resetConfig();
await AsyncStorage.removeItem('@config_updated');
setIsConfigUpdated(false);
};
const userAndFriendsPresence = useConfig(
(state) => state.settings.chatFeatures.coreMessagingExperience.userAndFriendsPresence
);
const openMessagesFor = (item: CometChat.Conversation) => {
// Determine if it's a user or group conversation
const isUser = item.getConversationType() === 'user';
const isGroup = item.getConversationType() === 'group';
// Navigate to Messages with appropriate params
navigation.navigate('Messages', {
user: isUser ? (item.getConversationWith() as CometChat.User) : undefined,
group: isGroup
? (item.getConversationWith() as CometChat.Group)
: undefined,
});
};
const _conversationsConfig = {
onItemPress: openMessagesFor,
onError: (err: any) => {
console.log('ERROR IN CONVO: ', err);
},
};
const handleAvatarPress = () => {
try {
if (avatarContainerRef.current) {
avatarContainerRef.current.measureInWindow((x, y, height) => {
// Set tooltip position 10px below the avatar
tooltipPositon.current = {pageX: x, pageY: y + height};
});
selectedConversation.current = null;
setTooltipVisible(true);
}
} catch (error) {
console.error('Error while handling avatar press:', error);
}
};
const handleLogout = async () => {
if (isLoggingOut) return;
setIsLoggingOut(true);
// Step 1: Logout from CometChat
try {
await CometChat.logout();
} catch (error) {
console.error('CometChat logout failed:', error);
setIsLoggingOut(false);
return; // Exit if CometChat logout fails
}
// If all operations succeed, navigate to the LoginScreen
setIsLoggingOut(false);
setLogout(false);
navigationRef.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'SampleUser' }],
}),
);
};
const NewConversation = () => {
return (
{
handleAvatarPress();
}}>
);
};
return (
{
navigate(SCREEN_CONSTANTS.SEARCH_MESSAGES);
}}
usersStatusVisibility={userAndFriendsPresence}
receiptsVisibility={messageDeliveryAndReadReceipts}
/>
{
setTooltipVisible(false);
}}
onDismiss={() => {
setTooltipVisible(false);
}}
event={{
nativeEvent: tooltipPositon.current,
}}
menuItems={[
{
text: t('CREATE_CONVERSATION'),
onPress: () => {
navigation.navigate('CreateConversation');
},
icon: (
),
textColor: theme.color.textPrimary,
iconColor: theme.color.textPrimary,
},
{
text: t('AI_ASSISTANTS'),
onPress: () => {
navigation.navigate(SCREEN_CONSTANTS.AI_AGENTS);
},
icon: (
),
textColor: theme.color.textPrimary,
iconColor: theme.color.textPrimary,
},
{
text: loggedInUser?.getName() || 'User',
onPress: () => {
setTooltipVisible(false);
},
icon: (
),
textColor: theme.color.textPrimary,
iconColor: theme.color.textPrimary,
},
{
text: t('LOGOUT'),
onPress: () => {
handleLogout();
},
icon: (
),
textColor: theme.color.error,
iconColor: theme.color.error,
},
{
text: AppConstants.versionNumber,
onPress: () => {},
icon: (
),
},
isConfigUpdated
? {
text: 'Reset to Default',
onPress: handleResetConfig,
icon: (
),
textColor: theme.color.textPrimary,
iconColor: theme.color.textPrimary,
}
: {
text: 'Builder Live Preview',
onPress: () => {
navigation.navigate(SCREEN_CONSTANTS.QR_SCREEN);
},
icon: (
),
textColor: theme.color.textPrimary,
iconColor: theme.color.textPrimary,
},
]}
/>
);
};
export default Conversations;
================================================
FILE: examples/SampleAppExpo/src/components/conversations/screens/CreateConversation.tsx
================================================
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native';
import {
useTheme,
CometChatUsers,
useCometChatTranslation,
} from '@cometchat/chat-uikit-react-native';
import { RootStackParamList } from '../../../navigation/types';
import Groups from '../../groups/Groups';
import { Icon } from '@cometchat/chat-uikit-react-native';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import ArrowBack from '../../../assets/icons/ArrowBack';
// Define prop types for the component using React Navigation's types
type Props = {
route: RouteProp;
navigation: StackNavigationProp;
};
const CreateConversation: React.FC = ({ route, navigation }) => {
const theme = useTheme();
const { t } = useCometChatTranslation();
const {
background1,
background3,
iconPrimary,
textPrimary,
textSecondary,
primary,
} = theme.color;
const { heading1 } = theme.typography;
const [selectedTab, setSelectedTab] = useState<'Users' | 'Groups'>('Users');
return (
{/* Header */}
}
/>
{t('NEW_CHAT')}
{/* Tab Bar */}
{['Users', 'Groups'].map(tab => (
setSelectedTab(tab as 'Users' | 'Groups')}
>
{t(tab.toUpperCase())}
))}
{/* Content */}
{selectedTab === 'Users' ? (
navigation.navigate('Messages', { user })
}
/>
) : (
)}
);
};
export default CreateConversation;
// Style definitions for the component
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
paddingTop: 15,
paddingLeft: 10,
flexDirection: 'row',
alignItems: 'center',
},
rowCenter: {
flexDirection: 'row',
alignItems: 'center',
},
headerText: {
paddingLeft: 5,
},
tabContainer: {
marginTop: 20,
flexDirection: 'row',
marginHorizontal: 20,
borderRadius: 30,
padding: 5,
},
tabButton: {
flex: 1,
paddingVertical: 10,
alignItems: 'center',
borderRadius: 30,
},
activeTab: {
borderRadius: 30,
},
tabText: {
fontSize: 16,
fontWeight: 'bold',
},
content: {
flex: 1,
},
});
================================================
FILE: examples/SampleAppExpo/src/components/conversations/screens/GroupInfo.tsx
================================================
import React, { useEffect, useRef, useState } from 'react';
import {
View,
Text,
TouchableOpacity,
useWindowDimensions,
BackHandler,
} from 'react-native';
import { RouteProp, useFocusEffect } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import {
CometChatAvatar,
CometChatGroupsEvents,
CometChatUIEventHandler,
CometChatConfirmDialog,
useTheme,
CometChatConversationEvents,
CometChatUIEvents,
useCometChatTranslation,
} from '@cometchat/chat-uikit-react-native';
import { Icon } from '@cometchat/chat-uikit-react-native';
import { CometChat } from '@cometchat/chat-sdk-react-native';
import {
CometChatUIKit,
CometChatUiKitConstants,
} from '@cometchat/chat-uikit-react-native';
import { RootStackParamList } from '../../../navigation/types';
import { listners } from '../helper/GroupListeners';
import { styles } from './GroupInfoStyles';
import { leaveGroup } from '../../../utils/helper';
import { CommonUtils } from '../../../utils/CommonUtils';
import ArrowBack from '../../../assets/icons/ArrowBack';
import Group from '../../../assets/icons/Group';
import PersonAdd from '../../../assets/icons/PersonAdd';
import PersonOff from '../../../assets/icons/PersonOff';
import Block from '../../../assets/icons/Block';
import Delete from '../../../assets/icons/Delete';
import { useConfig } from '../../../config/store';
type GroupInfoProps = {
route: RouteProp;
navigation: StackNavigationProp;
};
const GroupInfo: React.FC = ({route, navigation}) => {
const addMembersToGroups = useConfig(
(state) => state.settings.chatFeatures.groupManagement.addMembersToGroups
);
const joinLeaveGroup = useConfig(
(state) => state.settings.chatFeatures.groupManagement.joinLeaveGroup
);
const deleteGroup = useConfig(
(state) => state.settings.chatFeatures.groupManagement.deleteGroup
);
const viewGroupMembers = useConfig(
(state) => state.settings.chatFeatures.groupManagement.viewGroupMembers
);
const { group } = route.params;
const theme = useTheme();
const { t } = useCometChatTranslation();
const groupListenerId = useRef('groupListener' + new Date().getTime());
const [data, setData] = useState({ groupDetails: group });
const [userScope, setUserScope] = useState(
group?.getOwner() === CometChatUIKit.loggedInUser?.getUid()
? CometChatUiKitConstants.GroupMemberScope.owner
: group?.getScope(),
);
// Separate states for each type of modal
const [isLeaveModalOpen, setIsLeaveModalOpen] = useState(false);
const [isOwnerLeaveModalOpen, setIsOwnerLeaveModalOpen] = useState(false);
const [isDeleteExitModalOpen, setIsDeleteExitModalOpen] = useState(false);
const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);
const { width } = useWindowDimensions();
const isSmallDevice = width < 360;
useEffect(() => {
// Update group details in state whenever group changes
const handleGroupListener = (updatedGroup: CometChat.Group) => {
if (updatedGroup.getGuid() === route.params.group.getGuid()) {
setData({ groupDetails: updatedGroup });
setUserScope(
updatedGroup?.getOwner() === CometChatUIKit.loggedInUser?.getUid()
? CometChatUiKitConstants.GroupMemberScope.owner
: (updatedGroup?.getScope() ?? userScope),
);
}
};
const handleGroupMemberKicked = ({ kickedFrom }: any) => {
handleGroupListener(CommonUtils.clone(kickedFrom));
};
const handleGroupMemberBanned = ({ kickedFrom }: any) => {
handleGroupListener(CommonUtils.clone(kickedFrom));
};
const handleGroupMemberAdded = ({ userAddedIn }: any) => {
handleGroupListener(CommonUtils.clone(userAddedIn));
};
const handleOwnershipChanged = ({ group }: any) => {
handleGroupListener(group);
};
// Add group listeners
listners.addListener.groupListener({
groupListenerId: groupListenerId.current,
handleGroupListener,
});
CometChatUIEventHandler.addGroupListener(groupListenerId.current, {
ccGroupMemberKicked: (item: any) => handleGroupMemberKicked(item),
ccGroupMemberBanned: (item: any) => handleGroupMemberBanned(item),
ccGroupMemberAdded: (item: any) => handleGroupMemberAdded(item),
ccOwnershipChanged: (item: any) => handleOwnershipChanged(item),
});
return () => {
// Cleanup
listners.removeListner.removeGroupListener({
groupListenerId: groupListenerId.current,
});
CometChatUIEventHandler.removeGroupListener(groupListenerId.current);
CometChat.removeGroupListener(groupListenerId.current);
};
}, [group, userScope]);
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
// Navigate back to message screen (same as your onPress handler)
navigation.goBack();
return true; // Prevent default back behavior
};
const subscription = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
return () => subscription.remove();
}, [navigation]),
);
const getLabel = (key: string) => {
const label = t(key);
// Split into two words if device is small
if (isSmallDevice && label.split(' ').length === 2) {
return label.split(' ').join('\n');
}
return label;
};
/**
* Handlers for each modal's confirm action
*/
// 1) Normal "Leave Group" confirm
const handleLeaveConfirm = () => {
setIsLeaveModalOpen(false);
if (data.groupDetails) {
leaveGroup(data.groupDetails, navigation, 2);
}
};
// 2) "Owner => Transfer Ownership" confirm
const handleOwnerLeaveConfirm = () => {
if (!data.groupDetails) return;
setIsOwnerLeaveModalOpen(false);
navigation.navigate('TransferOwnershipSection', {
group: data.groupDetails,
});
};
// 3) "Delete and Exit" confirm
const handleDeleteExitConfirm = () => {
setIsDeleteExitModalOpen(false);
if (!data.groupDetails) return;
// Delete group
CometChat.deleteGroup(data.groupDetails.getGuid())
.then(() => {
navigation.pop(2);
})
.catch(error => {
console.log('Group deletion failed:', error);
});
// Emit group deleted event
CometChatUIEventHandler.emitGroupEvent(
CometChatGroupsEvents.ccGroupDeleted,
{
group: data.groupDetails,
},
);
};
/** DELETE CONVERSATION LOGIC **/
const handleDeleteConversationConfirm = () => {
setDeleteModalOpen(false); // close the dialog
if (group) {
CometChat.getConversation(group.getGuid(), 'group')
.then(conversation => {
CometChat.deleteConversation(group.getGuid(), 'group')
.then(deletedConversation => {
console.log(deletedConversation);
CometChatUIEventHandler.emitConversationEvent(
CometChatConversationEvents.ccConversationDeleted,
{ conversation: conversation },
);
navigation.pop(2);
})
.catch(error => {
console.log('Error while deleting conversation:', error);
});
})
.catch(error => {
console.log('Error while deleting conversation:', error);
});
}
};
return (
{/* Header */}
navigation.goBack()}
>