-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(chat-hooks): respect the order of group channel collection and qu…
…ery.
- Loading branch information
Showing
10 changed files
with
395 additions
and
304 deletions.
There are no files selected for viewing
2 changes: 1 addition & 1 deletion
2
...-hooks/src/channel/useGroupChannelList.ts → .../src/channel/useGroupChannelList/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
154 changes: 154 additions & 0 deletions
154
packages/uikit-chat-hooks/src/channel/useGroupChannelList/reducer.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
}; | ||
}; |
116 changes: 116 additions & 0 deletions
116
...ges/uikit-chat-hooks/src/channel/useGroupChannelList/useGroupChannelListWithCollection.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; | ||
}; |
114 changes: 114 additions & 0 deletions
114
packages/uikit-chat-hooks/src/channel/useGroupChannelList/useGroupChannelListWithQuery.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; | ||
}; |
Oops, something went wrong.