Skip to content

Commit

Permalink
fix(chat-hooks): respect the order of group channel collection and qu…
Browse files Browse the repository at this point in the history
…ery.
  • Loading branch information
bang9 committed Sep 13, 2022
1 parent 301801c commit 913875d
Show file tree
Hide file tree
Showing 10 changed files with 395 additions and 304 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { UseGroupChannelList } from '../types';
import type { UseGroupChannelList } from '../../types';
import { useGroupChannelListWithCollection } from './useGroupChannelListWithCollection';
import { useGroupChannelListWithQuery } from './useGroupChannelListWithQuery';

Expand Down
154 changes: 154 additions & 0 deletions packages/uikit-chat-hooks/src/channel/useGroupChannelList/reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { useReducer } from 'react';

import type { SendbirdChannel } from '@sendbird/uikit-utils';
import { SendbirdGroupChannel, getGroupChannels } from '@sendbird/uikit-utils';

type Order = 'latest_last_message' | 'chronological' | 'channel_name_alphabetical' | 'metadata_value_alphabetical';

type Action =
| {
type: 'update_loading' | 'update_refreshing';
value: { status: boolean };
}
| {
type: 'update_channels';
value: { channels: SendbirdChannel[] };
}
| {
type: 'delete_channels';
value: { channelUrls: string[] };
}
| {
type: 'set_channels';
value: { channels: SendbirdChannel[]; clearPrev: boolean };
}
| {
type: 'update_order';
value: { order?: Order };
};

type State = {
loading: boolean;
refreshing: boolean;
groupChannels: SendbirdGroupChannel[];
order?: Order;
};

const defaultReducer = ({ ...draft }: State, action: Action) => {
const compareByOrder = createCompareByOrder(draft.order);

switch (action.type) {
case 'update_refreshing':
case 'update_loading': {
const key = action.type === 'update_loading' ? 'loading' : 'refreshing';
draft[key] = action.value.status;
break;
}
case 'update_channels': {
getGroupChannels(action.value.channels).forEach((freshChannel) => {
const idx = draft.groupChannels.findIndex((staleChannel) => staleChannel.url === freshChannel.url);
if (idx > -1) draft.groupChannels[idx] = freshChannel;
});

compareByOrder && (draft.groupChannels = draft.groupChannels.sort(compareByOrder));
break;
}
case 'delete_channels': {
action.value.channelUrls.forEach((url) => {
const idx = draft.groupChannels.findIndex((c) => c.url === url);
if (idx > -1) draft.groupChannels.splice(idx, 1);
});

compareByOrder && (draft.groupChannels = draft.groupChannels.sort(compareByOrder));
break;
}
case 'set_channels': {
if (action.value.clearPrev) {
draft.groupChannels = getGroupChannels(action.value.channels);
} else {
draft.groupChannels = [...draft.groupChannels, ...getGroupChannels(action.value.channels)];
}

compareByOrder && (draft.groupChannels = draft.groupChannels.sort(compareByOrder));
break;
}
case 'update_order': {
draft.order = action.value.order;
break;
}
}
return draft;
};

export const useGroupChannelListReducer = (order?: Order) => {
const [{ loading, refreshing, groupChannels }, dispatch] = useReducer(defaultReducer, {
loading: true,
refreshing: false,
groupChannels: [],
order,
});

const updateChannels = (channels: SendbirdChannel[]) => {
dispatch({ type: 'update_channels', value: { channels } });
};
const deleteChannels = (channelUrls: string[]) => {
dispatch({ type: 'delete_channels', value: { channelUrls } });
};
const setChannels = (channels: SendbirdChannel[], clearPrev: boolean) => {
dispatch({ type: 'set_channels', value: { channels, clearPrev } });
};
const updateLoading = (status: boolean) => {
dispatch({ type: 'update_loading', value: { status } });
};
const updateRefreshing = (status: boolean) => {
dispatch({ type: 'update_refreshing', value: { status } });
};
const updateOrder = (order?: Order) => {
dispatch({ type: 'update_order', value: { order } });
};

return {
updateLoading,
updateRefreshing,
updateChannels,
deleteChannels,
setChannels,

updateOrder,

loading,
refreshing,
groupChannels,
};
};

const createCompareByOrder = (order?: Order) => {
if (!order) return undefined;

return (channel1: SendbirdGroupChannel, channel2: SendbirdGroupChannel): number => {
switch (order) {
case 'latest_last_message': {
if (channel1.lastMessage && channel2.lastMessage) {
return channel2.lastMessage.createdAt - channel1.lastMessage.createdAt;
} else if (channel1.lastMessage) {
return -1;
} else if (channel2.lastMessage) {
return 1;
} else {
return channel2.createdAt - channel1.createdAt;
}
}

case 'chronological': {
return channel2.createdAt - channel1.createdAt;
}

case 'channel_name_alphabetical': {
return channel1.name.localeCompare(channel2.name);
}
default: {
return 0;
}
}
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { useEffect, useRef } from 'react';

import type { SendbirdChatSDK, SendbirdGroupChannelCollection } from '@sendbird/uikit-utils';
import { useAsyncEffect, useFreshCallback, useUniqId } from '@sendbird/uikit-utils';

import { useAppFeatures } from '../../common/useAppFeatures';
import { useChannelHandler } from '../../handler/useChannelHandler';
import type { UseGroupChannelList, UseGroupChannelListOptions } from '../../types';
import { useGroupChannelListReducer } from './reducer';

const HOOK_NAME = 'useGroupChannelListWithCollection';

const createGroupChannelListCollection = (
sdk: SendbirdChatSDK,
collectionCreator: UseGroupChannelListOptions['collectionCreator'],
) => {
const passedCollection = collectionCreator?.();
if (passedCollection) return passedCollection;

const defaultOptions = {
includeEmpty: false,
limit: 5,
order: sdk.GroupChannelCollection.GroupChannelOrder.LATEST_LAST_MESSAGE,
};
const collectionBuilder = sdk.GroupChannel.createGroupChannelCollection();
const groupChannelFilter = new sdk.GroupChannelFilter();
groupChannelFilter.includeEmpty = defaultOptions.includeEmpty;

return collectionBuilder
.setFilter(groupChannelFilter)
.setLimit(defaultOptions.limit)
.setOrder(defaultOptions.order)
.build();
};

export const useGroupChannelListWithCollection: UseGroupChannelList = (sdk, userId, options) => {
const id = useUniqId(HOOK_NAME);
const { deliveryReceiptEnabled } = useAppFeatures(sdk);

const collectionRef = useRef<SendbirdGroupChannelCollection>();

const { loading, groupChannels, refreshing, setChannels, deleteChannels, updateRefreshing, updateLoading } =
useGroupChannelListReducer();

const updateChannelsAndMarkAsDelivered = (markAsDelivered: boolean) => {
const channels = collectionRef.current?.channelList ?? [];
setChannels(channels, true);
if (markAsDelivered && deliveryReceiptEnabled) channels.forEach((channel) => sdk.markAsDelivered(channel.url));
};

const init = useFreshCallback(async (uid?: string) => {
if (collectionRef.current) collectionRef.current?.dispose();

if (uid) {
collectionRef.current = createGroupChannelListCollection(sdk, options?.collectionCreator);

if (collectionRef.current?.hasMore) {
await collectionRef.current?.loadMore();
updateChannelsAndMarkAsDelivered(true);
}

collectionRef.current?.setGroupChannelCollectionHandler({
onChannelsAdded() {
updateChannelsAndMarkAsDelivered(true);
},
onChannelsUpdated() {
updateChannelsAndMarkAsDelivered(true);
},
onChannelsDeleted() {
updateChannelsAndMarkAsDelivered(false);
},
});
}
});

useEffect(() => {
return () => {
if (collectionRef.current) collectionRef.current?.dispose();
};
}, []);

useAsyncEffect(async () => {
updateLoading(true);
await init(userId);
updateLoading(false);
}, [init, userId]);

useChannelHandler(sdk, `${HOOK_NAME}_${id}`, {
onUserBanned: (channel, user) => {
const isMe = user.userId === userId;
if (isMe) deleteChannels([channel.url]);
else updateChannelsAndMarkAsDelivered(false);
},
});

const refresh = useFreshCallback(async () => {
updateRefreshing(true);
await init(userId);
updateRefreshing(false);
});

const next = useFreshCallback(async () => {
if (collectionRef.current?.hasMore) {
await collectionRef.current?.loadMore();
updateChannelsAndMarkAsDelivered(true);
}
});

return {
loading,
groupChannels,
refresh,
refreshing,
next,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { useRef } from 'react';
import type Sendbird from 'sendbird';

import type { SendbirdChannel, SendbirdChatSDK } from '@sendbird/uikit-utils';
import { useAsyncEffect, useFreshCallback } from '@sendbird/uikit-utils';

import { useAppFeatures } from '../../common/useAppFeatures';
import { useChannelHandler } from '../../handler/useChannelHandler';
import type { UseGroupChannelList, UseGroupChannelListOptions } from '../../types';
import { useGroupChannelListReducer } from './reducer';

const HOOK_NAME = 'useGroupChannelListWithQuery';

const createGroupChannelListQuery = (
sdk: SendbirdChatSDK,
queryCreator: UseGroupChannelListOptions['queryCreator'],
) => {
const passedQuery = queryCreator?.();
if (passedQuery) return passedQuery;

const defaultOptions = {
includeEmpty: false,
limit: 20,
order: 'latest_last_message',
} as const;
const defaultQuery = sdk.GroupChannel.createMyGroupChannelListQuery();
defaultQuery.limit = defaultOptions.limit;
defaultQuery.order = defaultOptions.order;
defaultQuery.includeEmpty = defaultOptions.includeEmpty;
return defaultQuery;
};

export const useGroupChannelListWithQuery: UseGroupChannelList = (sdk, userId, options) => {
const { deliveryReceiptEnabled } = useAppFeatures(sdk);
const queryRef = useRef<Sendbird.GroupChannelListQuery>();

const {
loading,
groupChannels,
refreshing,
updateChannels,
setChannels,
deleteChannels,
updateRefreshing,
updateLoading,
updateOrder,
} = useGroupChannelListReducer();

const updateChannelsAndMarkAsDelivered = (channels: SendbirdChannel[]) => {
updateChannels(channels);
if (deliveryReceiptEnabled) channels.forEach((channel) => sdk.markAsDelivered(channel.url));
};

const init = useFreshCallback(async (uid?: string) => {
if (uid) {
queryRef.current = createGroupChannelListQuery(sdk, options?.queryCreator);
updateOrder(queryRef.current?.order);

if (queryRef.current?.hasNext) {
const channels = await queryRef.current.next();

setChannels(channels, true);
if (deliveryReceiptEnabled) channels.forEach((channel) => sdk.markAsDelivered(channel.url));
}
}
});

useAsyncEffect(async () => {
updateLoading(true);
await init(userId);
updateLoading(false);
}, [init, userId]);

useChannelHandler(sdk, HOOK_NAME, {
onChannelChanged: (channel) => updateChannelsAndMarkAsDelivered([channel]),
onChannelFrozen: (channel) => updateChannels([channel]),
onChannelUnfrozen: (channel) => updateChannels([channel]),
onChannelMemberCountChanged: (channels) => updateChannels(channels),
onChannelDeleted: (url) => deleteChannels([url]),
onUserJoined: (channel) => updateChannelsAndMarkAsDelivered([channel]),
onUserLeft: (channel, user) => {
const isMe = user.userId === userId;
if (isMe) deleteChannels([channel.url]);
else updateChannelsAndMarkAsDelivered([channel]);
},
onUserBanned(channel, user) {
const isMe = user.userId === userId;
if (isMe) deleteChannels([channel.url]);
else updateChannelsAndMarkAsDelivered([channel]);
},
});

const refresh = useFreshCallback(async () => {
updateRefreshing(true);
await init(userId);
updateRefreshing(false);
});

const next = useFreshCallback(async () => {
if (queryRef.current?.hasNext) {
const channels = await queryRef.current.next();
setChannels(channels, false);
if (deliveryReceiptEnabled) channels.forEach((channel) => sdk.markAsDelivered(channel.url));
}
});

return {
loading,
groupChannels,
refresh,
refreshing,
next,
};
};
Loading

0 comments on commit 913875d

Please sign in to comment.