Skip to content

Commit 1bd87f4

Browse files
committed
feat: add channel archiving
1 parent 5825685 commit 1bd87f4

File tree

17 files changed

+305
-32
lines changed

17 files changed

+305
-32
lines changed

examples/SampleApp/src/ChatUsers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { UserResponse } from 'stream-chat';
22
import { StreamChatGenerics } from './types';
33

44
export const USER_TOKENS: Record<string, string> = {
5+
luke_skywalker:
6+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibHVrZV9za3l3YWxrZXIifQ.kFSLHRB5X62t0Zlc7nwczWUfsQMwfkpylC6jCUZ6Mc0',
57
e2etest1:
68
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZTJldGVzdDEifQ.XlQOw8nl7fFzHoBkEiTcYGkNo5r7EBYA40LABGOk4hc',
79
e2etest2:
@@ -28,6 +30,9 @@ export const USER_TOKENS: Record<string, string> = {
2830
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9kb2xwaGUifQ.tLl-I8ADBhTKB-x5FB9jK4-am0dELLXgydM6VN9rTL8',
2931
};
3032
export const USERS: Record<string, UserResponse<StreamChatGenerics>> = {
33+
luke_skywalker: {
34+
id: 'luke_skywalker',
35+
},
3136
neil: {
3237
id: 'neil',
3338
image: 'https://ca.slack-edge.com/T02RM6X6B-U01173D1D5J-0dead6eea6ea-512',

examples/SampleApp/src/components/ChannelInfoOverlay.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import React, { useEffect } from 'react';
2-
import { FlatList, Keyboard, SafeAreaView, StyleSheet, Text, View, ViewStyle } from 'react-native';
2+
import {
3+
FlatList,
4+
Keyboard,
5+
Pressable,
6+
SafeAreaView,
7+
StyleSheet,
8+
Text,
9+
View,
10+
ViewStyle,
11+
} from 'react-native';
312
import dayjs from 'dayjs';
413
import relativeTime from 'dayjs/plugin/relativeTime';
514
import {
@@ -33,6 +42,7 @@ import {
3342
import { useAppOverlayContext } from '../context/AppOverlayContext';
3443
import { useBottomSheetOverlayContext } from '../context/BottomSheetOverlayContext';
3544
import { useChannelInfoOverlayContext } from '../context/ChannelInfoOverlayContext';
45+
import { Archieve } from '../icons/Archieve';
3646

3747
dayjs.extend(relativeTime);
3848

@@ -111,7 +121,7 @@ export const ChannelInfoOverlay = (props: ChannelInfoOverlayProps) => {
111121
const halfScreenHeight = vh(50);
112122
const width = vw(100) - 60;
113123

114-
const { channel, clientId, navigation } = data || {};
124+
const { channel, clientId, membership, navigation } = data || {};
115125

116126
const {
117127
theme: {
@@ -361,6 +371,37 @@ export const ChannelInfoOverlay = (props: ChannelInfoOverlayProps) => {
361371
<Text style={[styles.rowText, { color: black }]}>View info</Text>
362372
</View>
363373
</TapGestureHandler>
374+
<Pressable
375+
onPress={async () => {
376+
try {
377+
if (membership?.archived_at) {
378+
await channel.unarchive();
379+
} else {
380+
await channel.archive();
381+
}
382+
} catch (error) {
383+
console.log('Error archiving/unarchiving channel', error);
384+
}
385+
386+
setOverlay('none');
387+
}}
388+
>
389+
<View
390+
style={[
391+
styles.row,
392+
{
393+
borderTopColor: border,
394+
},
395+
]}
396+
>
397+
<View style={styles.rowInner}>
398+
<Archieve height={24} width={24} />
399+
</View>
400+
<Text style={[styles.rowText, { color: black }]}>
401+
{membership?.archived_at ? 'Unarchieve' : 'Archieve'}
402+
</Text>
403+
</View>
404+
</Pressable>
364405
{otherMembers.length > 1 && (
365406
<TapGestureHandler
366407
onHandlerStateChange={({ nativeEvent: { state } }) => {

examples/SampleApp/src/components/ChannelPreview.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ChannelPreviewMessengerProps,
99
Delete,
1010
MenuPointHorizontal,
11+
useChannelMembershipState,
1112
useChatContext,
1213
useTheme,
1314
} from 'stream-chat-react-native';
@@ -57,6 +58,8 @@ export const ChannelPreview: React.FC<ChannelPreviewMessengerProps<StreamChatGen
5758

5859
const navigation = useNavigation<ChannelListScreenNavigationProp>();
5960

61+
const membership = useChannelMembershipState(channel);
62+
6063
const {
6164
theme: {
6265
colors: { accent_red, white_smoke },
@@ -75,7 +78,7 @@ export const ChannelPreview: React.FC<ChannelPreviewMessengerProps<StreamChatGen
7578
<View style={[styles.swipeableContainer, { backgroundColor: white_smoke }]}>
7679
<RectButton
7780
onPress={() => {
78-
setData({ channel, clientId: client.userID, navigation });
81+
setData({ channel, clientId: client.userID, membership, navigation });
7982
setOverlay('channelInfo');
8083
}}
8184
style={[styles.leftSwipeableButton]}

examples/SampleApp/src/context/ChannelInfoOverlayContext.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useContext, useState } from 'react';
22

3+
import { ChannelState } from 'stream-chat';
34
import type { StackNavigationProp } from '@react-navigation/stack';
45
import type { ChannelContextValue } from 'stream-chat-react-native';
56

@@ -14,6 +15,7 @@ export type ChannelInfoOverlayData = Partial<
1415
Pick<ChannelContextValue<StreamChatGenerics>, 'channel'>
1516
> & {
1617
clientId?: string;
18+
membership?: ChannelState<StreamChatGenerics>['membership'];
1719
navigation?: ChannelListScreenNavigationProp;
1820
};
1921

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import Svg, { Path } from 'react-native-svg';
3+
import { useTheme } from 'stream-chat-react-native';
4+
5+
import { IconProps } from '../utils/base';
6+
7+
export const Archieve: React.FC<IconProps> = ({ height = 512, width = 512 }) => {
8+
const {
9+
theme: {
10+
colors: { grey },
11+
},
12+
} = useTheme();
13+
14+
return (
15+
<Svg height={height} viewBox={'0 0 512 512'} width={width}>
16+
<Path
17+
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'
18+
fill={grey}
19+
/>
20+
</Svg>
21+
);
22+
};

examples/SampleApp/src/screens/ChannelListScreen.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ const styles = StyleSheet.create({
5151
const baseFilters = {
5252
type: 'messaging',
5353
};
54-
const sort: ChannelSort<StreamChatGenerics> = { last_updated: -1 };
54+
const sort: ChannelSort<StreamChatGenerics> = [
55+
{ pinned_at: 1 },
56+
{ last_message_at: -1 },
57+
{ updated_at: -1 },
58+
];
5559
const options = {
5660
presence: true,
5761
state: true,
@@ -192,6 +196,9 @@ export const ChannelListScreen: React.FC = () => {
192196
channel,
193197
});
194198
}}
199+
onNewMessage={(arg1, arg2, arg3, arg4) => {
200+
console.log(arg1, arg2, arg3, arg4);
201+
}}
195202
options={options}
196203
Preview={ChannelPreview}
197204
setFlatListRef={setScrollRef}

examples/TypeScriptMessaging/App.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,20 +60,16 @@ const options = {
6060

6161
I18nManager.forceRTL(false);
6262

63-
SqliteClient.logger = (level, message, extraData) => {
64-
console.log(level, `SqliteClient: ${message}`, extraData);
65-
};
66-
67-
const apiKey = 'q95x9hkbyd6p';
63+
const apiKey = '8br4watad788';
6864
const userToken =
69-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9uIn0.eRVjxLvd4aqCEHY_JRa97g6k7WpHEhxL7Z4K4yTot1c';
65+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibHVrZV9za3l3YWxrZXIifQ.kFSLHRB5X62t0Zlc7nwczWUfsQMwfkpylC6jCUZ6Mc0';
7066

7167
const user = {
72-
id: 'ron',
68+
id: 'luke_skywalker',
7369
};
7470
const filters = {
75-
example: 'example-apps',
76-
members: { $in: ['ron'] },
71+
archived: true,
72+
members: { $in: ['luke_skywalker'] },
7773
type: 'messaging',
7874
};
7975
const sort: ChannelSort<StreamChatGenerics> = { last_updated: -1 };

package/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"path": "0.12.7",
7878
"react-native-markdown-package": "1.8.2",
7979
"react-native-url-polyfill": "^1.3.0",
80-
"stream-chat": "8.46.0"
80+
"stream-chat": "^8.47.1"
8181
},
8282
"peerDependencies": {
8383
"@op-engineering/op-sqlite": ">=9.3.0",

package/src/components/ChannelList/ChannelList.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import type { DefaultStreamChatGenerics } from '../../types/types';
3333
import { ChannelPreviewMessenger } from '../ChannelPreview/ChannelPreviewMessenger';
3434
import { EmptyStateIndicator as EmptyStateIndicatorDefault } from '../Indicators/EmptyStateIndicator';
3535
import { LoadingErrorIndicator as LoadingErrorIndicatorDefault } from '../Indicators/LoadingErrorIndicator';
36+
import { shouldConsiderArchivedChannels } from './hooks/utils';
37+
import { useChannelMemberUpdated } from './hooks/listeners/useMemberUpdated';
3638

3739
export type ChannelListProps<
3840
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
@@ -163,13 +165,14 @@ export type ChannelListProps<
163165
* @param lockChannelOrder If set to true, channels won't dynamically sort by most recent message, defaults to false
164166
* @param setChannels Setter for internal state property - `channels`. It's created from useState() hook.
165167
* @param event An [Event object](https://getstream.io/chat/docs/event_object) corresponding to `message.new` event
166-
*
168+
* @param considerArchivedChannels If set to true, archived channels will be considered while updating the list of channels
167169
* @overrideType Function
168170
* */
169171
onNewMessage?: (
170172
lockChannelOrder: boolean,
171173
setChannels: React.Dispatch<React.SetStateAction<Channel<StreamChatGenerics>[] | null>>,
172174
event: Event<StreamChatGenerics>,
175+
considerArchivedChannels?: boolean,
173176
) => void;
174177
/**
175178
* Override the default listener/handler for event `notification.message_new`
@@ -183,6 +186,7 @@ export type ChannelListProps<
183186
onNewMessageNotification?: (
184187
setChannels: React.Dispatch<React.SetStateAction<Channel<StreamChatGenerics>[] | null>>,
185188
event: Event<StreamChatGenerics>,
189+
considerArchivedChannels?: boolean,
186190
) => void;
187191
/**
188192
* Function that overrides default behavior when a user gets removed from a channel
@@ -286,6 +290,8 @@ export const ChannelList = <
286290
sort,
287291
});
288292

293+
const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
294+
289295
// Setup event listeners
290296
useAddedToChannelNotification({
291297
onAddedToChannel,
@@ -323,11 +329,13 @@ export const ChannelList = <
323329
lockChannelOrder,
324330
onNewMessage,
325331
setChannels,
332+
considerArchivedChannels,
326333
});
327334

328335
useNewMessageNotification({
329336
onNewMessageNotification,
330337
setChannels,
338+
considerArchivedChannels,
331339
});
332340

333341
useRemovedFromChannelNotification({
@@ -340,6 +348,10 @@ export const ChannelList = <
340348
setForceUpdate,
341349
});
342350

351+
useChannelMemberUpdated({
352+
setChannels,
353+
});
354+
343355
const channelIdsStr = channels?.reduce((acc, channel) => `${acc}${channel.cid}`, '');
344356

345357
useEffect(() => {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { useEffect } from 'react';
2+
3+
import type { Channel, Event } from 'stream-chat';
4+
5+
import { useChatContext } from '../../../../contexts/chatContext/ChatContext';
6+
7+
import type { DefaultStreamChatGenerics } from '../../../../types/types';
8+
9+
type Parameters<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> =
10+
{
11+
setChannels: React.Dispatch<React.SetStateAction<Channel<StreamChatGenerics>[] | null>>;
12+
onChannelMemberUpdated?: (
13+
setChannels: React.Dispatch<React.SetStateAction<Channel<StreamChatGenerics>[] | null>>,
14+
event: Event<StreamChatGenerics>,
15+
) => void;
16+
};
17+
18+
export const useChannelMemberUpdated = <
19+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
20+
>({
21+
onChannelMemberUpdated,
22+
setChannels,
23+
}: Parameters<StreamChatGenerics>) => {
24+
const { client } = useChatContext<StreamChatGenerics>();
25+
26+
useEffect(() => {
27+
const handleEvent = async (event: Event<StreamChatGenerics>) => {
28+
if (typeof onChannelMemberUpdated === 'function') {
29+
onChannelMemberUpdated(setChannels, event);
30+
} else {
31+
if (!event.member?.user || event.member.user.id !== client.userID || !event.channel_type) {
32+
return;
33+
}
34+
const member = event.member;
35+
const channelType = event.channel_type;
36+
const channelId = event.channel_id;
37+
38+
setChannels((currentChannels) => {
39+
if (!currentChannels) return currentChannels;
40+
41+
const targetChannel = client.channel(channelType, channelId);
42+
// assumes that channel instances are not changing
43+
const targetChannelIndex = currentChannels.indexOf(targetChannel);
44+
const targetChannelExistsWithinList = targetChannelIndex >= 0;
45+
46+
const newChannels = [...currentChannels];
47+
48+
if (targetChannelExistsWithinList) {
49+
newChannels.splice(targetChannelIndex, 1);
50+
}
51+
52+
// handle archiving (remove channel)
53+
if (typeof member.archived_at === 'string') {
54+
return newChannels;
55+
}
56+
return currentChannels;
57+
});
58+
}
59+
};
60+
61+
const listener = client?.on('member.updated', handleEvent);
62+
return () => listener?.unsubscribe();
63+
// eslint-disable-next-line react-hooks/exhaustive-deps
64+
}, []);
65+
};

0 commit comments

Comments
 (0)