Skip to content

Commit 64387d0

Browse files
committed
fix: improve channel message new event
1 parent 20e403a commit 64387d0

File tree

8 files changed

+111
-80
lines changed

8 files changed

+111
-80
lines changed

examples/SampleApp/src/components/MessageSearch/MessageSearchList.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import React from 'react';
22
import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3-
import { useNavigation } from '@react-navigation/native';
3+
import { NavigationProp, useNavigation } from '@react-navigation/native';
44
import dayjs from 'dayjs';
55
import calendar from 'dayjs/plugin/calendar';
66
import { Avatar, Spinner, useTheme, useViewport } from 'stream-chat-react-native';
7-
8-
import { MESSAGE_SEARCH_LIMIT } from '../../hooks/usePaginatedSearchedMessages';
7+
import { DEFAULT_PAGINATION_LIMIT } from '../../utils/constants';
98

109
import type { MessageResponse } from 'stream-chat';
1110

12-
import type { StreamChatGenerics } from '../../types';
11+
import type { StackNavigatorParamList, StreamChatGenerics } from '../../types';
1312

1413
dayjs.extend(calendar);
1514

@@ -46,6 +45,7 @@ const styles = StyleSheet.create({
4645
paddingLeft: 8,
4746
},
4847
title: { fontSize: 14, fontWeight: '700' },
48+
titleContainer: {},
4949
});
5050

5151
export type MessageSearchListProps = {
@@ -74,7 +74,8 @@ export const MessageSearchList: React.FC<MessageSearchListProps> = React.forward
7474
},
7575
} = useTheme();
7676
const { vw } = useViewport();
77-
const navigation = useNavigation();
77+
const navigation =
78+
useNavigation<NavigationProp<StackNavigatorParamList, 'ChannelListScreen'>>();
7879

7980
if (!messages && !refreshing) {
8081
return null;
@@ -92,8 +93,10 @@ export const MessageSearchList: React.FC<MessageSearchListProps> = React.forward
9293
>
9394
<Text style={{ color: grey }}>
9495
{`${
95-
messages.length >= MESSAGE_SEARCH_LIMIT ? MESSAGE_SEARCH_LIMIT : messages.length
96-
}${messages.length >= MESSAGE_SEARCH_LIMIT ? '+ ' : ' '} result${
96+
messages.length >= DEFAULT_PAGINATION_LIMIT
97+
? DEFAULT_PAGINATION_LIMIT
98+
: messages.length
99+
}${messages.length >= DEFAULT_PAGINATION_LIMIT ? '+ ' : ' '} result${
97100
messages.length === 1 ? '' : 's'
98101
}`}
99102
</Text>
@@ -129,8 +132,6 @@ export const MessageSearchList: React.FC<MessageSearchListProps> = React.forward
129132
testID='channel-preview-button'
130133
>
131134
<Avatar
132-
channelId={item.channel?.id}
133-
id={item.user?.id}
134135
image={item.user?.image}
135136
name={item.user?.name}
136137
online={item?.user?.online}

examples/SampleApp/src/screens/ChannelListScreen.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,11 @@ const baseFilters = {
5252
type: 'messaging',
5353
};
5454
const sort: ChannelSort<StreamChatGenerics> = [
55-
{ pinned_at: 1 },
55+
{ pinned_at: -1 },
5656
{ last_message_at: -1 },
5757
{ updated_at: -1 },
5858
];
59+
5960
const options = {
6061
presence: true,
6162
state: true,
@@ -79,7 +80,7 @@ export const ChannelListScreen: React.FC = () => {
7980
const { loading, loadMore, messages, refreshing, refreshList, reset } =
8081
usePaginatedSearchedMessages(searchQuery);
8182

82-
const chatClientUserId = chatClient?.user?.id;
83+
const chatClientUserId = chatClient?.user?.id || '';
8384
const filters = useMemo(
8485
() => ({
8586
...baseFilters,
@@ -102,7 +103,7 @@ export const ChannelListScreen: React.FC = () => {
102103
</View>
103104
);
104105

105-
const setScrollRef = (ref: React.RefObject<FlatList<Channel<StreamChatGenerics>> | null>) => {
106+
const setScrollRef = (ref: FlatList<Channel<StreamChatGenerics>> | null) => {
106107
scrollRef.current = ref;
107108
};
108109

package/src/components/ChannelList/hooks/listeners/useNewMessage.ts

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,10 @@ export const useNewMessage = <
4242
setChannels((channels) => {
4343
if (!channels) return channels;
4444
const targetChannelIndex = channels.findIndex((channel) => channel.cid === event.cid);
45+
const targetChannel = channels[targetChannelIndex];
4546

4647
const channelInList = targetChannelIndex >= 0;
4748

48-
const targetChannel = channels[targetChannelIndex];
49-
5049
const isTargetChannelArchived = isChannelArchived(targetChannel);
5150
const isTargetChannelPinned = isChannelPinned(targetChannel);
5251
const isArchivedFilterTrue = filters && filters.archived === true;
@@ -64,28 +63,33 @@ export const useNewMessage = <
6463
return channels;
6564
}
6665

67-
if (!channelInList && event.channel_type && event.channel_id) {
68-
// If channel doesn't exist in existing list, check in activeChannels as well.
69-
// It may happen that channel was hidden using channel.hide(). In that case
70-
// We remove it from `channels` state, but its still being watched and exists in client.activeChannels.
71-
const channel = client.channel(event.channel_type, event.channel_id);
72-
// While adding new channels, we need to consider whether they are archived or not.
73-
if (
74-
// When archived filter false, and channel is archived
75-
(isChannelArchived(channel) && isArchivedFilterFalse) ||
76-
// When archived filter true, and channel is not archived
77-
(isArchivedFilterTrue && !isChannelArchived(channel))
78-
)
79-
return channels;
80-
return [channel, ...channels];
66+
if (!channelInList) {
67+
if (event.channel_type && event.channel_id) {
68+
// If channel doesn't exist in existing list, check in activeChannels as well.
69+
// It may happen that channel was hidden using channel.hide(). In that case
70+
// We remove it from `channels` state, but its still being watched and exists in client.activeChannels.
71+
const channel = client.channel(event.channel_type, event.channel_id);
72+
// While adding new channels, we need to consider whether they are archived or not.
73+
if (
74+
// When archived filter false, and channel is archived
75+
(isChannelArchived(channel) && isArchivedFilterFalse) ||
76+
// When archived filter true, and channel is not archived
77+
(isArchivedFilterTrue && !isChannelArchived(channel))
78+
)
79+
return channels;
80+
return [channel, ...channels];
81+
}
82+
} else {
83+
if (event.cid) {
84+
return moveChannelUp<StreamChatGenerics>({
85+
channels,
86+
channelToMove: targetChannel,
87+
channelToMoveIndexWithinChannels: targetChannelIndex,
88+
sort,
89+
});
90+
}
8191
}
8292

83-
if (!lockChannelOrder && event.cid)
84-
return moveChannelUp<StreamChatGenerics>({
85-
channels,
86-
cid: event.cid,
87-
});
88-
8993
return [...channels];
9094
});
9195
}

package/src/components/ChannelList/hooks/useChannelMembershipState.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ export const useChannelMembershipState = <
1717
useEffect(() => {
1818
if (!channel) return;
1919

20-
setMembership(channel.state.membership);
21-
2220
const handleMembershipUpdate = () => {
2321
setMembership(channel.state.membership);
2422
};

package/src/components/ChannelList/utils.ts

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,66 @@
1-
import uniqBy from 'lodash/uniqBy';
2-
import type { Channel, StreamChat } from 'stream-chat';
1+
import type { Channel, ChannelSort, StreamChat } from 'stream-chat';
32

43
import type { DefaultStreamChatGenerics } from '../../types/types';
4+
import { findLastPinnedChannelIndex, shouldConsiderPinnedChannels } from './hooks/utils';
55

66
type MoveParameters<
77
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
88
> = {
9-
channels: Channel<StreamChatGenerics>[];
10-
cid: string;
9+
channels: Array<Channel<StreamChatGenerics>>;
10+
channelToMove: Channel<StreamChatGenerics>;
11+
/**
12+
* If the index of the channel within `channels` list which is being moved upwards
13+
* (`channelToMove`) is known, you can supply it to skip extra calculation.
14+
*/
15+
channelToMoveIndexWithinChannels?: number;
16+
sort?: ChannelSort<StreamChatGenerics>;
1117
};
1218

1319
export const moveChannelUp = <
1420
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
1521
>({
16-
channels = [],
17-
cid,
22+
channels,
23+
channelToMove,
24+
channelToMoveIndexWithinChannels,
25+
sort,
1826
}: MoveParameters<StreamChatGenerics>) => {
19-
// get channel from channels
20-
const index = channels.findIndex((c) => c.cid === cid);
21-
if (index <= 0) return channels;
22-
const channel = channels[index];
27+
// get index of channel to move up
28+
const targetChannelIndex =
29+
channelToMoveIndexWithinChannels ??
30+
channels.findIndex((channel) => channel.cid === channelToMove.cid);
2331

24-
// remove channel from current position and add to start
25-
channels.splice(index, 1);
26-
channels.unshift(channel);
32+
const targetChannelExistsWithinList = targetChannelIndex >= 0;
33+
const targetChannelAlreadyAtTheTop = targetChannelIndex === 0;
2734

28-
return uniqBy([channel, ...channels], 'cid');
35+
// pinned channels should not move within the list based on recent activity, channels which
36+
// receive messages and are not pinned should move upwards but only under the last pinned channel
37+
// in the list
38+
const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
39+
40+
if (targetChannelAlreadyAtTheTop) return channels;
41+
42+
const newChannels = [...channels];
43+
44+
// target channel index is known, remove it from the list
45+
if (targetChannelExistsWithinList) {
46+
newChannels.splice(targetChannelIndex, 1);
47+
}
48+
49+
// as position of pinned channels has to stay unchanged, we need to
50+
// find last pinned channel in the list to move the target channel after
51+
let lastPinnedChannelIndex: number | null = null;
52+
if (considerPinnedChannels) {
53+
lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels: newChannels });
54+
}
55+
56+
// re-insert it at the new place (to specific index if pinned channels are considered)
57+
newChannels.splice(
58+
typeof lastPinnedChannelIndex === 'number' ? lastPinnedChannelIndex + 1 : 0,
59+
0,
60+
channelToMove,
61+
);
62+
63+
return newChannels;
2964
};
3065

3166
type GetParameters<

package/src/components/ChannelPreview/ChannelPreviewMessenger.tsx

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { Button, StyleSheet, View } from 'react-native';
2+
import { StyleSheet, TouchableOpacity, View } from 'react-native';
33

44
import { ChannelAvatar } from './ChannelAvatar';
55
import type { ChannelPreviewProps } from './ChannelPreview';
@@ -19,7 +19,6 @@ import {
1919
import { useTheme } from '../../contexts/themeContext/ThemeContext';
2020
import { useViewport } from '../../hooks/useViewport';
2121
import type { DefaultStreamChatGenerics } from '../../types/types';
22-
import { useChannelMembershipState } from '../ChannelList/hooks/useChannelMembershipState';
2322

2423
const styles = StyleSheet.create({
2524
container: {
@@ -111,6 +110,7 @@ const ChannelPreviewMessengerWithContext = <
111110
latestMessagePreview,
112111
maxUnreadCount,
113112
muted,
113+
onSelect,
114114
PreviewAvatar = ChannelAvatar,
115115
PreviewMessage = ChannelPreviewMessage,
116116
PreviewMutedStatus = ChannelPreviewMutedStatus,
@@ -135,10 +135,13 @@ const ChannelPreviewMessengerWithContext = <
135135
Math.floor(maxWidth / ((title.fontSize || styles.title.fontSize) / 2)),
136136
);
137137

138-
const membership = useChannelMembershipState(channel);
139-
140138
return (
141-
<View
139+
<TouchableOpacity
140+
onPress={() => {
141+
if (onSelect) {
142+
onSelect(channel);
143+
}
144+
}}
142145
style={[
143146
styles.container,
144147
{ backgroundColor: white_snow, borderBottomColor: border },
@@ -165,33 +168,9 @@ const ChannelPreviewMessengerWithContext = <
165168
formatLatestMessageDate={formatLatestMessageDate}
166169
latestMessagePreview={latestMessagePreview}
167170
/>
168-
{membership && (
169-
<>
170-
<Button
171-
title={membership.archived_at ? 'Unarchieve' : 'Archieve'}
172-
onPress={async () => {
173-
if (membership.archived_at) {
174-
await channel.unarchive();
175-
} else {
176-
await channel.archive();
177-
}
178-
}}
179-
/>
180-
<Button
181-
title={membership.pinned_at ? 'Unpin' : 'Pin'}
182-
onPress={async () => {
183-
if (membership.pinned_at) {
184-
await channel.unpin();
185-
} else {
186-
await channel.pin();
187-
}
188-
}}
189-
/>
190-
</>
191-
)}
192171
</View>
193172
</View>
194-
</View>
173+
</TouchableOpacity>
195174
);
196175
};
197176

package/src/icons/Archieve.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react';
2+
import Svg, { Path } from 'react-native-svg';
3+
4+
import { IconProps } from './utils/base';
5+
6+
export const Archieve: React.FC<IconProps> = ({ height = 512, width = 512, ...rest }) => {
7+
return (
8+
<Svg height={height} viewBox={'0 0 512 512'} width={width} {...rest}>
9+
<Path d='M32 32l448 0c17.7 0 32 14.3 32 32l0 32c0 17.7-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96L0 64C0 46.3 14.3 32 32 32zm0 128l448 0 0 256c0 35.3-28.7 64-64 64L96 480c-35.3 0-64-28.7-64-64l0-256zm128 80c0 8.8 7.2 16 16 16l160 0c8.8 0 16-7.2 16-16s-7.2-16-16-16l-160 0c-8.8 0-16 7.2-16 16z' />
10+
</Svg>
11+
);
12+
};

package/src/icons/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './utils/base';
22

3+
export * from './Archieve';
34
export * from './ArrowRight';
45
export * from './ArrowLeft';
56
export * from './ArrowUp';

0 commit comments

Comments
 (0)