Skip to content

Commit fcf98f9

Browse files
committed
fix: add channel command middleware
1 parent 0eb0484 commit fcf98f9

22 files changed

+441
-277
lines changed

examples/SampleApp/src/middlewares/textComposerEmojiMiddleware.ts renamed to examples/SampleApp/src/middlewares/emojiControl.ts

File renamed without changes.

examples/SampleApp/src/screens/ChannelListScreen.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,7 @@ const baseFilters = {
5959
type: 'messaging',
6060
};
6161

62-
const sort: ChannelSort = [
63-
{ pinned_at: -1 },
64-
{ last_message_at: -1 },
65-
{ updated_at: -1 },
66-
];
62+
const sort: ChannelSort = [{ pinned_at: -1 }, { last_message_at: -1 }, { updated_at: -1 }];
6763

6864
const options = {
6965
presence: true,

examples/SampleApp/src/screens/ChannelScreen.tsx

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import React, { useCallback, useEffect, useState } from 'react';
2-
import type { Channel as StreamChatChannel, TextComposerMiddleware } from 'stream-chat';
2+
import type {
3+
CustomDataManagerState,
4+
LocalMessage,
5+
Channel as StreamChatChannel,
6+
TextComposerMiddleware,
7+
} from 'stream-chat';
38
import { RouteProp, useFocusEffect, useNavigation } from '@react-navigation/native';
49
import {
510
Channel,
@@ -10,11 +15,17 @@ import {
1015
useAttachmentPickerContext,
1116
useChannelPreviewDisplayName,
1217
useChatContext,
18+
useMessageComposer,
1319
useTheme,
1420
useTypingString,
1521
AITypingIndicatorView,
22+
AutoCompleteInput,
23+
GiphyLightning,
24+
useStateStore,
25+
CircleClose,
26+
SendButton,
1627
} from 'stream-chat-react-native';
17-
import { Platform, StyleSheet, View } from 'react-native';
28+
import { Platform, Pressable, StyleSheet, Text, View } from 'react-native';
1829
import type { StackNavigationProp } from '@react-navigation/stack';
1930
import { useSafeAreaInsets } from 'react-native-safe-area-context';
2031

@@ -25,16 +36,12 @@ import { useChannelMembersStatus } from '../hooks/useChannelMembersStatus';
2536

2637
import type { StackNavigatorParamList } from '../types';
2738
import { NetworkDownIndicator } from '../components/NetworkDownIndicator';
28-
import { createTextComposerEmojiMiddleware } from '../middlewares/textComposerEmojiMiddleware';
39+
import { createTextComposerEmojiMiddleware } from '../middlewares/emojiControl';
2940
import { init, SearchIndex } from 'emoji-mart';
3041
import data from '@emoji-mart/data';
3142

3243
init({ data });
3344

34-
const styles = StyleSheet.create({
35-
flex: { flex: 1 },
36-
});
37-
3845
export type ChannelScreenNavigationProp = StackNavigationProp<
3946
StackNavigatorParamList,
4047
'ChannelScreen'
@@ -152,14 +159,14 @@ export const ChannelScreen: React.FC<ChannelScreenProps> = ({
152159
chatClient.setMessageComposerSetupFunction(({ composer }) => {
153160
composer.textComposer.middlewareExecutor.insert({
154161
middleware: [createTextComposerEmojiMiddleware(SearchIndex) as TextComposerMiddleware],
155-
position: { before: 'stream-io/text-composer/mentions-middleware' },
162+
position: { after: 'stream-io/text-composer/mentions-middleware' },
156163
unique: true,
157164
});
158165
});
159166
}, [chatClient]);
160167

161168
const onThreadSelect = useCallback(
162-
(thread) => {
169+
(thread: LocalMessage | null) => {
163170
setSelectedThread(thread);
164171
navigation.navigate('ThreadScreen', {
165172
channel,
@@ -194,3 +201,23 @@ export const ChannelScreen: React.FC<ChannelScreenProps> = ({
194201
</View>
195202
);
196203
};
204+
205+
const styles = StyleSheet.create({
206+
flex: { flex: 1 },
207+
input: {
208+
flexDirection: 'row',
209+
justifyContent: 'space-between',
210+
},
211+
giphyContainer: {
212+
alignItems: 'center',
213+
borderRadius: 12,
214+
flexDirection: 'row',
215+
marginRight: 8,
216+
paddingHorizontal: 8,
217+
paddingVertical: 4,
218+
},
219+
giphyText: {
220+
fontSize: 12,
221+
fontWeight: 'bold',
222+
},
223+
});

package/src/components/AutoCompleteInput/AutoCompleteInput.tsx

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
TextInputSelectionChangeEventData,
1010
} from 'react-native';
1111

12-
import { TextComposerState } from 'stream-chat';
12+
import { CustomDataManagerState, TextComposerState } from 'stream-chat';
1313

1414
import { useMessageComposer } from '../../contexts/messageInputContext/hooks/useMessageComposer';
1515
import {
@@ -27,13 +27,7 @@ import { useStateStore } from '../../hooks/useStateStore';
2727
type AutoCompleteInputPropsWithContext = TextInputProps &
2828
Pick<
2929
MessageInputContextValue,
30-
| 'giphyActive'
31-
| 'giphyEnabled'
32-
| 'maxMessageLength'
33-
| 'numberOfLines'
34-
| 'onChangeText'
35-
| 'setGiphyActive'
36-
| 'setInputBoxRef'
30+
'isCommandUIEnabled' | 'maxMessageLength' | 'numberOfLines' | 'onChangeText' | 'setInputBoxRef'
3731
> &
3832
Pick<TranslationContextValue, 't'> & {
3933
/**
@@ -49,10 +43,13 @@ const textComposerStateSelector = (state: TextComposerState) => ({
4943
text: state.text,
5044
});
5145

46+
const customComposerDataSelector = (state: CustomDataManagerState) => ({
47+
command: state.custom.command,
48+
});
49+
5250
const AutoCompleteInputWithContext = (props: AutoCompleteInputPropsWithContext) => {
5351
const {
5452
cooldownActive = false,
55-
giphyActive,
5653
maxMessageLength,
5754
numberOfLines,
5855
onChangeText,
@@ -62,8 +59,9 @@ const AutoCompleteInputWithContext = (props: AutoCompleteInputPropsWithContext)
6259
} = props;
6360
const [textHeight, setTextHeight] = useState(0);
6461
const messageComposer = useMessageComposer();
65-
const { textComposer } = messageComposer;
62+
const { customDataManager, textComposer } = messageComposer;
6663
const { text } = useStateStore(textComposer.state, textComposerStateSelector);
64+
const { command } = useStateStore(customDataManager.state, customComposerDataSelector);
6765

6866
const handleSelectionChange = useCallback(
6967
(e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
@@ -103,12 +101,8 @@ const AutoCompleteInputWithContext = (props: AutoCompleteInputPropsWithContext)
103101
} = useTheme();
104102

105103
const placeholderText = useMemo(() => {
106-
return giphyActive
107-
? t('Search GIFs')
108-
: cooldownActive
109-
? t('Slow mode ON')
110-
: t('Send a message');
111-
}, [cooldownActive, giphyActive, t]);
104+
return command ? t('Search GIFs') : cooldownActive ? t('Slow mode ON') : t('Send a message');
105+
}, [command, cooldownActive, t]);
112106

113107
const handleContentSizeChange = useCallback(
114108
({
@@ -121,7 +115,7 @@ const AutoCompleteInputWithContext = (props: AutoCompleteInputPropsWithContext)
121115

122116
return (
123117
<TextInput
124-
autoFocus={giphyActive}
118+
autoFocus={!!command}
125119
maxLength={maxMessageLength}
126120
multiline
127121
onChangeText={onTextChangeHandler}
@@ -150,13 +144,8 @@ const areEqual = (
150144
prevProps: AutoCompleteInputPropsWithContext,
151145
nextProps: AutoCompleteInputPropsWithContext,
152146
) => {
153-
const { cooldownActive: prevCooldownActive, giphyActive: prevGiphyActive, t: prevT } = prevProps;
154-
const { cooldownActive: nextCooldownActive, giphyActive: nextGiphyActive, t: nextT } = nextProps;
155-
156-
const giphyActiveEqual = prevGiphyActive === nextGiphyActive;
157-
if (!giphyActiveEqual) {
158-
return false;
159-
}
147+
const { cooldownActive: prevCooldownActive, t: prevT } = prevProps;
148+
const { cooldownActive: nextCooldownActive, t: nextT } = nextProps;
160149

161150
const tEqual = prevT === nextT;
162151
if (!tEqual) {
@@ -178,13 +167,11 @@ const MemoizedAutoCompleteInput = React.memo(
178167

179168
export const AutoCompleteInput = (props: AutoCompleteInputProps) => {
180169
const {
181-
giphyEnabled,
170+
isCommandUIEnabled,
182171
additionalTextInputProps,
183-
giphyActive,
184172
maxMessageLength,
185173
numberOfLines,
186174
onChangeText,
187-
setGiphyActive,
188175
setInputBoxRef,
189176
} = useMessageInputContext();
190177
const { t } = useTranslationContext();
@@ -193,12 +180,10 @@ export const AutoCompleteInput = (props: AutoCompleteInputProps) => {
193180
<MemoizedAutoCompleteInput
194181
{...{
195182
additionalTextInputProps,
196-
giphyActive,
197-
giphyEnabled,
183+
isCommandUIEnabled,
198184
maxMessageLength,
199185
numberOfLines,
200186
onChangeText,
201-
setGiphyActive,
202187
setInputBoxRef,
203188
t,
204189
}}
Lines changed: 52 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,11 @@
1-
import React from 'react';
1+
import React, { useCallback } from 'react';
22
import { StyleSheet, View } from 'react-native';
33

44
import { CommandVariants } from 'stream-chat';
55

66
import { useTheme } from '../../contexts/themeContext/ThemeContext';
77
import { Flag, GiphyIcon, Imgur, Lightning, Mute, Sound, UserAdd, UserDelete } from '../../icons';
88

9-
const styles = StyleSheet.create({
10-
iconContainer: {
11-
alignItems: 'center',
12-
borderRadius: 12,
13-
height: 24,
14-
justifyContent: 'center',
15-
marginRight: 8,
16-
width: 24,
17-
},
18-
});
19-
209
export const AutoCompleteSuggestionCommandIcon = ({ name }: { name: CommandVariants }) => {
2110
const {
2211
theme: {
@@ -28,54 +17,55 @@ export const AutoCompleteSuggestionCommandIcon = ({ name }: { name: CommandVaria
2817
},
2918
},
3019
} = useTheme();
31-
switch (name) {
32-
case 'ban':
33-
return (
34-
<View style={[styles.iconContainer, { backgroundColor: accent_blue }, iconContainer]}>
35-
<UserDelete height={16} pathFill={white} width={16} />
36-
</View>
37-
);
38-
case 'flag':
39-
return (
40-
<View style={[styles.iconContainer, { backgroundColor: accent_blue }, iconContainer]}>
41-
<Flag pathFill={white} />
42-
</View>
43-
);
44-
case 'giphy':
45-
return (
46-
<View style={[styles.iconContainer, { backgroundColor: accent_blue }, iconContainer]}>
47-
<GiphyIcon />
48-
</View>
49-
);
50-
case 'imgur':
51-
return (
52-
<View style={[styles.iconContainer, { backgroundColor: accent_blue }, iconContainer]}>
53-
<Imgur />
54-
</View>
55-
);
56-
case 'mute':
57-
return (
58-
<View style={[styles.iconContainer, { backgroundColor: accent_blue }, iconContainer]}>
59-
<Mute height={16} pathFill={white} width={16} />
60-
</View>
61-
);
62-
case 'unban':
63-
return (
64-
<View style={[styles.iconContainer, { backgroundColor: accent_blue }, iconContainer]}>
65-
<UserAdd height={16} pathFill={white} width={16} />
66-
</View>
67-
);
68-
case 'unmute':
69-
return (
70-
<View style={[styles.iconContainer, { backgroundColor: accent_blue }, iconContainer]}>
71-
<Sound pathFill={white} />
72-
</View>
73-
);
74-
default:
75-
return (
76-
<View style={[styles.iconContainer, { backgroundColor: accent_blue }, iconContainer]}>
77-
<Lightning fill={white} size={16} />
78-
</View>
79-
);
80-
}
20+
21+
const renderIcon = useCallback(
22+
(name: CommandVariants) => {
23+
switch (name) {
24+
case 'ban':
25+
return <UserDelete height={16} pathFill={white} width={16} />;
26+
case 'flag':
27+
return <Flag pathFill={white} />;
28+
case 'giphy':
29+
return <GiphyIcon />;
30+
case 'imgur':
31+
return <Imgur />;
32+
case 'mute':
33+
return <Mute height={16} pathFill={white} width={16} />;
34+
case 'unban':
35+
return <UserAdd height={16} pathFill={white} width={16} />;
36+
case 'unmute':
37+
return <Sound pathFill={white} />;
38+
default:
39+
return <Lightning fill={white} size={16} />;
40+
}
41+
},
42+
[white],
43+
);
44+
45+
const icon = renderIcon(name);
46+
47+
return (
48+
<View
49+
style={[
50+
styles.iconContainer,
51+
{
52+
backgroundColor: accent_blue,
53+
},
54+
iconContainer,
55+
]}
56+
>
57+
{icon}
58+
</View>
59+
);
8160
};
61+
62+
const styles = StyleSheet.create({
63+
iconContainer: {
64+
alignItems: 'center',
65+
borderRadius: 12,
66+
height: 24,
67+
justifyContent: 'center',
68+
marginRight: 8,
69+
width: 24,
70+
},
71+
});

0 commit comments

Comments
 (0)