Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next Release #2862

Merged
merged 5 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
[![NPM](https://img.shields.io/npm/v/stream-chat-react-native.svg)](https://www.npmjs.com/package/stream-chat-react-native)
[![Build Status](https://github.com/GetStream/stream-chat-react-native/actions/workflows/release.yml/badge.svg)](https://github.com/GetStream/stream-chat-react-native/actions)
[![Component Reference](https://img.shields.io/badge/docs-component%20reference-blue.svg)](https://getstream.io/chat/docs/sdk/reactnative)
![JS Bundle Size](https://img.shields.io/badge/js_bundle_size-456%20KB-blue)
![JS Bundle Size](https://img.shields.io/badge/js_bundle_size-460%20KB-blue)

<img align="right" src="https://getstream.imgix.net/images/ios-chat-tutorial/[email protected]?auto=format,enhance" width="50%" />

Expand Down
10 changes: 5 additions & 5 deletions examples/SampleApp/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2164,7 +2164,7 @@ PODS:
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.10)
- SocketRocket (0.7.1)
- stream-chat-react-native (6.0.0):
- stream-chat-react-native (6.0.1):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -2474,7 +2474,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
boost: 1dca942403ed9342f98334bf4c3621f011aa7946
DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385
DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5
FBLazyVector: 1bf99bb46c6af9a2712592e707347315f23947aa
Firebase: 7a56fe4f56b5ab81b86a6822f5b8f909ae6fc7e2
FirebaseAnalytics: 2f4a11eeb7a0e9c6fcf642d4e6aaca7fa4d38c28
Expand All @@ -2488,7 +2488,7 @@ SPEC CHECKSUMS:
FirebaseRemoteConfigInterop: e75e348953352a000331eb77caf01e424248e176
FirebaseSessions: b252b3f91a51186188882ea8e7e1730fc1eee391
fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
glog: 69ef571f3de08433d766d614c73a9838a06bf7eb
GoogleAppMeasurement: ee5c2d2242816773fbf79e5b0563f5355ef1c315
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
Expand Down Expand Up @@ -2576,9 +2576,9 @@ SPEC CHECKSUMS:
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
stream-chat-react-native: 557a9b07b068fea9a1670302ca43f8589d884e33
stream-chat-react-native: 4b3bb162446ad9b25c745fc8083a2516d363d5eb
Yoga: 7548e4449365bf0ef60db4aefe58abff37fcabec

PODFILE CHECKSUM: 4f662370295f8f9cee909f1a4c59a614999a209d

COCOAPODS: 1.14.3
COCOAPODS: 1.16.2
8 changes: 4 additions & 4 deletions examples/SampleApp/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7952,10 +7952,10 @@ statuses@~1.5.0:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==

[email protected].0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-6.0.0.tgz#77798c7082877572ef70223e1f799d22f0c78fe7"
integrity sha512-3cFao8iL2MjP7nhVRAl1vi526FbPlqUj4BHnYQ7sUNe+xb4z/HCEL6fKFh8kIfK5MEAacOQO4juPPQktoIf7zg==
[email protected].1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-6.0.1.tgz#a67f14685519cafa58466d28eee8f1edc9dbafcf"
integrity sha512-kyHgGn2PF+JTt7eEKdHMot9Nxzx+yecnlut9oyhi/IJbxOwpjIgB87+rdQXEI5o8SeNwQuAeV3VatxGaxl5Jbw==
dependencies:
"@gorhom/bottom-sheet" "^5.0.6"
dayjs "1.10.5"
Expand Down
126 changes: 102 additions & 24 deletions package/src/components/Channel/Channel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
Channel as ChannelType,
EventHandler,
FormatMessageResponse,
logChatPromiseExecution,
MessageResponse,
Reaction,
SendMessageAPIResponse,
Expand Down Expand Up @@ -92,7 +91,7 @@ import {
isImagePickerAvailable,
} from '../../native';
import * as dbApi from '../../store/apis';
import { DefaultStreamChatGenerics, FileTypes } from '../../types/types';
import { ChannelUnreadState, DefaultStreamChatGenerics, FileTypes } from '../../types/types';
import { addReactionToLocalState } from '../../utils/addReactionToLocalState';
import { compressedImageURI } from '../../utils/compressImage';
import { DBSyncManager } from '../../utils/DBSyncManager';
Expand Down Expand Up @@ -179,6 +178,7 @@ import { ScrollToBottomButton as ScrollToBottomButtonDefault } from '../MessageL
import { StickyHeader as StickyHeaderDefault } from '../MessageList/StickyHeader';
import { TypingIndicator as TypingIndicatorDefault } from '../MessageList/TypingIndicator';
import { TypingIndicatorContainer as TypingIndicatorContainerDefault } from '../MessageList/TypingIndicatorContainer';
import { UnreadMessagesNotification as UnreadMessagesNotificationDefault } from '../MessageList/UnreadMessagesNotification';
import { MessageActionList as MessageActionListDefault } from '../MessageMenu/MessageActionList';
import { MessageActionListItem as MessageActionListItemDefault } from '../MessageMenu/MessageActionListItem';
import { MessageMenu as MessageMenuDefault } from '../MessageMenu/MessageMenu';
Expand All @@ -188,6 +188,15 @@ import { MessageUserReactionsAvatar as MessageUserReactionsAvatarDefault } from
import { MessageUserReactionsItem as MessageUserReactionsItemDefault } from '../MessageMenu/MessageUserReactionsItem';
import { Reply as ReplyDefault } from '../Reply/Reply';

export type MarkReadFunctionOptions = {
/**
* Signal, whether the `channelUnreadUiState` should be updated.
* By default, the local state update is prevented when the Channel component is mounted.
* This is in order to keep the UI indicating the original unread state, when the user opens a channel.
*/
updateChannelUnreadState?: boolean;
};

const styles = StyleSheet.create({
selectChannel: { fontWeight: 'bold', padding: 16 },
});
Expand Down Expand Up @@ -301,6 +310,7 @@ export type ChannelPropsWithContext<
| 'handleDelete'
| 'handleEdit'
| 'handleFlag'
| 'handleMarkUnread'
| 'handleMute'
| 'handlePinMessage'
| 'handleReaction'
Expand Down Expand Up @@ -360,6 +370,7 @@ export type ChannelPropsWithContext<
| 'VideoThumbnail'
| 'PollContent'
| 'hasCreatePoll'
| 'UnreadMessagesNotification'
| 'StreamingMessageView'
>
> &
Expand All @@ -384,7 +395,10 @@ export type ChannelPropsWithContext<
* Overrides the Stream default mark channel read request (Advanced usage only)
* @param channel Channel object
*/
doMarkReadRequest?: (channel: ChannelType<StreamChatGenerics>) => void;
doMarkReadRequest?: (
channel: ChannelType<StreamChatGenerics>,
setChannelUnreadUiState?: (state: ChannelUnreadState) => void,
) => void;
/**
* Overrides the Stream default send message request (Advanced usage only)
* @param channelId
Expand Down Expand Up @@ -433,6 +447,10 @@ export type ChannelPropsWithContext<
* Custom loading error indicator to override the Stream default
*/
LoadingErrorIndicator?: React.ComponentType<LoadingErrorProps>;
/**
* Boolean flag to enable/disable marking the channel as read on mount
*/
markReadOnMount?: boolean;
maxMessageLength?: number;
/**
* Load the channel at a specified message instead of the most recent message.
Expand Down Expand Up @@ -529,6 +547,7 @@ const ChannelWithContext = <
handleDelete,
handleEdit,
handleFlag,
handleMarkUnread,
handleMute,
handlePinMessage,
handleQuotedReply,
Expand Down Expand Up @@ -566,6 +585,7 @@ const ChannelWithContext = <
loadingMore: loadingMoreProp,
loadingMoreRecent: loadingMoreRecentProp,
markdownRules,
markReadOnMount = true,
maxMessageLength: maxMessageLengthProp,
maxNumberOfFiles = 10,
maxTimeBetweenGroupedMessages,
Expand Down Expand Up @@ -647,6 +667,7 @@ const ChannelWithContext = <
threadMessages,
TypingIndicator = TypingIndicatorDefault,
TypingIndicatorContainer = TypingIndicatorContainerDefault,
UnreadMessagesNotification = UnreadMessagesNotificationDefault,
UploadProgressIndicator = UploadProgressIndicatorDefault,
UrlPreview = CardDefault,
VideoThumbnail = VideoThumbnailDefault,
Expand Down Expand Up @@ -674,10 +695,13 @@ const ChannelWithContext = <
const [thread, setThread] = useState<MessageType<StreamChatGenerics> | null>(threadProps || null);
const [threadHasMore, setThreadHasMore] = useState(true);
const [threadLoadingMore, setThreadLoadingMore] = useState(false);
const [channelUnreadState, setChannelUnreadState] = useState<ChannelUnreadState | undefined>(
undefined,
);

const syncingChannelRef = useRef(false);

const { setTargetedMessage, targetedMessage } = useTargetedMessage();
const { highlightedMessageId, setTargetedMessage, targetedMessage } = useTargetedMessage();

/**
* This ref will hold the abort controllers for
Expand All @@ -692,6 +716,7 @@ const ChannelWithContext = <
const {
copyStateFromChannel,
initStateFromChannel,
setRead,
setTyping,
state: channelState,
} = useChannelDataState<StreamChatGenerics>(channel);
Expand Down Expand Up @@ -754,6 +779,22 @@ const ChannelWithContext = <
}
}

if (event.type === 'notification.mark_unread') {
setChannelUnreadState((prev) => {
if (!(event.last_read_at && event.user)) return prev;
return {
first_unread_message_id: event.first_unread_message_id,
last_read: new Date(event.last_read_at),
last_read_message_id: event.last_read_message_id,
unread_messages: event.unread_messages ?? 0,
};
});
}

if (event.type === 'channel.truncated' && event.cid === channel.cid) {
setChannelUnreadState(undefined);
}

// only update channel state if the events are not the previously subscribed useEffect's subscription events
if (channel && channel.initialized) {
copyChannelState();
Expand All @@ -764,6 +805,8 @@ const ChannelWithContext = <
useEffect(() => {
let listener: ReturnType<typeof channel.on>;
const initChannel = async () => {
setLastRead(new Date());
const unreadCount = channel.countUnread();
if (!channel || !shouldSyncChannel || channel.offlineMode) return;
let errored = false;

Expand All @@ -782,14 +825,33 @@ const ChannelWithContext = <
loadInitialMessagesStateFromChannel(channel, channel.state.messagePagination.hasPrev);
}

if (client.user?.id && channel.state.read[client.user.id]) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { user, ...ownReadState } = channel.state.read[client.user.id];
setChannelUnreadState(ownReadState);
}

if (messageId) {
await loadChannelAroundMessage({ messageId, setTargetedMessage });
} else if (
initialScrollToFirstUnreadMessage &&
channel.countUnread() > scrollToFirstUnreadThreshold
client.user &&
unreadCount > scrollToFirstUnreadThreshold
) {
await loadChannelAtFirstUnreadMessage({ setTargetedMessage });
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { user, ...ownReadState } = channel.state.read[client.user.id];

await loadChannelAtFirstUnreadMessage({
channelUnreadState: ownReadState,
setChannelUnreadState,
setTargetedMessage,
});
}

if (unreadCount > 0 && markReadOnMount) {
await markRead({ updateChannelUnreadState: false });
}

listener = channel.on(handleEvent);
};

Expand Down Expand Up @@ -819,12 +881,12 @@ const ChannelWithContext = <
*/
useEffect(() => {
const handleEvent: EventHandler<StreamChatGenerics> = (event) => {
if (channel.cid === event.cid) copyChannelState();
if (channel.cid === event.cid) setRead(channel);
};

const { unsubscribe } = client.on('notification.mark_read', handleEvent);
return unsubscribe;
}, [channel.cid, client, copyChannelState]);
}, [channel, client, setRead]);

const threadPropsExists = !!threadProps;

Expand Down Expand Up @@ -858,23 +920,33 @@ const ChannelWithContext = <
/**
* CHANNEL METHODS
*/
const markRead: ChannelContextValue<StreamChatGenerics>['markRead'] = useRef(
throttle(
() => {
if (!channel || channel?.disconnected || !clientChannelConfig?.read_events) {
return;
}
const markRead: ChannelContextValue<StreamChatGenerics>['markRead'] = throttle(
async (options?: MarkReadFunctionOptions) => {
const { updateChannelUnreadState = true } = options ?? {};
if (!channel || channel?.disconnected || !clientChannelConfig?.read_events) {
return;
}

if (doMarkReadRequest) {
doMarkReadRequest(channel);
} else {
logChatPromiseExecution(channel.markRead(), 'mark read');
if (doMarkReadRequest) {
doMarkReadRequest(channel, updateChannelUnreadState ? setChannelUnreadState : undefined);
} else {
try {
const response = await channel.markRead();
if (updateChannelUnreadState && response && lastRead) {
setChannelUnreadState({
last_read: lastRead,
last_read_message_id: response?.event.last_read_message_id,
unread_messages: 0,
});
}
} catch (err) {
console.log('Error marking channel as read:', err);
}
},
defaultThrottleInterval,
throttleOptions,
),
).current;
}
},
defaultThrottleInterval,
throttleOptions,
);

const reloadThread = async () => {
if (!channel || !thread?.id) return;
Expand Down Expand Up @@ -1596,8 +1668,9 @@ const ChannelWithContext = <
overrideCapabilities: overrideOwnCapabilities,
});

const channelContext = useCreateChannelContext({
const channelContext = useCreateChannelContext<StreamChatGenerics>({
channel,
channelUnreadState,
disabled: !!channel?.data?.frozen,
EmptyStateIndicator,
enableMessageGroupingByUser,
Expand All @@ -1608,9 +1681,11 @@ const ChannelWithContext = <
!!(clientChannelConfig?.commands || [])?.some((command) => command.name === 'giphy'),
hideDateSeparators,
hideStickyDateHeader,
highlightedMessageId,
isChannelActive: shouldSyncChannel,
lastRead,
loadChannelAroundMessage,
loadChannelAtFirstUnreadMessage,
loading: channelMessagesState.loading,
LoadingIndicator,
markRead,
Expand All @@ -1620,6 +1695,7 @@ const ChannelWithContext = <
read: channelState.read ?? {},
reloadChannel,
scrollToFirstUnreadThreshold,
setChannelUnreadState,
setLastRead,
setTargetedMessage,
StickyHeader,
Expand Down Expand Up @@ -1748,6 +1824,7 @@ const ChannelWithContext = <
handleDelete,
handleEdit,
handleFlag,
handleMarkUnread,
handleMute,
handlePinMessage,
handleQuotedReply,
Expand Down Expand Up @@ -1815,6 +1892,7 @@ const ChannelWithContext = <
targetedMessage,
TypingIndicator,
TypingIndicatorContainer,
UnreadMessagesNotification,
updateMessage,
UrlPreview,
VideoThumbnail,
Expand Down
Loading
Loading