Skip to content

Commit 922b693

Browse files
authored
feat: add the ability to override pn provider names (#2981)
1 parent 91302d6 commit 922b693

File tree

3 files changed

+171
-34
lines changed

3 files changed

+171
-34
lines changed

examples/SampleApp/src/components/SecretMenu.tsx

Lines changed: 153 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,163 @@
11
import React, { useCallback, useEffect, useMemo, useState } from 'react';
2-
import Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';
3-
import { LayoutChangeEvent, Text, TouchableOpacity, View, Platform, StyleSheet } from 'react-native';
4-
import { Close, Notification, Check, Delete, useTheme } from 'stream-chat-react-native';
2+
import Animated, {
3+
useAnimatedStyle,
4+
useSharedValue,
5+
withSpring,
6+
withTiming,
7+
} from 'react-native-reanimated';
8+
import {
9+
LayoutChangeEvent,
10+
Text,
11+
TouchableOpacity,
12+
View,
13+
Platform,
14+
StyleSheet,
15+
} from 'react-native';
16+
import { Close, Notification, Delete, useTheme } from 'stream-chat-react-native';
517
import { styles as menuDrawerStyles } from './MenuDrawer.tsx';
618
import AsyncStore from '../utils/AsyncStore.ts';
719
import { StreamChat } from 'stream-chat';
20+
import { LabeledTextInput } from '../screens/AdvancedUserSelectorScreen.tsx';
821

9-
export const SlideInView = ({ visible, children }: { visible: boolean; children: React.ReactNode }) => {
22+
export const SlideInView = ({
23+
visible,
24+
children,
25+
}: {
26+
visible: boolean;
27+
children: React.ReactNode;
28+
}) => {
1029
const animatedHeight = useSharedValue(0);
1130

1231
const onLayout = (event: LayoutChangeEvent) => {
1332
const { height } = event.nativeEvent.layout;
1433
animatedHeight.value = height;
1534
};
1635

17-
const animatedStyle = useAnimatedStyle(() => ({
18-
height: withSpring(visible ? animatedHeight.value : 0, { damping: 10 }),
19-
opacity: withTiming(visible ? 1 : 0, { duration: 500 }),
20-
}), [visible]);
36+
const animatedStyle = useAnimatedStyle(
37+
() => ({
38+
height: withSpring(visible ? animatedHeight.value : 0, { damping: 10 }),
39+
opacity: withTiming(visible ? 1 : 0, { duration: 500 }),
40+
}),
41+
[visible],
42+
);
2143

2244
return (
2345
<Animated.View style={animatedStyle}>
24-
{visible ? <View onLayout={onLayout} style={{ position: 'absolute', width: '100%' }}>
25-
{children}
26-
</View> : null}
46+
{visible ? (
47+
<View onLayout={onLayout} style={{ position: 'absolute', width: '100%' }}>
48+
{children}
49+
</View>
50+
) : null}
2751
</Animated.View>
2852
);
2953
};
3054

3155
const isAndroid = Platform.OS === 'android';
3256

33-
export const SecretMenu = ({ close, visible, chatClient }: { close: () => void, visible: boolean, chatClient: StreamChat }) => {
57+
type NotificationConfigItem = { label: string; name: string; id: string };
58+
59+
const SecretMenuNotificationConfigItem = ({
60+
notificationConfigItem,
61+
storeProvider,
62+
isSelected,
63+
}: {
64+
notificationConfigItem: NotificationConfigItem;
65+
storeProvider: (item: NotificationConfigItem) => void;
66+
isSelected: boolean;
67+
}) => {
68+
const [providerNameOverride, setProviderNameOverride] = useState<string>('');
69+
const [lastSubmittedOverride, setLastSubmittedOverride] = useState<string | null>(null);
70+
71+
const asyncStorageKey = useMemo(
72+
() => `@stream-rn-sampleapp-push-provider-${notificationConfigItem.id}-override`,
73+
[notificationConfigItem],
74+
);
75+
76+
useEffect(() => {
77+
const getProviderNameOverride = async () => {
78+
const nameOverride = await AsyncStore.getItem(asyncStorageKey, '');
79+
setLastSubmittedOverride(nameOverride ?? '');
80+
};
81+
getProviderNameOverride();
82+
}, [asyncStorageKey]);
83+
84+
const storeProviderNameOverride = useCallback(async () => {
85+
await AsyncStore.setItem(asyncStorageKey, providerNameOverride);
86+
setLastSubmittedOverride(providerNameOverride);
87+
}, [asyncStorageKey, providerNameOverride]);
88+
89+
return (
90+
<View
91+
style={[styles.notificationItemContainer, { borderColor: isSelected ? 'green' : 'gray' }]}
92+
>
93+
<TouchableOpacity
94+
style={{ flexDirection: 'row' }}
95+
onPress={() => storeProvider(notificationConfigItem)}
96+
>
97+
<Text style={styles.notificationItem}>{notificationConfigItem.label}</Text>
98+
<Text
99+
numberOfLines={1}
100+
style={{ opacity: 0.7, fontSize: 14, marginLeft: 4, maxWidth: 145 }}
101+
>
102+
{lastSubmittedOverride && lastSubmittedOverride.length > 0
103+
? lastSubmittedOverride
104+
: notificationConfigItem.name}
105+
</Text>
106+
</TouchableOpacity>
107+
{isSelected ? (
108+
<>
109+
<LabeledTextInput
110+
onChangeText={setProviderNameOverride}
111+
label={'PN Provider name override'}
112+
value={providerNameOverride}
113+
/>
114+
<TouchableOpacity style={styles.submitButton} onPress={storeProviderNameOverride}>
115+
<Text style={{ color: 'white', fontSize: 12 }}>Submit</Text>
116+
</TouchableOpacity>
117+
</>
118+
) : null}
119+
</View>
120+
);
121+
};
122+
123+
export const SecretMenu = ({
124+
close,
125+
visible,
126+
chatClient,
127+
}: {
128+
close: () => void;
129+
visible: boolean;
130+
chatClient: StreamChat;
131+
}) => {
34132
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
35133
const {
36134
theme: {
37135
colors: { black, grey },
38136
},
39137
} = useTheme();
40138

41-
const notificationConfigItems = useMemo(() => [{ label: 'Firebase', name: 'rn-fcm', id: 'firebase' }, { label: 'APNs', name: 'APN', id: 'apn' }], []);
139+
const notificationConfigItems = useMemo(
140+
() => [
141+
{ label: 'Firebase', name: 'rn-fcm', id: 'firebase' },
142+
{ label: 'APNs', name: 'APN', id: 'apn' },
143+
],
144+
[],
145+
);
42146

43147
useEffect(() => {
44148
const getSelectedProvider = async () => {
45-
const provider = await AsyncStore.getItem('@stream-rn-sampleapp-push-provider', notificationConfigItems[0]);
149+
const provider = await AsyncStore.getItem(
150+
'@stream-rn-sampleapp-push-provider',
151+
notificationConfigItems[0],
152+
);
46153
setSelectedProvider(provider?.id ?? 'firebase');
47154
};
48155
getSelectedProvider();
49156
}, [notificationConfigItems]);
50157

51-
const storeProvider = useCallback(async (item: { label: string, name: string, id: string }) => {
52-
await AsyncStore.setItem('@stream-rn-sampleapp-push-provider', item);
53-
setSelectedProvider(item.id);
158+
const storeProvider = useCallback(async (item: NotificationConfigItem) => {
159+
await AsyncStore.setItem('@stream-rn-sampleapp-push-provider', item);
160+
setSelectedProvider(item.id);
54161
}, []);
55162

56163
const removeAllDevices = useCallback(async () => {
@@ -81,14 +188,18 @@ export const SecretMenu = ({ close, visible, chatClient }: { close: () => void,
81188
>
82189
Notification Provider
83190
</Text>
84-
{isAndroid ? null : <View style={{ marginLeft: 16 }}>
85-
{notificationConfigItems.map((item) => (
86-
<TouchableOpacity key={item.id} style={{ paddingTop: 8, flexDirection: 'row' }} onPress={() => storeProvider(item)}>
87-
<Text style={styles.notificationItem}>{item.label}</Text>
88-
{item.id === selectedProvider ? <Check height={16} pathFill={'green'} width={16} style={{ marginLeft: 12 }} /> : null}
89-
</TouchableOpacity>
90-
))}
91-
</View>}
191+
{isAndroid ? null : (
192+
<View style={{ marginLeft: 16 }}>
193+
{notificationConfigItems.map((item) => (
194+
<SecretMenuNotificationConfigItem
195+
key={item.id}
196+
notificationConfigItem={item}
197+
storeProvider={storeProvider}
198+
isSelected={item.id === selectedProvider}
199+
/>
200+
))}
201+
</View>
202+
)}
92203
</View>
93204
</View>
94205
<TouchableOpacity onPress={removeAllDevices} style={menuDrawerStyles.menuItem}>
@@ -126,9 +237,24 @@ export const SecretMenu = ({ close, visible, chatClient }: { close: () => void,
126237

127238
export const styles = StyleSheet.create({
128239
separator: { height: 1, width: '100%', opacity: 0.2 },
129-
notificationContainer: {},
240+
notificationItemContainer: {
241+
paddingTop: 8,
242+
marginTop: 12,
243+
borderRadius: 15,
244+
borderWidth: 2,
245+
padding: 8,
246+
width: 225,
247+
},
130248
notificationItem: {
131-
fontSize: 13,
249+
fontSize: 15,
132250
fontWeight: '500',
133251
},
252+
submitButton: {
253+
flex: 1,
254+
marginTop: 8,
255+
borderRadius: 5,
256+
backgroundColor: 'lightskyblue',
257+
padding: 8,
258+
alignItems: 'center',
259+
},
134260
});

examples/SampleApp/src/hooks/useChatClient.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ const messaging = getMessaging();
1515
const requestNotificationPermission = async () => {
1616
const authStatus = await messaging.requestPermission();
1717
const isEnabled =
18-
authStatus === AuthorizationStatus.AUTHORIZED ||
19-
authStatus === AuthorizationStatus.PROVISIONAL;
18+
authStatus === AuthorizationStatus.AUTHORIZED || authStatus === AuthorizationStatus.PROVISIONAL;
2019
console.log('Permission Status', { authStatus, isEnabled });
2120
};
2221

@@ -121,16 +120,27 @@ export const useChatClient = () => {
121120
// await messaging.set;
122121
const apnsToken = await messaging.getAPNSToken();
123122
const firebaseToken = await messaging.getToken();
124-
const provider = await AsyncStore.getItem('@stream-rn-sampleapp-push-provider', { id: 'firebase', name: 'rn-fcm' });
123+
const provider = await AsyncStore.getItem('@stream-rn-sampleapp-push-provider', {
124+
id: 'firebase',
125+
name: 'rn-fcm',
126+
});
127+
const providerNameOverride = await AsyncStore.getItem<string>(
128+
`@stream-rn-sampleapp-push-provider-${provider?.id}-override`,
129+
null,
130+
);
125131
const id = provider?.id ?? 'firebase';
126-
const name = provider?.name ?? 'rn-fcm';
127-
const token = id === 'firebase' ? firebaseToken : apnsToken ?? firebaseToken;
132+
const name =
133+
providerNameOverride && providerNameOverride?.length > 0
134+
? providerNameOverride
135+
: (provider?.name ?? 'rn-fcm');
136+
console.log(id, name);
137+
const token = id === 'firebase' ? firebaseToken : (apnsToken ?? firebaseToken);
128138
await client.addDevice(token, id as PushProvider, client.userID, name);
129139

130140
// Listen to new FCM tokens and register them with stream chat server.
131141
const unsubscribeTokenRefresh = messaging.onTokenRefresh(async (newFirebaseToken) => {
132142
const newApnsToken = await messaging.getAPNSToken();
133-
const newToken = id === 'firebase' ? newFirebaseToken : newApnsToken ?? firebaseToken;
143+
const newToken = id === 'firebase' ? newFirebaseToken : (newApnsToken ?? firebaseToken);
134144
await client.addDevice(newToken, id as PushProvider, client.userID, name);
135145
});
136146
// show notifications when on foreground

examples/SampleApp/src/screens/AdvancedUserSelectorScreen.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export const LabeledTextInput: React.FC<LabeledTextInputProps> = ({
121121
placeholder={label}
122122
placeholderTextColor={grey}
123123
returnKeyType='next'
124+
numberOfLines={1}
124125
style={[
125126
styles.input,
126127
{
@@ -234,7 +235,7 @@ export const AdvancedUserSelectorScreen: React.FC = () => {
234235
});
235236
} catch (e) {
236237
Alert.alert(
237-
'Login resulted in error. Please make sure you have entered valid credentials',
238+
`Login resulted in error. Please make sure you have entered valid credentials. Error: ${(e as Error).message}`,
238239
);
239240
console.warn(e);
240241
}

0 commit comments

Comments
 (0)