├── assets
├── icon.png
├── favicon.png
├── splash.png
└── adaptive-icon.png
├── babel.config.js
├── App.js
├── .gitignore
├── src
├── chatkitty
│ └── index.js
├── components
│ ├── loading.js
│ ├── formInput.js
│ └── formButton.js
├── context
│ ├── authStack.js
│ ├── index.js
│ ├── routes.js
│ ├── authProvider.js
│ ├── homeStack.js
│ └── notificationProvider.js
├── firebase
│ └── index.js
└── screens
│ ├── loginScreen.js
│ ├── browseChannelsScreen.js
│ ├── createChannelScreen.js
│ ├── signupScreen.js
│ ├── homeScreen.js
│ └── chatScreen.js
├── app.json
└── package.json
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChatKitty/chatkitty-example-react-native/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChatKitty/chatkitty-example-react-native/HEAD/assets/favicon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChatKitty/chatkitty-example-react-native/HEAD/assets/splash.png
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChatKitty/chatkitty-example-react-native/HEAD/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Providers from './src/context';
4 |
5 | export default function App() {
6 | return ;
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | dist/
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 |
13 | # macOS
14 | .DS_Store
15 |
16 | # Temporary files created by Metro to check the health of the file watcher
17 | .metro-health-check*
18 |
--------------------------------------------------------------------------------
/src/chatkitty/index.js:
--------------------------------------------------------------------------------
1 | import ChatKitty from '@chatkitty/core';
2 |
3 | export const chatkitty = ChatKitty.getInstance('eb8472e5-db71-48fa-a92a-633bbbd3fcf8');
4 |
5 | export function channelDisplayName(channel) {
6 | if (channel.type === 'DIRECT') {
7 | return channel.members.map((member) => member.displayName).join(', ');
8 | } else {
9 | return channel.name;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ActivityIndicator, StyleSheet, View } from 'react-native';
3 |
4 | export default function Loading() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
12 | const styles = StyleSheet.create({
13 | loadingContainer: {
14 | flex: 1,
15 | alignItems: 'center',
16 | justifyContent: 'center'
17 | }
18 | });
19 |
--------------------------------------------------------------------------------
/src/context/authStack.js:
--------------------------------------------------------------------------------
1 | import { createStackNavigator } from '@react-navigation/stack';
2 | import React from 'react';
3 |
4 | import LoginScreen from '../screens/loginScreen';
5 | import SignupScreen from '../screens/signupScreen';
6 |
7 | const Stack = createStackNavigator();
8 |
9 | export default function AuthStack() {
10 | return (
11 |
12 |
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/formInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Dimensions, StyleSheet } from 'react-native';
3 | import { TextInput } from 'react-native-paper';
4 |
5 | const { width, height } = Dimensions.get('screen');
6 |
7 | export default function FormInput({ labelName, ...rest }) {
8 | return (
9 |
15 | );
16 | }
17 |
18 | const styles = StyleSheet.create({
19 | input: {
20 | marginTop: 10,
21 | marginBottom: 10,
22 | width: width / 1.5,
23 | height: height / 15
24 | }
25 | });
26 |
--------------------------------------------------------------------------------
/src/components/formButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Dimensions, StyleSheet } from 'react-native';
3 | import { Button } from 'react-native-paper';
4 |
5 | const { width, height } = Dimensions.get('screen');
6 |
7 | export default function FormButton({ title, modeValue, ...rest }) {
8 | return (
9 |
17 | );
18 | }
19 |
20 | const styles = StyleSheet.create({
21 | button: {
22 | marginTop: 10
23 | },
24 | buttonContainer: {
25 | width: width / 2,
26 | height: height / 15
27 | }
28 | });
29 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "chatkitty-example-react-native",
4 | "slug": "chatkitty-example-react-native",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "light",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff"
13 | },
14 | "assetBundlePatterns": [
15 | "**/*"
16 | ],
17 | "ios": {
18 | "supportsTablet": true
19 | },
20 | "android": {
21 | "adaptiveIcon": {
22 | "foregroundImage": "./assets/adaptive-icon.png",
23 | "backgroundColor": "#ffffff"
24 | }
25 | },
26 | "web": {
27 | "favicon": "./assets/favicon.png"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/context/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DefaultTheme, Provider as PaperProvider } from 'react-native-paper';
3 |
4 | import { AuthProvider } from './authProvider';
5 | import { NotificationProvider } from './notificationProvider';
6 | import Routes from './routes';
7 |
8 | export default function Providers() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | const theme = {
21 | ...DefaultTheme,
22 | roundness: 2,
23 | colors: {
24 | ...DefaultTheme.colors,
25 | primary: '#5b3a70',
26 | accent: '#50c878',
27 | background: '#f7f9fb'
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/src/firebase/index.js:
--------------------------------------------------------------------------------
1 | import AsyncStorage from '@react-native-async-storage/async-storage';
2 | import { initializeApp } from 'firebase/app';
3 | import {
4 | initializeAuth,
5 | getReactNativePersistence
6 | } from 'firebase/auth/react-native';
7 |
8 |
9 | // Replace this with your Firebase SDK config snippet
10 | const firebaseConfig = {
11 | apiKey: "AIzaSyBt4Su-ZxyUFHCrBpBXzSaEzuisGKMar_w",
12 | authDomain: "chatkitty-expo-review.firebaseapp.com",
13 | projectId: "chatkitty-expo-review",
14 | storageBucket: "chatkitty-expo-review.appspot.com",
15 | messagingSenderId: "891463062190",
16 | appId: "1:891463062190:web:ef345c8bdeb8748bc3133f"
17 | };
18 |
19 | // Initialize Firebase
20 | const app = initializeApp(firebaseConfig);
21 |
22 | // Initialize Auth
23 | const auth = initializeAuth(app, {
24 | persistence: getReactNativePersistence(AsyncStorage)
25 | });
26 |
27 | export { auth };
28 |
--------------------------------------------------------------------------------
/src/context/routes.js:
--------------------------------------------------------------------------------
1 | import { NavigationContainer } from '@react-navigation/native';
2 | import React, { useContext, useState, useEffect } from 'react';
3 |
4 | import { chatkitty } from '../chatkitty';
5 | import Loading from '../components/loading';
6 |
7 | import AuthStack from './authStack';
8 | import HomeStack from './homeStack';
9 | import { AuthContext } from './authProvider';
10 |
11 | export default function Routes() {
12 | const { user, setUser } = useContext(AuthContext);
13 | const [loading, setLoading] = useState(true);
14 | const [initializing, setInitializing] = useState(true);
15 |
16 | useEffect(() => {
17 | return chatkitty.onCurrentUserChanged((currentUser) => {
18 | setUser(currentUser);
19 |
20 | if (initializing) {
21 | setInitializing(false);
22 | }
23 |
24 | setLoading(false);
25 | });
26 | }, [initializing, setUser]);
27 |
28 | if (loading) {
29 | return ;
30 | }
31 |
32 | return (
33 |
34 | {user ? : }
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chatkitty-example-react-native",
3 | "version": "1.0.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web"
10 | },
11 | "dependencies": {
12 | "@chatkitty/core": "^1.2.13",
13 | "@react-native-async-storage/async-storage": "1.17.11",
14 | "@react-native-community/masked-view": "^0.1.11",
15 | "@react-navigation/native": "^6.1.6",
16 | "@react-navigation/stack": "^6.3.16",
17 | "expo": "~48.0.9",
18 | "expo-status-bar": "~1.4.4",
19 | "firebase": "^9.18.0",
20 | "react": "18.2.0",
21 | "react-native": "0.71.4",
22 | "react-native-gesture-handler": "~2.9.0",
23 | "react-native-gifted-chat": "^2.0.1",
24 | "react-native-paper": "^5.5.1",
25 | "react-native-reanimated": "~2.14.4",
26 | "react-native-safe-area-context": "4.5.0",
27 | "react-native-screens": "~3.20.0",
28 | "react-native-vector-icons": "^9.2.0",
29 | "expo-device": "~5.2.1",
30 | "expo-notifications": "~0.18.1"
31 | },
32 | "devDependencies": {
33 | "@babel/core": "^7.20.0"
34 | },
35 | "private": true
36 | }
37 |
--------------------------------------------------------------------------------
/src/screens/loginScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from 'react';
2 | import { StyleSheet, View } from 'react-native';
3 | import { Title } from 'react-native-paper';
4 |
5 | import FormButton from '../components/formButton.js';
6 | import FormInput from '../components/formInput.js';
7 | import Loading from '../components/loading.js';
8 | import { AuthContext } from '../context/authProvider.js';
9 |
10 | export default function LoginScreen({ navigation }) {
11 | const [email, setEmail] = useState('');
12 | const [password, setPassword] = useState('');
13 |
14 | const { login, loading } = useContext(AuthContext);
15 |
16 | if (loading) {
17 | return ;
18 | }
19 |
20 | return (
21 |
22 | Welcome!
23 | setEmail(userEmail)}
28 | />
29 | setPassword(userPassword)}
34 | />
35 | {
40 | login(email, password);
41 | }}
42 | />
43 | navigation.navigate('Signup')}
49 | />
50 |
51 | );
52 | }
53 |
54 | const styles = StyleSheet.create({
55 | container: {
56 | backgroundColor: '#f5f5f5',
57 | flex: 1,
58 | justifyContent: 'center',
59 | alignItems: 'center'
60 | },
61 | titleText: {
62 | fontSize: 24,
63 | marginBottom: 10
64 | },
65 | loginButtonLabel: {
66 | fontSize: 22
67 | },
68 | navButtonText: {
69 | fontSize: 16
70 | }
71 | });
72 |
--------------------------------------------------------------------------------
/src/screens/browseChannelsScreen.js:
--------------------------------------------------------------------------------
1 | import { useIsFocused } from '@react-navigation/native';
2 | import React, { useEffect, useState } from 'react';
3 | import { FlatList, StyleSheet, View } from 'react-native';
4 | import { Divider, List } from 'react-native-paper';
5 |
6 | import { chatkitty, channelDisplayName } from '../chatkitty';
7 | import Loading from '../components/loading';
8 |
9 | export default function BrowseChannelsScreen({ navigation }) {
10 | const [channels, setChannels] = useState([]);
11 | const [loading, setLoading] = useState(true);
12 |
13 | const isFocused = useIsFocused();
14 |
15 | useEffect(() => {
16 | chatkitty.listChannels({ filter: { joined: false } }).then((result) => {
17 | setChannels(result.paginator.items);
18 |
19 | if (loading) {
20 | setLoading(false);
21 | }
22 | });
23 | }, [isFocused, loading]);
24 |
25 | async function handleJoinChannel(channel) {
26 | const result = await chatkitty.joinChannel({ channel: channel });
27 |
28 | navigation.navigate('Chat', { channel: result.channel });
29 | }
30 |
31 | if (loading) {
32 | return ;
33 | }
34 |
35 | return (
36 |
37 | item.id.toString()}
40 | ItemSeparatorComponent={() => }
41 | renderItem={({ item }) => (
42 | handleJoinChannel(item)}
50 | />
51 | )}
52 | />
53 |
54 | );
55 | }
56 |
57 | const styles = StyleSheet.create({
58 | container: {
59 | backgroundColor: '#f5f5f5',
60 | flex: 1
61 | },
62 | listTitle: {
63 | fontSize: 22
64 | },
65 | listDescription: {
66 | fontSize: 16
67 | }
68 | });
69 |
--------------------------------------------------------------------------------
/src/screens/createChannelScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { StyleSheet, View } from 'react-native';
3 | import { IconButton, Title } from 'react-native-paper';
4 |
5 | import { chatkitty } from '../chatkitty';
6 | import FormButton from '../components/formButton';
7 | import FormInput from '../components/formInput';
8 |
9 | export default function CreateChannelScreen({ navigation }) {
10 | const [channelName, setChannelName] = useState('');
11 |
12 | function handleButtonPress() {
13 | if (channelName.length > 0) {
14 | chatkitty
15 | .createChannel({
16 | type: 'PUBLIC',
17 | name: channelName
18 | })
19 | .then(() => navigation.navigate('Home'));
20 | }
21 | }
22 |
23 | return (
24 |
25 |
26 | navigation.goBack()}
31 | />
32 |
33 |
34 | Create a new channel
35 | setChannelName(text)}
39 | clearButtonMode='while-editing'
40 | />
41 | handleButtonPress()}
46 | disabled={channelName.length === 0}
47 | />
48 |
49 |
50 | );
51 | }
52 |
53 | const styles = StyleSheet.create({
54 | rootContainer: {
55 | flex: 1
56 | },
57 | closeButtonContainer: {
58 | position: 'absolute',
59 | top: 30,
60 | right: 0,
61 | zIndex: 1
62 | },
63 | innerContainer: {
64 | flex: 1,
65 | justifyContent: 'center',
66 | alignItems: 'center'
67 | },
68 | title: {
69 | fontSize: 24,
70 | marginBottom: 10
71 | },
72 | buttonLabel: {
73 | fontSize: 22
74 | }
75 | });
76 |
--------------------------------------------------------------------------------
/src/screens/signupScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from 'react';
2 | import { StyleSheet, View } from 'react-native';
3 | import { IconButton, Title } from 'react-native-paper';
4 |
5 | import FormButton from '../components/formButton';
6 | import FormInput from '../components/formInput';
7 | import Loading from '../components/loading';
8 | import { AuthContext } from '../context/authProvider';
9 |
10 | export default function SignupScreen({ navigation }) {
11 | const [displayName, setDisplayName] = useState('');
12 | const [email, setEmail] = useState('');
13 | const [password, setPassword] = useState('');
14 |
15 | const { register, loading } = useContext(AuthContext);
16 |
17 | if (loading) {
18 | return ;
19 | }
20 |
21 | return (
22 |
23 | Let's get started!
24 | setDisplayName(userDisplayName)}
29 | />
30 | setEmail(userEmail)}
35 | />
36 | setPassword(userPassword)}
41 | />
42 | register(displayName, email, password)}
47 | />
48 | navigation.goBack()}
54 | />
55 |
56 | );
57 | }
58 |
59 | const styles = StyleSheet.create({
60 | container: {
61 | backgroundColor: '#f5f5f5',
62 | flex: 1,
63 | justifyContent: 'center',
64 | alignItems: 'center'
65 | },
66 | titleText: {
67 | fontSize: 24,
68 | marginBottom: 10
69 | },
70 | loginButtonLabel: {
71 | fontSize: 22
72 | },
73 | navButtonText: {
74 | fontSize: 18
75 | },
76 | navButton: {
77 | marginTop: 10
78 | }
79 | });
80 |
--------------------------------------------------------------------------------
/src/context/authProvider.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useState } from 'react';
2 | import {
3 | createUserWithEmailAndPassword,
4 | updateProfile,
5 | signInWithEmailAndPassword
6 | } from 'firebase/auth/react-native';
7 | import { auth } from '../firebase';
8 | import { chatkitty } from '../chatkitty';
9 |
10 | export const AuthContext = createContext({});
11 |
12 | export const AuthProvider = ({ children }) => {
13 | const [user, setUser] = useState(null);
14 | const [loading, setLoading] = useState(false);
15 |
16 | return (
17 | {
24 | setLoading(true);
25 |
26 | try {
27 | const userCredential = await signInWithEmailAndPassword(auth,
28 | email, password);
29 |
30 | // Signed-in Firebase user
31 | const currentUser = userCredential.user;
32 |
33 | const result = await chatkitty.startSession({
34 | username: currentUser.uid,
35 | authParams: {
36 | idToken: await currentUser.getIdToken()
37 | }
38 | });
39 |
40 | if (result.failed) {
41 | console.log('could not login');
42 | }
43 | } catch (error) {
44 | console.error(error);
45 | } finally {
46 | setLoading(false);
47 | }
48 | },
49 |
50 | register: async (displayName, email, password) => {
51 | setLoading(true);
52 |
53 | try {
54 | const userCredential = await createUserWithEmailAndPassword(
55 | auth, email, password);
56 |
57 | await updateProfile(auth.currentUser, {
58 | displayName: displayName
59 | });
60 |
61 | // Signed-in Firebase user
62 | const currentUser = userCredential.user;
63 |
64 | const startSessionResult = await chatkitty.startSession({
65 | username: currentUser.uid,
66 | authParams: {
67 | idToken: await currentUser.getIdToken()
68 | }
69 | });
70 |
71 | if (startSessionResult.failed) {
72 | console.log('Could not sign up');
73 | }
74 | } catch (error) {
75 | console.error(error);
76 | } finally {
77 | setLoading(false);
78 | }
79 | },
80 | logout: async () => {
81 | try {
82 | await chatkitty.endSession();
83 | } catch (error) {
84 | console.error(error);
85 | }
86 | }
87 | }}
88 | >
89 | {children}
90 |
91 | );
92 | };
93 |
--------------------------------------------------------------------------------
/src/context/homeStack.js:
--------------------------------------------------------------------------------
1 | import { createStackNavigator } from '@react-navigation/stack';
2 | import React, { useContext, useEffect } from 'react';
3 | import { IconButton } from 'react-native-paper';
4 |
5 | import { chatkitty, channelDisplayName } from '../chatkitty';
6 |
7 | import { NotificationContext } from './notificationProvider';
8 |
9 | import BrowseChannelsScreen from '../screens/browseChannelsScreen';
10 | import ChatScreen from '../screens/chatScreen';
11 | import CreateChannelScreen from '../screens/createChannelScreen';
12 | import HomeScreen from '../screens/homeScreen';
13 |
14 | const ChatStack = createStackNavigator();
15 | const ModalStack = createStackNavigator();
16 |
17 | export default function HomeStack() {
18 | const { registerForPushNotifications, sendNotification } = useContext(NotificationContext);
19 |
20 | useEffect(() => {
21 | registerForPushNotifications();
22 |
23 | chatkitty.onNotificationReceived(async (notification) => {
24 | await sendNotification({
25 | title: notification.title,
26 | body: notification.body
27 | });
28 | });
29 | }, []);
30 |
31 | return (
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | function ChatComponent() {
40 | return (
41 |
52 | ({
56 | headerRight: () => (
57 | navigation.navigate('BrowseChannels')}
62 | />
63 | )
64 | })}
65 | />
66 | ({
70 | headerRight: () => (
71 | navigation.navigate('CreateChannel')}
76 | />
77 | )
78 | })}
79 | />
80 | ({
84 | title: channelDisplayName(route.params.channel)
85 | })}
86 | />
87 |
88 | );
89 | }
90 |
--------------------------------------------------------------------------------
/src/screens/homeScreen.js:
--------------------------------------------------------------------------------
1 | import { useIsFocused } from '@react-navigation/native';
2 | import React, { useEffect, useState } from 'react';
3 | import { FlatList, StyleSheet, View } from 'react-native';
4 | import { Button, Dialog, Divider, List, Portal } from 'react-native-paper';
5 |
6 | import { chatkitty, channelDisplayName } from '../chatkitty';
7 | import Loading from '../components/loading';
8 |
9 | export default function HomeScreen({ navigation }) {
10 | const [channels, setChannels] = useState([]);
11 | const [loading, setLoading] = useState(true);
12 | const [leaveChannel, setLeaveChannel] = useState(null);
13 |
14 | const isFocused = useIsFocused();
15 |
16 | useEffect(() => {
17 | let isCancelled = false;
18 |
19 | chatkitty.listChannels({ filter: { joined: true } }).then((result) => {
20 | if (!isCancelled) {
21 | setChannels(result.paginator.items);
22 |
23 | if (loading) {
24 | setLoading(false);
25 | }
26 | }
27 | });
28 |
29 | return () => {
30 | isCancelled = true;
31 | };
32 | }, [isFocused, loading]);
33 |
34 | if (loading) {
35 | return ;
36 | }
37 |
38 | function handleLeaveChannel() {
39 | chatkitty.leaveChannel({ channel: leaveChannel }).then(() => {
40 | setLeaveChannel(null);
41 |
42 | chatkitty.listChannels({ filter: { joined: true } }).then((result) => {
43 | setChannels(result.paginator.items);
44 | });
45 | });
46 | }
47 |
48 | function handleDismissLeaveChannel() {
49 | setLeaveChannel(null);
50 | }
51 |
52 | return (
53 |
54 | item.id.toString()}
57 | ItemSeparatorComponent={() => }
58 | renderItem={({ item }) => (
59 | navigation.navigate('Chat', { channel: item })}
67 | onLongPress={() => {
68 | setLeaveChannel(item);
69 | }}
70 | />
71 | )}
72 | />
73 |
74 |
81 |
82 |
83 | );
84 | }
85 |
86 | const styles = StyleSheet.create({
87 | container: {
88 | backgroundColor: '#f5f5f5',
89 | flex: 1
90 | },
91 | listTitle: {
92 | fontSize: 22
93 | },
94 | listDescription: {
95 | fontSize: 16
96 | }
97 | });
98 |
--------------------------------------------------------------------------------
/src/context/notificationProvider.js:
--------------------------------------------------------------------------------
1 | import { createContext, useEffect, useRef, useState } from 'react';
2 | import { Platform } from 'react-native';
3 |
4 | import * as Device from 'expo-device';
5 | import * as Notifications from 'expo-notifications';
6 | import { chatkitty } from '../chatkitty';
7 |
8 | Notifications.setNotificationHandler({
9 | handleNotification: async () => ({
10 | shouldShowAlert: true,
11 | shouldPlaySound: false,
12 | shouldSetBadge: false
13 | })
14 | });
15 |
16 | export const NotificationContext = createContext({});
17 |
18 | export const NotificationProvider = ({ children }) => {
19 | const [notification, setNotification] = useState(null);
20 | const notificationListener = useRef();
21 | const responseListener = useRef();
22 |
23 | useEffect(() => {
24 | notificationListener.current =
25 | Notifications.addNotificationReceivedListener((notification) => {
26 | setNotification(notification);
27 | });
28 |
29 | responseListener.current =
30 | Notifications.addNotificationResponseReceivedListener((response) => {
31 | console.log(response);
32 | });
33 |
34 | return () => {
35 | Notifications.removeNotificationSubscription(
36 | notificationListener.current
37 | );
38 | Notifications.removeNotificationSubscription(responseListener.current);
39 | };
40 | }, []);
41 |
42 | return (
43 | {
47 | await Notifications.scheduleNotificationAsync({
48 | content,
49 | trigger: null
50 | });
51 | },
52 | registerForPushNotifications: async () => {
53 | let token;
54 | if (Device.isDevice) {
55 | const { status: existingStatus } = await Notifications.getPermissionsAsync();
56 | let finalStatus = existingStatus;
57 | if (existingStatus !== 'granted') {
58 | const { status } = await Notifications.requestPermissionsAsync();
59 | finalStatus = status;
60 | }
61 | if (finalStatus !== 'granted') {
62 | alert('Failed to get push token for push notification!');
63 | return;
64 | }
65 | token = (await Notifications.getExpoPushTokenAsync()).data;
66 | console.log(token);
67 | } else {
68 | alert('Must use physical device for Push Notifications');
69 | }
70 |
71 | if (Platform.OS === 'android') {
72 | await Notifications.setNotificationChannelAsync('default', {
73 | name: 'default',
74 | importance: Notifications.AndroidImportance.MAX,
75 | vibrationPattern: [0, 250, 250, 250],
76 | lightColor: '#FF231F7C'
77 | });
78 | }
79 |
80 | await chatkitty.updateCurrentUser((user) => {
81 | user.properties = {
82 | ...user.properties,
83 | 'expo-push-token': token
84 | };
85 | return user;
86 | });
87 | }
88 | }}
89 | >
90 | {children}
91 |
92 | );
93 | };
94 |
--------------------------------------------------------------------------------
/src/screens/chatScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from 'react';
2 | import { StyleSheet, View } from 'react-native';
3 | import { Avatar, Bubble, GiftedChat } from 'react-native-gifted-chat';
4 | import { Text } from 'react-native-paper';
5 |
6 | import { chatkitty } from '../chatkitty';
7 | import Loading from '../components/loading';
8 | import { AuthContext } from '../context/authProvider';
9 | import { NotificationContext } from '../context/notificationProvider';
10 |
11 | export default function ChatScreen({ route, navigation }) {
12 | const { user } = useContext(AuthContext);
13 | const { sendNotification } = useContext(NotificationContext);
14 | const { channel } = route.params;
15 |
16 | const [messages, setMessages] = useState([]);
17 | const [loading, setLoading] = useState(true);
18 | const [loadEarlier, setLoadEarlier] = useState(false);
19 | const [isLoadingEarlier, setIsLoadingEarlier] = useState(false);
20 | const [messagePaginator, setMessagePaginator] = useState(null);
21 | const [typing, setTyping] = useState(null);
22 |
23 | useEffect(() => {
24 | const startChatSessionResult = chatkitty.startChatSession({
25 | channel: channel,
26 | onMessageReceived: (message) => {
27 | setMessages((currentMessages) =>
28 | GiftedChat.append(currentMessages, [mapMessage(message)])
29 | );
30 | },
31 | onTypingStarted: (typingUser) => {
32 | if (typingUser.id !== user.id) {
33 | setTyping(typingUser);
34 | }
35 | },
36 | onTypingStopped: (typingUser) => {
37 | if (typingUser.id !== user.id) {
38 | setTyping(null);
39 | }
40 | },
41 | onParticipantEnteredChat: (participant) => {
42 | sendNotification({
43 | title: `${participant.displayName} entered the chat`
44 | });
45 | },
46 | onParticipantLeftChat: (participant) => {
47 | sendNotification({
48 | title: `${participant.displayName} left the chat`
49 | });
50 | }
51 | });
52 |
53 | chatkitty
54 | .listMessages({
55 | channel: channel
56 | })
57 | .then((result) => {
58 | setMessages(result.paginator.items.map(mapMessage));
59 |
60 | setMessagePaginator(result.paginator);
61 | setLoadEarlier(result.paginator.hasNextPage);
62 |
63 | setLoading(false);
64 | });
65 |
66 | return startChatSessionResult.session.end;
67 | }, [user, channel]);
68 |
69 | async function handleSend(pendingMessages) {
70 | await chatkitty.sendMessage({
71 | channel: channel,
72 | body: pendingMessages[0].text
73 | });
74 | }
75 |
76 | async function handleLoadEarlier() {
77 | if (!messagePaginator.hasNextPage) {
78 | setLoadEarlier(false);
79 |
80 | return;
81 | }
82 |
83 | setIsLoadingEarlier(true);
84 |
85 | const nextPaginator = await messagePaginator.nextPage();
86 |
87 | setMessagePaginator(nextPaginator);
88 |
89 | setMessages((currentMessages) =>
90 | GiftedChat.prepend(currentMessages, nextPaginator.items.map(mapMessage))
91 | );
92 |
93 | setIsLoadingEarlier(false);
94 | }
95 |
96 | function handleInputTextChanged(text) {
97 | chatkitty.sendKeystrokes({
98 | channel: channel,
99 | keys: text
100 | });
101 | }
102 |
103 | function renderBubble(props) {
104 | return (
105 |
113 | );
114 | }
115 |
116 | function renderAvatar(props) {
117 | return (
118 | {
121 | chatkitty
122 | .createChannel({
123 | type: 'DIRECT',
124 | members: [{ id: avatarUser._id }]
125 | })
126 | .then((result) => {
127 | navigation.navigate('Chat', { channel: result.channel });
128 | });
129 | }}
130 | />
131 | );
132 | }
133 |
134 | function renderFooter() {
135 | if (typing) {
136 | return (
137 |
138 | {typing.displayName} is typing
139 |
140 | );
141 | }
142 |
143 | return null;
144 | }
145 |
146 | if (loading) {
147 | return ;
148 | }
149 |
150 | return (
151 |
163 | );
164 | }
165 |
166 | function mapMessage(message) {
167 | return {
168 | _id: message.id,
169 | text: message.body,
170 | createdAt: new Date(message.createdTime),
171 | user: mapUser(message.user)
172 | };
173 | }
174 |
175 | function mapUser(user) {
176 | return {
177 | _id: user.id,
178 | name: user.displayName,
179 | avatar: user.displayPictureUrl
180 | };
181 | }
182 |
183 | const styles = StyleSheet.create({
184 | footer: {
185 | paddingRight: 10,
186 | paddingLeft: 10,
187 | paddingBottom: 5
188 | }
189 | });
190 |
--------------------------------------------------------------------------------