diff --git a/.github/workflows/sample-distribution.yml b/.github/workflows/sample-distribution.yml index 366b4f25af..2f8252bbd5 100644 --- a/.github/workflows/sample-distribution.yml +++ b/.github/workflows/sample-distribution.yml @@ -33,7 +33,7 @@ jobs: ${{ runner.os }}-pods- - name: iOS Pods setup working-directory: examples/SampleApp/ios - run: bundle exec pod install + run: pod update hermes-engine --no-repo-update - name: Build and release Testflight QA working-directory: examples/SampleApp run: bundle exec fastlane deploy_to_testflight_qa deploy:${{ github.ref == 'refs/heads/develop' }}; diff --git a/examples/SampleApp/CHANGELOG.md b/examples/SampleApp/CHANGELOG.md index 73aa2c1d82..c0f4aa5872 100644 --- a/examples/SampleApp/CHANGELOG.md +++ b/examples/SampleApp/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +### [1.31.5](https://github.com/GetStream/stream-chat-react-native/compare/sampleapp@v1.31.4...sampleapp@v1.31.5) (2024-11-08) + + +### Workspaces + +* Following linked packages updated: [stream-chat-react-native] + +### [1.31.4](https://github.com/GetStream/stream-chat-react-native/compare/sampleapp@v1.31.3...sampleapp@v1.31.4) (2024-11-07) + + +### Workspaces + +* Following linked packages updated: [stream-chat-react-native] + ### [1.31.3](https://github.com/GetStream/stream-chat-react-native/compare/sampleapp@v1.31.2...sampleapp@v1.31.3) (2024-11-04) diff --git a/examples/SampleApp/Gemfile.lock b/examples/SampleApp/Gemfile.lock index 50ef699e97..ef41caa79c 100644 --- a/examples/SampleApp/Gemfile.lock +++ b/examples/SampleApp/Gemfile.lock @@ -323,6 +323,7 @@ DEPENDENCIES fastlane-plugin-stream_actions (= 0.3.73) rubocop-performance rubocop-require_tools + xcodeproj (< 1.26.0) RUBY VERSION ruby 3.3.4p94 diff --git a/examples/SampleApp/ios/Podfile.lock b/examples/SampleApp/ios/Podfile.lock index 99d2473e57..d435ba502f 100644 --- a/examples/SampleApp/ios/Podfile.lock +++ b/examples/SampleApp/ios/Podfile.lock @@ -2187,7 +2187,7 @@ PODS: - libwebp (~> 1.0) - SDWebImage/Core (~> 5.10) - SocketRocket (0.7.1) - - stream-chat-react-native (5.41.2): + - stream-chat-react-native (5.41.4): - DoubleConversion - glog - hermes-engine @@ -2499,8 +2499,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: 4cb898d0bf20404aab1850c656dcea009429d6c1 - DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 + boost: 1dca942403ed9342f98334bf4c3621f011aa7946 + DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 FBLazyVector: 7075bb12898bc3998fd60f4b7ca422496cc2cdf7 Firebase: 91fefd38712feb9186ea8996af6cbdef41473442 FirebaseAnalytics: b5efc493eb0f40ec560b04a472e3e1a15d39ca13 @@ -2513,8 +2513,8 @@ SPEC CHECKSUMS: FirebaseMessaging: 4d52717dd820707cc4eadec5eb981b4832ec8d5d FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d FirebaseSessions: dbd14adac65ce996228652c1fc3a3f576bdf3ecc - fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 - glog: 69ef571f3de08433d766d614c73a9838a06bf7eb + fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be + glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 GoogleAppMeasurement: f3abf08495ef2cba7829f15318c373b8d9226491 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 @@ -2524,7 +2524,7 @@ SPEC CHECKSUMS: op-sqlite: 88dd68621bea1e7ea28606f76228a84a23d5ad58 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 - RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740 + RCT-Folly: bf5c0376ffe4dd2cf438dcf86db385df9fdce648 RCTDeprecation: fde92935b3caa6cb65cbff9fbb7d3a9867ffb259 RCTRequired: 75c6cee42d21c1530a6f204ba32ff57335d19007 RCTTypeSafety: 7e6fe47bfb693c50d4669db1a480ca5331795f5b @@ -2603,9 +2603,9 @@ SPEC CHECKSUMS: SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - stream-chat-react-native: fde9ff0df6796523ef66b7bef2274f0deef8ff19 + stream-chat-react-native: 5f358230d87074128d47ea6bee43ef6a81720186 Yoga: 83048fe4b4ce15018a5e9ccbf0b06bd76fc643d4 PODFILE CHECKSUM: 4f662370295f8f9cee909f1a4c59a614999a209d -COCOAPODS: 1.16.2 +COCOAPODS: 1.14.3 diff --git a/examples/SampleApp/package.json b/examples/SampleApp/package.json index fa0856e064..8938b5b681 100644 --- a/examples/SampleApp/package.json +++ b/examples/SampleApp/package.json @@ -1,6 +1,6 @@ { "name": "sampleapp", - "version": "1.31.3", + "version": "1.31.5", "private": true, "repository": { "type": "git", diff --git a/examples/SampleApp/yarn.lock b/examples/SampleApp/yarn.lock index b5494738ec..ad859895ab 100644 --- a/examples/SampleApp/yarn.lock +++ b/examples/SampleApp/yarn.lock @@ -7361,10 +7361,10 @@ statuses@~1.5.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -stream-chat-react-native-core@5.41.2: - version "5.41.2" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.41.2.tgz#74fda94b80e28dd542a90abbfeb0291bf97a3d77" - integrity sha512-mr8yTj7O40w13EvOkhDTJH8OCisdB8f8REeFFt2y2LA2PWkFb5hpI1e5uFMW7XTuL58ftD1jXdF2ug6n4OUDug== +stream-chat-react-native-core@5.41.4: + version "5.41.4" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.41.4.tgz#55104e1eb10f464b27d82b9566a18005723ea26d" + integrity sha512-Fe2AnbfZ98nRfP5BwMOwR2iae/hT8N5oEiYOb2FwbgtJqot4UFABZDCxMVcoYcFouOK1gc5BkTjdnXYYm/L8EQ== dependencies: "@gorhom/bottom-sheet" "^4.6.4" dayjs "1.10.5" diff --git a/package/CHANGELOG.md b/package/CHANGELOG.md index 118f2c0a51..8742ce2ff3 100644 --- a/package/CHANGELOG.md +++ b/package/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +### [5.41.4](https://github.com/GetStream/stream-chat-react-native/compare/v5.41.3...v5.41.4) (2024-11-08) + + +### Bug Fixes + +* poll edge cases ([#2768](https://github.com/GetStream/stream-chat-react-native/issues/2768)) ([b26b98c](https://github.com/GetStream/stream-chat-react-native/commit/b26b98c5cb34e5446c1600026fbaa1f442d8dbcb)) +* theme for the message bubble and replies ([#2766](https://github.com/GetStream/stream-chat-react-native/issues/2766)) ([980c383](https://github.com/GetStream/stream-chat-react-native/commit/980c3832a4a59ebab07c67f1d24a50ebe587e8fe)) + +### [5.41.3](https://github.com/GetStream/stream-chat-react-native/compare/v5.41.2...v5.41.3) (2024-11-07) + + +### Bug Fixes + +* check for channel validity before consuming config ([#2760](https://github.com/GetStream/stream-chat-react-native/issues/2760)) ([ff53399](https://github.com/GetStream/stream-chat-react-native/commit/ff5339982914051b6e8f941a4799fc772ce409aa)) +* message disallowed indicator display ([#2754](https://github.com/GetStream/stream-chat-react-native/issues/2754)) ([fe08bd7](https://github.com/GetStream/stream-chat-react-native/commit/fe08bd75ea1d7d3041b86b1e8519f2f04b9c19bc)) +* native image picker poll control ([#2762](https://github.com/GetStream/stream-chat-react-native/issues/2762)) ([b9c49be](https://github.com/GetStream/stream-chat-react-native/commit/b9c49be85b8d7927dc64f55d7e9ba01b11737016)) +* receiverMessageBackgroundColor hotfix ([#2763](https://github.com/GetStream/stream-chat-react-native/issues/2763)) ([5258a18](https://github.com/GetStream/stream-chat-react-native/commit/5258a18468fe9716918ec9e663ae5e0861b17df5)) + ### [5.41.2](https://github.com/GetStream/stream-chat-react-native/compare/v5.41.1...v5.41.2) (2024-11-04) diff --git a/package/expo-package/package.json b/package/expo-package/package.json index a6294a21cf..d194952cad 100644 --- a/package/expo-package/package.json +++ b/package/expo-package/package.json @@ -1,7 +1,7 @@ { "name": "stream-chat-expo", "description": "The official Expo SDK for Stream Chat, a service for building chat applications", - "version": "5.41.2", + "version": "5.41.4", "author": { "company": "Stream.io Inc", "name": "Stream.io Inc" @@ -10,7 +10,7 @@ "main": "src/index.js", "types": "types/index.d.ts", "dependencies": { - "stream-chat-react-native-core": "5.41.2" + "stream-chat-react-native-core": "5.41.4" }, "peerDependencies": { "expo": ">=51.0.0", diff --git a/package/expo-package/yarn.lock b/package/expo-package/yarn.lock index 9374ee4e0c..a8db4e9d2e 100644 --- a/package/expo-package/yarn.lock +++ b/package/expo-package/yarn.lock @@ -5113,10 +5113,10 @@ stream-buffers@2.2.x, stream-buffers@~2.2.0: resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== -stream-chat-react-native-core@5.41.2: - version "5.41.2" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.41.2.tgz#74fda94b80e28dd542a90abbfeb0291bf97a3d77" - integrity sha512-mr8yTj7O40w13EvOkhDTJH8OCisdB8f8REeFFt2y2LA2PWkFb5hpI1e5uFMW7XTuL58ftD1jXdF2ug6n4OUDug== +stream-chat-react-native-core@5.41.4: + version "5.41.4" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.41.4.tgz#55104e1eb10f464b27d82b9566a18005723ea26d" + integrity sha512-Fe2AnbfZ98nRfP5BwMOwR2iae/hT8N5oEiYOb2FwbgtJqot4UFABZDCxMVcoYcFouOK1gc5BkTjdnXYYm/L8EQ== dependencies: "@gorhom/bottom-sheet" "^4.6.4" dayjs "1.10.5" diff --git a/package/native-package/package.json b/package/native-package/package.json index b7ff53368a..095ba68bc1 100644 --- a/package/native-package/package.json +++ b/package/native-package/package.json @@ -1,7 +1,7 @@ { "name": "stream-chat-react-native", "description": "The official React Native SDK for Stream Chat, a service for building chat applications", - "version": "5.41.2", + "version": "5.41.4", "homepage": "https://www.npmjs.com/package/stream-chat-react-native", "author": { "company": "Stream.io Inc", @@ -20,7 +20,7 @@ "types": "types/index.d.ts", "dependencies": { "es6-symbol": "^3.1.3", - "stream-chat-react-native-core": "5.41.2" + "stream-chat-react-native-core": "5.41.4" }, "peerDependencies": { "@react-native-camera-roll/camera-roll": ">=7.8.0", diff --git a/package/native-package/yarn.lock b/package/native-package/yarn.lock index 0724955d0b..9a8108dbd0 100644 --- a/package/native-package/yarn.lock +++ b/package/native-package/yarn.lock @@ -4242,10 +4242,10 @@ statuses@~1.5.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -stream-chat-react-native-core@5.41.2: - version "5.41.2" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.41.2.tgz#74fda94b80e28dd542a90abbfeb0291bf97a3d77" - integrity sha512-mr8yTj7O40w13EvOkhDTJH8OCisdB8f8REeFFt2y2LA2PWkFb5hpI1e5uFMW7XTuL58ftD1jXdF2ug6n4OUDug== +stream-chat-react-native-core@5.41.4: + version "5.41.4" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.41.4.tgz#55104e1eb10f464b27d82b9566a18005723ea26d" + integrity sha512-Fe2AnbfZ98nRfP5BwMOwR2iae/hT8N5oEiYOb2FwbgtJqot4UFABZDCxMVcoYcFouOK1gc5BkTjdnXYYm/L8EQ== dependencies: "@gorhom/bottom-sheet" "^4.6.4" dayjs "1.10.5" diff --git a/package/package.json b/package/package.json index 43d914cb63..83e89f5add 100644 --- a/package/package.json +++ b/package/package.json @@ -1,7 +1,7 @@ { "name": "stream-chat-react-native-core", "description": "The official React Native and Expo components for Stream Chat, a service for building chat applications", - "version": "5.41.2", + "version": "5.41.4", "author": { "company": "Stream.io Inc", "name": "Stream.io Inc" diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 10c83b1377..fd13f8277e 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -881,13 +881,7 @@ const ChannelWithContext = < setThreadMessages(updatedThreadMessages); } - if ( - channel && - thread?.id && - event.message?.id === thread.id && - !threadInstance && - !thread.poll_id - ) { + if (channel && thread?.id && event.message?.id === thread.id && !threadInstance) { const updatedThread = channel.state.formatMessage(event.message); setThread(updatedThread); } diff --git a/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts b/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts index da58999427..cc43a21768 100644 --- a/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts +++ b/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts @@ -40,6 +40,7 @@ export type LatestMessagePreview< export type LatestMessagePreviewSelectorReturnType = { createdBy?: UserResponse | null; latestVotesByOption?: Record; + name?: string; }; const selector = ( @@ -47,6 +48,7 @@ const selector = ({ createdBy: nextValue.created_by, latestVotesByOption: nextValue.latest_votes_by_option, + name: nextValue.name, }); const getMessageSenderName = < @@ -145,8 +147,8 @@ const getLatestMessageDisplayText = < { bold: false, text: t('🏙 Attachment...') }, ]; } - if (message.poll && pollState) { - const { createdBy, latestVotesByOption } = pollState; + if (message.poll_id && pollState) { + const { createdBy, latestVotesByOption, name } = pollState; let latestVotes; if (latestVotesByOption) { latestVotes = Object.values(latestVotesByOption) @@ -161,7 +163,7 @@ const getLatestMessageDisplayText = < } const previewMessage = `${ client.userID === previewUser?.id ? 'You' : previewUser?.name - } ${previewAction}: ${message.poll.name}`; + } ${previewAction}: ${name}`; return [ { bold: false, text: '📊 ' }, { bold: false, text: previewMessage }, @@ -298,7 +300,8 @@ export const useLatestMessagePreview = < useEffect(() => { if (channelConfigExists) { - const read_events = channel.getConfig()?.read_events; + const read_events = + !channel.disconnected && !!channel?.id && channel.getConfig()?.read_events; if (typeof read_events === 'boolean') { setReadEvents(read_events); } @@ -310,7 +313,7 @@ export const useLatestMessagePreview = < const poll = client.polls.fromState(pollId); const pollState: LatestMessagePreviewSelectorReturnType = useStateStore(poll?.state, selector) ?? {}; - const { createdBy, latestVotesByOption } = pollState; + const { createdBy, latestVotesByOption, name } = pollState; useEffect( () => @@ -325,7 +328,15 @@ export const useLatestMessagePreview = < }), ), // eslint-disable-next-line react-hooks/exhaustive-deps - [channelLastMessageString, forceUpdate, readEvents, readStatus, latestVotesByOption, createdBy], + [ + channelLastMessageString, + forceUpdate, + readEvents, + readStatus, + latestVotesByOption, + createdBy, + name, + ], ); return latestMessagePreview; diff --git a/package/src/components/Chat/hooks/handleEventToSyncDB.ts b/package/src/components/Chat/hooks/handleEventToSyncDB.ts index a03e694e3e..6ca71eade6 100644 --- a/package/src/components/Chat/hooks/handleEventToSyncDB.ts +++ b/package/src/components/Chat/hooks/handleEventToSyncDB.ts @@ -235,11 +235,14 @@ export const handleEventToSyncDB = async < 'poll.vote_removed', ].includes(type) ) { - const poll = event.poll; + const { poll, poll_vote, type } = event; if (poll) { return updatePollMessage({ + eventType: type, flush, poll, + poll_vote, + userID: client?.userID || '', }); } } diff --git a/package/src/components/Message/MessageSimple/MessageContent.tsx b/package/src/components/Message/MessageSimple/MessageContent.tsx index 5cbcfe3f66..016745c7cc 100644 --- a/package/src/components/Message/MessageSimple/MessageContent.tsx +++ b/package/src/components/Message/MessageSimple/MessageContent.tsx @@ -139,7 +139,7 @@ const MessageContentWithContext = < const { theme: { - colors: { grey_gainsboro, grey_whisper }, + colors: { grey_whisper, light_gray }, messageSimple: { content: { container: { @@ -171,7 +171,7 @@ const MessageContentWithContext = < const { hasThreadReplies, isMessageErrorType, isMessageReceivedOrErrorType } = useMessageData({}); - const repliesCurveColor = !isMessageReceivedOrErrorType ? backgroundColor : grey_gainsboro; + const repliesCurveColor = !isMessageReceivedOrErrorType ? backgroundColor : light_gray; const getBorderRadius = () => { // enum('top', 'middle', 'bottom', 'single') diff --git a/package/src/components/Message/MessageSimple/MessageReplies.tsx b/package/src/components/Message/MessageSimple/MessageReplies.tsx index c98a82df0d..9f3092bf23 100644 --- a/package/src/components/Message/MessageSimple/MessageReplies.tsx +++ b/package/src/components/Message/MessageSimple/MessageReplies.tsx @@ -32,7 +32,7 @@ const styles = StyleSheet.create({ }, messageRepliesCurve: { borderTopWidth: 0, - borderWidth: 1, + borderWidth: 2, height: 16, width: 16, }, @@ -181,6 +181,7 @@ const areEqual = ; } diff --git a/package/src/components/MessageInput/components/NativeAttachmentPicker.tsx b/package/src/components/MessageInput/components/NativeAttachmentPicker.tsx index ae53be66a2..83120a8590 100644 --- a/package/src/components/MessageInput/components/NativeAttachmentPicker.tsx +++ b/package/src/components/MessageInput/components/NativeAttachmentPicker.tsx @@ -121,9 +121,8 @@ export const NativeAttachmentPicker = ({ // do not allow poll creation in threads const buttons = - threadList && hasCreatePoll && ownCapabilities.sendPoll - ? [] - : [ + !threadList && hasCreatePoll && ownCapabilities.sendPoll + ? [ { icon: , id: 'Poll', @@ -131,7 +130,8 @@ export const NativeAttachmentPicker = ({ openPollCreationDialog?.({ sendMessage }); }, }, - ]; + ] + : []; if (hasImagePicker) { buttons.push({ diff --git a/package/src/components/Poll/CreatePollContent.tsx b/package/src/components/Poll/CreatePollContent.tsx index 4c7a41ccf7..67c244b418 100644 --- a/package/src/components/Poll/CreatePollContent.tsx +++ b/package/src/components/Poll/CreatePollContent.tsx @@ -164,7 +164,12 @@ export const CreatePollContent = () => { {t('Multiple answers')} setMultipleAnswersAllowed(!multipleAnswersAllowed)} + onValueChange={() => { + if (multipleAnswersAllowed) { + setMaxVotesPerPersonEnabled(false); + } + setMultipleAnswersAllowed(!multipleAnswersAllowed); + }} value={multipleAnswersAllowed} /> diff --git a/package/src/components/Reply/Reply.tsx b/package/src/components/Reply/Reply.tsx index 1dfe5bc033..e9d0a97bc4 100644 --- a/package/src/components/Reply/Reply.tsx +++ b/package/src/components/Reply/Reply.tsx @@ -6,8 +6,9 @@ import dayjs from 'dayjs'; import merge from 'lodash/merge'; -import type { Attachment } from 'stream-chat'; +import type { Attachment, PollState } from 'stream-chat'; +import { useChatContext } from '../../contexts'; import { useChatConfigContext } from '../../contexts/chatConfigContext/ChatConfigContext'; import { useMessageContext } from '../../contexts/messageContext/MessageContext'; import { @@ -23,6 +24,7 @@ import { TranslationContextValue, useTranslationContext, } from '../../contexts/translationContext/TranslationContext'; +import { useStateStore } from '../../hooks'; import { DefaultStreamChatGenerics, FileTypes } from '../../types/types'; import { getResizedImageUrl } from '../../utils/getResizedImageUrl'; import { getTrimmedAttachmentTitle } from '../../utils/getTrimmedAttachmentTitle'; @@ -32,6 +34,7 @@ import { FileIcon as FileIconDefault } from '../Attachment/FileIcon'; import { VideoThumbnail } from '../Attachment/VideoThumbnail'; import { MessageAvatar as MessageAvatarDefault } from '../Message/MessageSimple/MessageAvatar'; import { MessageTextContainer } from '../Message/MessageSimple/MessageTextContainer'; +import { MessageType } from '../MessageList/hooks/useMessageList'; const styles = StyleSheet.create({ container: { @@ -73,6 +76,16 @@ const styles = StyleSheet.create({ }, }); +export type ReplySelectorReturnType = { + name?: string; +}; + +const selector = ( + nextValue: PollState, +): ReplySelectorReturnType => ({ + name: nextValue.name, +}); + type ReplyPropsWithContext< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Pick, 'quotedMessage'> & @@ -135,6 +148,7 @@ const ReplyWithContext = < >( props: ReplyPropsWithContext, ) => { + const { client } = useChatContext(); const { attachmentSize = 40, FileAttachmentIcon, @@ -169,6 +183,9 @@ const ReplyWithContext = < }, } = useTheme(); + const poll = client.polls.fromState((quotedMessage as MessageType)?.poll_id ?? ''); + const { name: pollName }: ReplySelectorReturnType = useStateStore(poll?.state, selector) ?? {}; + const messageText = quotedMessage ? quotedMessage.text : ''; const emojiOnlyText = useMemo(() => { @@ -265,8 +282,8 @@ const ReplyWithContext = < text: quotedMessage.type === 'deleted' ? `_${t('Message deleted')}_` - : quotedMessage.poll - ? `📊 ${quotedMessage.poll.name}` + : pollName + ? `📊 ${pollName}` : quotedMessage.text ? quotedMessage.text.length > 170 ? `${quotedMessage.text.slice(0, 170)}...` diff --git a/package/src/store/SqliteClient.ts b/package/src/store/SqliteClient.ts index 0721739e8e..9b33106e24 100644 --- a/package/src/store/SqliteClient.ts +++ b/package/src/store/SqliteClient.ts @@ -28,7 +28,7 @@ import type { PreparedQueries, Table } from './types'; * This way usage @op-engineering/op-sqlite package is scoped to a single class/file. */ export class SqliteClient { - static dbVersion = 6; + static dbVersion = 7; static dbName = DB_NAME; static dbLocation = DB_LOCATION; diff --git a/package/src/store/apis/getChannelMessages.ts b/package/src/store/apis/getChannelMessages.ts index 62fcbb0c2a..083218b734 100644 --- a/package/src/store/apis/getChannelMessages.ts +++ b/package/src/store/apis/getChannelMessages.ts @@ -7,8 +7,9 @@ import { selectReactionsForMessages } from './queries/selectReactionsForMessages import type { DefaultStreamChatGenerics } from '../../types/types'; import { isBlockedMessage } from '../../utils/utils'; import { mapStorableToMessage } from '../mappers/mapStorableToMessage'; +import { createSelectQuery } from '../sqlite-utils/createSelectQuery'; import { SqliteClient } from '../SqliteClient'; -import type { TableRowJoinedUser } from '../types'; +import type { TableRow, TableRowJoinedUser } from '../types'; export const getChannelMessages = async < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, @@ -35,6 +36,21 @@ export const getChannelMessages = async < } messageIdVsReactions[reaction.messageId].push(reaction); }); + const messageIdsVsPolls: Record> = {}; + const pollsById: Record> = {}; + const messagesWithPolls = messageRows.filter((message) => !!message.poll_id); + const polls = await SqliteClient.executeSql.apply( + null, + createSelectQuery('poll', ['*'], { + id: messagesWithPolls.map((message) => message.poll_id), + }), + ); + polls.forEach((poll) => { + pollsById[poll.id] = poll; + }); + messagesWithPolls.forEach((message) => { + messageIdsVsPolls[message.poll_id] = pollsById[message.poll_id]; + }); // Populate the messages. const cidVsMessages: Record[]> = {}; @@ -48,6 +64,7 @@ export const getChannelMessages = async < mapStorableToMessage({ currentUserId, messageRow: m, + pollRow: messageIdsVsPolls[m.poll_id], reactionRows: messageIdVsReactions[m.id], }), ); diff --git a/package/src/store/apis/updateMessage.ts b/package/src/store/apis/updateMessage.ts index 02424e8cf6..75ba87e21c 100644 --- a/package/src/store/apis/updateMessage.ts +++ b/package/src/store/apis/updateMessage.ts @@ -30,7 +30,9 @@ export const updateMessage = async ({ return queries; } - const storableMessage = mapMessageToStorable(message); + const storableMessage = mapMessageToStorable({ + ...message, + }); queries.push( createUpdateQuery('messages', storableMessage, { diff --git a/package/src/store/apis/updatePollMessage.ts b/package/src/store/apis/updatePollMessage.ts index dee3ae07b2..2ae92a22ec 100644 --- a/package/src/store/apis/updatePollMessage.ts +++ b/package/src/store/apis/updatePollMessage.ts @@ -1,41 +1,67 @@ -import type { PollResponse } from 'stream-chat'; +import { isVoteAnswer, PollAnswer, PollResponse, PollVote } from 'stream-chat'; +import { DefaultStreamChatGenerics } from '../../types/types'; +import { mapPollToStorable } from '../mappers/mapPollToStorable'; +import { mapStorableToPoll } from '../mappers/mapStorableToPoll'; import { createSelectQuery } from '../sqlite-utils/createSelectQuery'; import { createUpdateQuery } from '../sqlite-utils/createUpdateQuery'; import { SqliteClient } from '../SqliteClient'; import type { PreparedQueries } from '../types'; -export const updatePollMessage = async ({ +export const updatePollMessage = async < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>({ + eventType, flush = true, poll, + poll_vote, + userID, }: { - poll: PollResponse; + eventType: string; + poll: PollResponse; + userID: string; flush?: boolean; + poll_vote?: PollVote | PollAnswer; }) => { const queries: PreparedQueries[] = []; - const messagesWithPoll = await SqliteClient.executeSql.apply( + const pollsFromDB = await SqliteClient.executeSql.apply( null, - createSelectQuery('messages', ['*'], { - poll_id: poll.id, + createSelectQuery('poll', ['*'], { + id: poll.id, }), ); - for (const message of messagesWithPoll) { - const storablePoll = JSON.stringify({ + for (const pollFromDB of pollsFromDB) { + const serializedPoll = mapStorableToPoll(pollFromDB); + const { latest_answers = [], own_votes = [] } = serializedPoll; + let newOwnVotes = own_votes; + if (poll_vote && poll_vote.user?.id === userID) { + newOwnVotes = + eventType === 'poll.vote_removed' + ? newOwnVotes.filter((vote) => vote.id !== poll_vote.id) + : [poll_vote, ...newOwnVotes.filter((vote) => vote.id !== poll_vote.id)]; + } + let newLatestAnswers = latest_answers; + if (poll_vote && isVoteAnswer(poll_vote)) { + newLatestAnswers = + eventType === 'poll.vote_removed' + ? newLatestAnswers.filter((answer) => answer.id !== poll_vote?.id) + : [poll_vote, ...newLatestAnswers.filter((answer) => answer.id !== poll_vote?.id)]; + } + + const storablePoll = mapPollToStorable({ ...poll, - latest_votes: message.poll.latest_votes, - own_votes: message.poll.own_votes, + latest_answers: newLatestAnswers, + own_votes: newOwnVotes, }); - const storableMessage = { ...message, poll: storablePoll }; queries.push( - createUpdateQuery('messages', storableMessage, { - id: message.id, + createUpdateQuery('poll', storablePoll, { + id: poll.id, }), ); SqliteClient.logger?.('info', 'updatePoll', { - message: storableMessage, poll: storablePoll, }); } diff --git a/package/src/store/apis/upsertMessages.ts b/package/src/store/apis/upsertMessages.ts index 9296e6fed8..7bcc007a51 100644 --- a/package/src/store/apis/upsertMessages.ts +++ b/package/src/store/apis/upsertMessages.ts @@ -1,6 +1,7 @@ import type { MessageResponse } from 'stream-chat'; import { mapMessageToStorable } from '../mappers/mapMessageToStorable'; +import { mapPollToStorable } from '../mappers/mapPollToStorable'; import { mapReactionToStorable } from '../mappers/mapReactionToStorable'; import { mapUserToStorable } from '../mappers/mapUserToStorable'; import { createUpsertQuery } from '../sqlite-utils/createUpsertQuery'; @@ -16,6 +17,7 @@ export const upsertMessages = async ({ const storableMessages: Array> = []; const storableUsers: Array> = []; const storableReactions: Array> = []; + const storablePolls: Array> = []; messages?.forEach((message: MessageResponse) => { storableMessages.push(mapMessageToStorable(message)); @@ -28,6 +30,9 @@ export const upsertMessages = async ({ } storableReactions.push(mapReactionToStorable(r)); }); + if (message.poll) { + storablePolls.push(mapPollToStorable(message.poll)); + } }); const finalQueries = [ @@ -36,11 +41,13 @@ export const upsertMessages = async ({ ...storableReactions.map((storableReaction) => createUpsertQuery('reactions', storableReaction), ), + ...storablePolls.map((storablePoll) => createUpsertQuery('poll', storablePoll)), ]; SqliteClient.logger?.('info', 'upsertMessages', { flush, messages: storableMessages, + polls: storablePolls, reactions: storableReactions, users: storableUsers, }); diff --git a/package/src/store/mappers/mapMessageToStorable.ts b/package/src/store/mappers/mapMessageToStorable.ts index 3caac21ba4..e8f4484eef 100644 --- a/package/src/store/mappers/mapMessageToStorable.ts +++ b/package/src/store/mappers/mapMessageToStorable.ts @@ -18,6 +18,7 @@ export const mapMessageToStorable = ( message_text_updated_at, // eslint-disable-next-line @typescript-eslint/no-unused-vars own_reactions, + // eslint-disable-next-line @typescript-eslint/no-unused-vars poll, poll_id, reaction_groups, @@ -36,7 +37,6 @@ export const mapMessageToStorable = ( extraData: JSON.stringify(extraData), id, messageTextUpdatedAt: mapDateTimeToStorable(message_text_updated_at), - poll: JSON.stringify(poll), poll_id: poll_id || '', reactionGroups: JSON.stringify(reaction_groups), text, diff --git a/package/src/store/mappers/mapPollToStorable.ts b/package/src/store/mappers/mapPollToStorable.ts new file mode 100644 index 0000000000..fb9ea75bd5 --- /dev/null +++ b/package/src/store/mappers/mapPollToStorable.ts @@ -0,0 +1,53 @@ +import type { PollResponse } from 'stream-chat'; + +import { mapDateTimeToStorable } from './mapDateTimeToStorable'; + +import type { TableRow } from '../types'; + +export const mapPollToStorable = (poll: PollResponse): TableRow<'poll'> => { + const { + allow_answers, + allow_user_suggested_options, + answers_count, + created_at, + created_by, + created_by_id, + description, + enforce_unique_vote, + id, + is_closed, + latest_answers, + latest_votes_by_option, + max_votes_allowed, + name, + options, + own_votes, + updated_at, + vote_count, + vote_counts_by_option, + voting_visibility, + } = poll; + + return { + allow_answers, + allow_user_suggested_options, + answers_count, + created_at: mapDateTimeToStorable(created_at), + created_by: JSON.stringify(created_by), // decouple the users from the actual poll + created_by_id, + description, + enforce_unique_vote, + id, + is_closed, + latest_answers: JSON.stringify(latest_answers), + latest_votes_by_option: JSON.stringify(latest_votes_by_option), + max_votes_allowed, + name, + options: JSON.stringify(options), + own_votes: JSON.stringify(own_votes), + updated_at, + vote_count, + vote_counts_by_option: JSON.stringify(vote_counts_by_option), + voting_visibility, + }; +}; diff --git a/package/src/store/mappers/mapStorableToMessage.ts b/package/src/store/mappers/mapStorableToMessage.ts index b1ae3fa01a..45d34d1734 100644 --- a/package/src/store/mappers/mapStorableToMessage.ts +++ b/package/src/store/mappers/mapStorableToMessage.ts @@ -1,22 +1,25 @@ import type { MessageResponse } from 'stream-chat'; +import { mapStorableToPoll } from './mapStorableToPoll'; import { mapStorableToReaction } from './mapStorableToReaction'; import { mapStorableToUser } from './mapStorableToUser'; import type { DefaultStreamChatGenerics } from '../../types/types'; -import type { TableRowJoinedUser } from '../types'; +import type { TableRow, TableRowJoinedUser } from '../types'; export const mapStorableToMessage = < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ currentUserId, messageRow, + pollRow, reactionRows, }: { currentUserId: string; messageRow: TableRowJoinedUser<'messages'>; + pollRow: TableRow<'poll'>; reactionRows: TableRowJoinedUser<'reactions'>[]; }): MessageResponse => { const { @@ -24,7 +27,6 @@ export const mapStorableToMessage = < deletedAt, extraData, messageTextUpdatedAt, - poll, poll_id, reactionGroups, updatedAt, @@ -44,11 +46,11 @@ export const mapStorableToMessage = < latest_reactions: latestReactions, message_text_updated_at: messageTextUpdatedAt, own_reactions: ownReactions, - poll: JSON.parse(poll), poll_id, reaction_groups: reactionGroups ? JSON.parse(reactionGroups) : {}, updated_at: updatedAt, user: mapStorableToUser(user), + ...(pollRow ? { poll: mapStorableToPoll(pollRow) } : {}), ...(extraData ? JSON.parse(extraData) : {}), }; }; diff --git a/package/src/store/mappers/mapStorableToPoll.ts b/package/src/store/mappers/mapStorableToPoll.ts new file mode 100644 index 0000000000..ffeb1ad2de --- /dev/null +++ b/package/src/store/mappers/mapStorableToPoll.ts @@ -0,0 +1,56 @@ +import { PollResponse, VotingVisibility } from 'stream-chat'; + +import type { DefaultStreamChatGenerics } from '../../types/types'; +import type { TableRow } from '../types'; + +export const mapStorableToPoll = < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( + pollRow: TableRow<'poll'>, +): PollResponse => { + const { + allow_answers, + allow_user_suggested_options, + answers_count, + created_at, + created_by, + created_by_id, + description, + enforce_unique_vote, + id, + is_closed, + latest_answers, + latest_votes_by_option, + max_votes_allowed, + name, + options, + own_votes, + updated_at, + vote_count, + vote_counts_by_option, + voting_visibility, + } = pollRow; + + return { + allow_answers, + allow_user_suggested_options, + answers_count, + created_at, + created_by: JSON.parse(created_by), + created_by_id, + description, + enforce_unique_vote, + id, + is_closed, + latest_answers: JSON.parse(latest_answers), + latest_votes_by_option: JSON.parse(latest_votes_by_option), + max_votes_allowed, + name, + options: JSON.parse(options), + own_votes: own_votes ? JSON.parse(own_votes) : [], + updated_at, + vote_count, + vote_counts_by_option: JSON.parse(vote_counts_by_option), + voting_visibility: voting_visibility as VotingVisibility | undefined, + }; +}; diff --git a/package/src/store/schema.ts b/package/src/store/schema.ts index 995b423510..be894f1667 100644 --- a/package/src/store/schema.ts +++ b/package/src/store/schema.ts @@ -103,7 +103,6 @@ export const tables: Tables = { extraData: 'TEXT', id: 'TEXT', messageTextUpdatedAt: 'TEXT', - poll: 'TEXT', poll_id: 'TEXT', reactionGroups: 'TEXT', text: "TEXT DEFAULT ''", @@ -139,6 +138,31 @@ export const tables: Tables = { type: 'TEXT', }, }, + poll: { + columns: { + allow_answers: 'BOOLEAN DEFAULT FALSE', + allow_user_suggested_options: 'BOOLEAN DEFAULT FALSE', + answers_count: 'INTEGER DEFAULT 0', + created_at: 'TEXT', + created_by: 'TEXT', + created_by_id: 'TEXT', + description: 'TEXT', + enforce_unique_vote: 'BOOLEAN DEFAULT FALSE', + id: 'TEXT NOT NULL', + is_closed: 'BOOLEAN DEFAULT FALSE', + latest_answers: 'TEXT', + latest_votes_by_option: 'TEXT', + max_votes_allowed: 'INTEGER DEFAULT 1', + name: 'TEXT', + options: 'TEXT', + own_votes: 'TEXT', + updated_at: 'TEXT', + vote_count: 'INTEGER DEFAULT 0', + vote_counts_by_option: 'TEXT', + voting_visibility: 'TEXT', + }, + primaryKey: ['id'], + }, reactions: { columns: { createdAt: 'TEXT', @@ -266,7 +290,6 @@ export type Schema = { extraData: string; id: string; messageTextUpdatedAt: string; - poll: string; poll_id: string; reactionGroups: string; type: MessageLabel; @@ -283,6 +306,28 @@ export type Schema = { payload: string; type: ValueOf; }; + poll: { + answers_count: number; + created_at: string; + created_by: string; + created_by_id: string; + enforce_unique_vote: boolean; + id: string; + latest_answers: string; + latest_votes_by_option: string; + max_votes_allowed: number; + name: string; + options: string; + updated_at: string; + vote_count: number; + vote_counts_by_option: string; + allow_answers?: boolean; + allow_user_suggested_options?: boolean; + description?: string; + is_closed?: boolean; + own_votes?: string; + voting_visibility?: string; + }; reactions: { createdAt: string; messageId: string; diff --git a/package/src/version.json b/package/src/version.json index 85fac2b514..8be309d7fb 100644 --- a/package/src/version.json +++ b/package/src/version.json @@ -1,3 +1,3 @@ { - "version": "5.41.2" + "version": "5.41.4" }