Skip to content

Commit 7d3dd9e

Browse files
authored
feat: expo sample app 54 upgrade, pn setup and login user flow (#3238)
* feat: expo sample app 54 upgrade, pn setup and login user flow * fix: inline styles * fix: change package id for android
1 parent 8e74e6f commit 7d3dd9e

22 files changed

+3428
-2935
lines changed

examples/ExpoMessaging/app.json

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,26 @@
1919
"ios": {
2020
"supportsTablet": true,
2121
"usesIcloudStorage": true,
22-
"bundleIdentifier": "io.stream.expomessagingapp",
23-
"appleTeamId": "EHV7XZLAHA"
22+
"bundleIdentifier": "io.getstream.iOS.expomessagingapp",
23+
"appleTeamId": "EHV7XZLAHA",
24+
"googleServicesFile": "./firebase/GoogleService-Info.plist"
2425
},
2526
"android": {
2627
"config": {
2728
"googleMaps": {
2829
"apiKey": "AIzaSyDVh35biMyXbOjt74CQyO1dlqSMlrdHOOA"
2930
}
3031
},
31-
"package": "io.stream.expomessagingapp",
32+
"package": "io.getstream.android.expomessagingapp",
3233
"adaptiveIcon": {
3334
"foregroundImage": "./assets/adaptive-icon.png",
3435
"backgroundColor": "#ffffff"
3536
},
36-
"permissions": ["android.permission.RECORD_AUDIO", "android.permission.MODIFY_AUDIO_SETTINGS"]
37+
"permissions": [
38+
"android.permission.RECORD_AUDIO",
39+
"android.permission.MODIFY_AUDIO_SETTINGS"
40+
],
41+
"googleServicesFile": "./firebase/google-services.json"
3742
},
3843
"web": {
3944
"favicon": "./assets/favicon.png",
@@ -69,8 +74,22 @@
6974
"microphonePermission": "$(PRODUCT_NAME) would like to use your microphone for voice recording."
7075
}
7176
],
72-
73-
"./plugins/keyboardInsetMainActivityListener.js"
77+
"@react-native-firebase/app",
78+
"@react-native-firebase/messaging",
79+
[
80+
"expo-build-properties",
81+
{
82+
"android": {
83+
"extraMavenRepos": ["$rootDir/../../../node_modules/@notifee/react-native/android/libs"]
84+
},
85+
"ios": {
86+
"useFrameworks": "static",
87+
"forceStaticLinking": ["RNFBApp", "RNFBMessaging"]
88+
}
89+
}
90+
],
91+
"./plugins/keyboardInsetMainActivityListener.js",
92+
"./plugins/opSqliteSwiftPlugin.js"
7493
]
7594
}
7695
}

examples/ExpoMessaging/app/_layout.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,16 @@ import { StyleSheet } from 'react-native';
77
import { SafeAreaProvider } from 'react-native-safe-area-context';
88
import { LiveLocationManagerProvider } from 'stream-chat-expo';
99
import { watchLocation } from '../utils/watchLocation';
10+
import { UserProvider, useUserContext } from '@/context/UserContext';
11+
import UserLogin from '@/components/UserLogin';
12+
13+
function Layout() {
14+
const { user } = useUserContext();
15+
16+
if (!user) {
17+
return <UserLogin />;
18+
}
1019

11-
export default function Layout() {
1220
return (
1321
<SafeAreaProvider>
1422
<GestureHandlerRootView style={styles.container}>
@@ -24,6 +32,14 @@ export default function Layout() {
2432
);
2533
}
2634

35+
export default function App() {
36+
return (
37+
<UserProvider>
38+
<Layout />
39+
</UserProvider>
40+
);
41+
}
42+
2743
const styles = StyleSheet.create({
2844
container: {
2945
flex: 1,
Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,42 @@
1-
import React, { useContext } from 'react';
2-
import { SafeAreaView, View } from 'react-native';
3-
import { Channel, MessageInput, MessageFlashList } from 'stream-chat-expo';
4-
import { Stack, useRouter } from 'expo-router';
1+
import React, { useContext, useEffect, useState } from 'react';
2+
import type { Channel as StreamChatChannel } from 'stream-chat';
3+
import { Channel, MessageInput, MessageFlashList, useChatContext } from 'stream-chat-expo';
4+
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
55
import { AuthProgressLoader } from '../../../components/AuthProgressLoader';
66
import { AppContext } from '../../../context/AppContext';
77
import { useHeaderHeight } from '@react-navigation/elements';
88
import InputButtons from '../../../components/InputButtons';
99
import { MessageLocation } from '../../../components/LocationSharing/MessageLocation';
10+
import { SafeAreaView } from 'react-native-safe-area-context';
11+
import { StyleSheet } from 'react-native';
1012

1113
export default function ChannelScreen() {
14+
const { client } = useChatContext();
1215
const router = useRouter();
13-
const { setThread, channel, thread } = useContext(AppContext);
16+
const params = useLocalSearchParams();
17+
const navigateThroughPushNotification = params.push_notification as string;
18+
const channelId = params.cid as string;
19+
const [channelFromParams, setChannelFromParams] = useState<StreamChatChannel | undefined>(
20+
undefined,
21+
);
22+
23+
// Effect to fetch channel from params if channel is navigated through push notification
24+
useEffect(() => {
25+
const initChannel = async () => {
26+
if (navigateThroughPushNotification) {
27+
const channel = client?.channel('messaging', channelId);
28+
await channel?.watch();
29+
setChannelFromParams(channel);
30+
}
31+
};
32+
initChannel();
33+
}, [navigateThroughPushNotification, channelId, client]);
34+
35+
const { setThread, channel: channelContext, thread } = useContext(AppContext);
1436
const headerHeight = useHeaderHeight();
1537

38+
const channel = channelFromParams || channelContext;
39+
1640
if (!channel) {
1741
return <AuthProgressLoader />;
1842
}
@@ -32,29 +56,36 @@ export default function ChannelScreen() {
3256
defaultHandler?.();
3357
};
3458

59+
if (!channel) {
60+
return null;
61+
}
62+
3563
return (
36-
<SafeAreaView>
64+
<Channel
65+
audioRecordingEnabled={true}
66+
channel={channel}
67+
onPressMessage={onPressMessage}
68+
keyboardVerticalOffset={headerHeight}
69+
MessageLocation={MessageLocation}
70+
thread={thread}
71+
>
3772
<Stack.Screen options={{ title: 'Channel Screen' }} />
38-
{channel && (
39-
<Channel
40-
audioRecordingEnabled={true}
41-
channel={channel}
42-
onPressMessage={onPressMessage}
43-
keyboardVerticalOffset={headerHeight}
44-
MessageLocation={MessageLocation}
45-
thread={thread}
46-
>
47-
<View style={{ flex: 1 }}>
48-
<MessageFlashList
49-
onThreadSelect={(thread) => {
50-
setThread(thread);
51-
router.push(`/channel/${channel.cid}/thread/${thread.cid}`);
52-
}}
53-
/>
54-
<MessageInput InputButtons={InputButtons} />
55-
</View>
56-
</Channel>
57-
)}
58-
</SafeAreaView>
73+
74+
<SafeAreaView edges={['bottom']} style={styles.container}>
75+
<MessageFlashList
76+
onThreadSelect={(thread) => {
77+
setThread(thread);
78+
router.push(`/channel/${channel.cid}/thread/${thread.cid}`);
79+
}}
80+
/>
81+
<MessageInput InputButtons={InputButtons} />
82+
</SafeAreaView>
83+
</Channel>
5984
);
6085
}
86+
87+
const styles = StyleSheet.create({
88+
container: {
89+
flex: 1,
90+
},
91+
});
Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,43 @@
11
import React, { useContext } from 'react';
2-
import { SafeAreaView, View } from 'react-native';
2+
import { StyleSheet, View } from 'react-native';
33
import { Channel, Thread } from 'stream-chat-expo';
44
import { Stack } from 'expo-router';
55
import { AppContext } from '../../../../../context/AppContext';
66
import { useHeaderHeight } from '@react-navigation/elements';
7+
import { SafeAreaView } from 'react-native-safe-area-context';
78

89
export default function ThreadScreen() {
910
const { channel, thread, setThread } = useContext(AppContext);
1011
const headerHeight = useHeaderHeight();
1112

12-
if (channel === undefined) {
13+
if (!channel) {
1314
return null;
1415
}
1516

1617
return (
17-
<SafeAreaView>
18+
<Channel
19+
audioRecordingEnabled={true}
20+
channel={channel}
21+
keyboardVerticalOffset={headerHeight}
22+
thread={thread}
23+
threadList
24+
>
1825
<Stack.Screen options={{ title: 'Thread Screen' }} />
1926

20-
<Channel
21-
audioRecordingEnabled={true}
22-
channel={channel}
23-
keyboardVerticalOffset={headerHeight}
24-
thread={thread}
25-
threadList
26-
>
27-
<View
28-
style={{
29-
flex: 1,
30-
justifyContent: 'flex-start',
27+
<SafeAreaView edges={['bottom']} style={styles.container}>
28+
<Thread
29+
onThreadDismount={() => {
30+
setThread(undefined);
3131
}}
32-
>
33-
<Thread
34-
onThreadDismount={() => {
35-
setThread(undefined);
36-
}}
37-
/>
38-
</View>
39-
</Channel>
40-
</SafeAreaView>
32+
/>
33+
</SafeAreaView>
34+
</Channel>
4135
);
4236
}
37+
38+
const styles = StyleSheet.create({
39+
container: {
40+
flex: 1,
41+
justifyContent: 'flex-start',
42+
},
43+
});
Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,62 @@
1-
import { StyleSheet, View } from 'react-native';
1+
import { Alert, Image, Pressable, StyleSheet, View } from 'react-native';
22
import { ChannelList } from 'stream-chat-expo';
3-
import { useContext, useMemo } from 'react';
3+
import { useCallback, useContext, useMemo } from 'react';
44
import { Stack, useRouter } from 'expo-router';
55
import { ChannelSort } from 'stream-chat';
66
import { AppContext } from '../context/AppContext';
7-
import { user } from '../constants';
7+
import { useUserContext } from '@/context/UserContext';
8+
import { getInitialsOfName } from '@/utils/getInitialsOfName';
89

9-
const filters = {
10-
members: { $in: [user.id] },
11-
type: 'messaging',
12-
};
1310
const sort: ChannelSort = { last_updated: -1 };
1411
const options = {
1512
state: true,
1613
watch: true,
1714
};
1815

16+
const LogoutButton = () => {
17+
const { logOut, user } = useUserContext();
18+
19+
const onLogoutHandler = useCallback(() => {
20+
Alert.alert('Logout', 'Are you sure you want to logout?', [
21+
{ text: 'Cancel', style: 'cancel' },
22+
{ text: 'Logout', onPress: logOut },
23+
]);
24+
}, [logOut]);
25+
26+
return (
27+
<Pressable onPress={onLogoutHandler}>
28+
<Image
29+
source={{
30+
uri:
31+
user?.image ||
32+
`https://getstream.imgix.net/images/random_svg/${getInitialsOfName(user?.name || '')}.png`,
33+
}}
34+
style={styles.avatar}
35+
/>
36+
</Pressable>
37+
);
38+
};
39+
1940
export default function ChannelListScreen() {
20-
const memoizedFilters = useMemo(() => filters, []);
41+
const { user } = useUserContext();
42+
const filters = useMemo(
43+
() => ({
44+
members: { $in: [user?.id as string] },
45+
type: 'messaging',
46+
}),
47+
[user?.id],
48+
);
2149
const router = useRouter();
2250
const { setChannel } = useContext(AppContext);
2351

2452
return (
2553
<View style={styles.container}>
26-
<Stack.Screen options={{ title: 'Channel List Screen' }} />
54+
<Stack.Screen
55+
options={{ title: 'Channel List Screen', headerLeft: () => <LogoutButton /> }}
56+
/>
57+
2758
<ChannelList
28-
filters={memoizedFilters}
59+
filters={filters}
2960
onSelect={(channel) => {
3061
setChannel(channel);
3162
router.push(`/channel/${channel.cid}`);
@@ -41,4 +72,9 @@ const styles = StyleSheet.create({
4172
container: {
4273
flex: 1,
4374
},
75+
avatar: {
76+
borderRadius: 18,
77+
height: 36,
78+
width: 36,
79+
},
4480
});

examples/ExpoMessaging/babel.config.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

examples/ExpoMessaging/components/ChatWrapper.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ import {
77
Streami18n,
88
useCreateChatClient,
99
} from 'stream-chat-expo';
10+
import { UserResponse } from 'stream-chat';
1011
import { AuthProgressLoader } from './AuthProgressLoader';
11-
import { STREAM_API_KEY, user, userToken } from '../constants';
12-
import { useStreamChatTheme } from '../useStreamChatTheme';
12+
import { useStreamChatTheme } from '../hooks/useStreamChatTheme';
13+
import { useUserContext } from '@/context/UserContext';
14+
import { STREAM_API_KEY, USER_TOKENS } from '@/constants/ChatUsers';
15+
import { usePushNotifications } from '@/hooks/usePushNotifications';
16+
import '../utils/backgroundMessageHandler';
1317

1418
const streami18n = new Streami18n({
1519
language: 'en',
@@ -20,12 +24,15 @@ SqliteClient.logger = (level, message, extraData) => {
2024
};
2125

2226
export const ChatWrapper = ({ children }: PropsWithChildren<{}>) => {
27+
const { user } = useUserContext();
2328
const chatClient = useCreateChatClient({
2429
apiKey: STREAM_API_KEY,
25-
userData: user,
26-
tokenOrProvider: userToken,
30+
userData: user as UserResponse,
31+
tokenOrProvider: USER_TOKENS[user?.id as string],
2732
});
2833

34+
usePushNotifications({ chatClient });
35+
2936
streami18n.registerTranslation('en', {
3037
...enTranslations,
3138
'timestamp/Location end at': '{{ milliseconds | durationFormatter(withSuffix: false) }}',

0 commit comments

Comments
 (0)