diff --git a/README.md b/README.md index e94bf2d0d8..67323d9a49 100644 --- a/README.md +++ b/README.md @@ -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-467%20KB-blue) +![JS Bundle Size](https://img.shields.io/badge/js_bundle_size-469%20KB-blue) diff --git a/examples/ExpoMessaging/app.json b/examples/ExpoMessaging/app.json index 4e9029e1df..e8dafe9cd7 100644 --- a/examples/ExpoMessaging/app.json +++ b/examples/ExpoMessaging/app.json @@ -48,6 +48,13 @@ { "microphonePermission": "$(PRODUCT_NAME) would like to use your microphone for voice recording." } + ], + [ + "expo-video", + { + "supportsBackgroundPlayback": true, + "supportsPictureInPicture": true + } ] ] } diff --git a/examples/ExpoMessaging/app/index.tsx b/examples/ExpoMessaging/app/index.tsx index 66f64105f7..9487a9d1f8 100644 --- a/examples/ExpoMessaging/app/index.tsx +++ b/examples/ExpoMessaging/app/index.tsx @@ -3,7 +3,6 @@ import { ChannelList } from 'stream-chat-expo'; import { useContext, useMemo } from 'react'; import { Stack, useRouter } from 'expo-router'; import { ChannelSort } from 'stream-chat'; -import { StreamChatGenerics } from '../types'; import { AppContext } from '../context/AppContext'; import { user } from '../constants'; @@ -11,7 +10,7 @@ const filters = { members: { $in: [user.id] }, type: 'messaging', }; -const sort: ChannelSort = { last_updated: -1 }; +const sort: ChannelSort = { last_updated: -1 }; const options = { state: true, watch: true, diff --git a/examples/ExpoMessaging/components/ChatWrapper.tsx b/examples/ExpoMessaging/components/ChatWrapper.tsx index e803bbc48f..736acafbfa 100644 --- a/examples/ExpoMessaging/components/ChatWrapper.tsx +++ b/examples/ExpoMessaging/components/ChatWrapper.tsx @@ -1,4 +1,4 @@ -import React, { PropsWithChildren } from 'react'; +import React, { PropsWithChildren, useRef } from 'react'; import { Chat, OverlayProvider, @@ -7,7 +7,6 @@ import { useCreateChatClient, } from 'stream-chat-expo'; import { AuthProgressLoader } from './AuthProgressLoader'; -import { StreamChatGenerics } from '../types'; import { STREAM_API_KEY, user, userToken } from '../constants'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useStreamChatTheme } from '../useStreamChatTheme'; @@ -34,7 +33,7 @@ export const ChatWrapper = ({ children }: PropsWithChildren<{}>) => { } return ( - + | undefined; - setChannel: React.Dispatch | undefined>>; + channel: ChannelType | undefined; + setChannel: React.Dispatch>; setThread: React.Dispatch< - React.SetStateAction['thread'] | undefined> + React.SetStateAction >; - thread: ThreadContextValue['thread'] | undefined; + thread: ThreadContextValue['thread'] | undefined; }; export const AppContext = createContext({ @@ -20,9 +19,9 @@ export const AppContext = createContext({ }); export const AppProvider = ({ children }: PropsWithChildren) => { - const [channel, setChannel] = useState | undefined>(undefined); + const [channel, setChannel] = useState(undefined); const [thread, setThread] = useState< - ThreadContextValue['thread'] | undefined + ThreadContextValue['thread'] | undefined >(undefined); return ( diff --git a/examples/ExpoMessaging/custom-types.d.ts b/examples/ExpoMessaging/custom-types.d.ts new file mode 100644 index 0000000000..dcd62b8eb0 --- /dev/null +++ b/examples/ExpoMessaging/custom-types.d.ts @@ -0,0 +1,41 @@ +import { + DefaultAttachmentData, + DefaultChannelData, + DefaultCommandData, + DefaultEventData, + DefaultMemberData, + DefaultMessageData, + DefaultPollData, + DefaultPollOptionData, + DefaultReactionData, + DefaultThreadData, + DefaultUserData, +} from 'stream-chat-expo'; + +declare module 'stream-chat' { + /* eslint-disable @typescript-eslint/no-empty-object-type */ + + interface CustomAttachmentData extends DefaultAttachmentData {} + + interface CustomChannelData extends DefaultChannelData {} + + interface CustomCommandData extends DefaultCommandData {} + + interface CustomEventData extends DefaultEventData {} + + interface CustomMemberData extends DefaultMemberData {} + + interface CustomUserData extends DefaultUserData {} + + interface CustomMessageData extends DefaultMessageData {} + + interface CustomPollOptionData extends DefaultPollOptionData {} + + interface CustomPollData extends DefaultPollData {} + + interface CustomReactionData extends DefaultReactionData {} + + interface CustomThreadData extends DefaultThreadData {} + + /* eslint-enable @typescript-eslint/no-empty-object-type */ +} diff --git a/examples/ExpoMessaging/package.json b/examples/ExpoMessaging/package.json index d60f1de309..ed87be9264 100644 --- a/examples/ExpoMessaging/package.json +++ b/examples/ExpoMessaging/package.json @@ -23,11 +23,11 @@ "expo-image-manipulator": "~13.0.6", "expo-image-picker": "~16.0.6", "expo-linking": "~7.0.5", - "expo-media-library": "~17.0.6", "expo-router": "~4.0.17", "expo-sharing": "~13.0.1", "expo-splash-screen": "~0.29.22", "expo-status-bar": "~2.0.1", + "expo-video": "^2.0.5", "react": "18.3.1", "react-dom": "18.3.1", "react-native": "0.77.1", @@ -46,5 +46,8 @@ "@rnx-kit/metro-config": "^2.0.1", "@rnx-kit/metro-resolver-symlinks": "^0.2.1" }, + "resolutions": { + "@types/react": "^19.0.0" + }, "private": true } diff --git a/examples/ExpoMessaging/types.ts b/examples/ExpoMessaging/types.ts deleted file mode 100644 index 6607eb8e63..0000000000 --- a/examples/ExpoMessaging/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -export type LocalAttachmentType = Record; -export type LocalChannelType = Record; -export type LocalCommandType = string; -export type LocalEventType = Record; -export type LocalMessageType = Record; -export type LocalReactionType = Record; -export type LocalUserType = Record; -export type LocalPollOptionType = Record; -export type LocalPollType = Record; -export type LocalMemberType = Record; - -export type StreamChatGenerics = { - attachmentType: LocalAttachmentType; - channelType: LocalChannelType; - commandType: LocalCommandType; - eventType: LocalEventType; - memberType: LocalMemberType; - messageType: LocalMessageType; - pollOptionType: LocalPollOptionType; - pollType: LocalPollType; - reactionType: LocalReactionType; - userType: LocalUserType; -}; diff --git a/examples/ExpoMessaging/yarn.lock b/examples/ExpoMessaging/yarn.lock index 18c12bc492..48d77730ca 100644 --- a/examples/ExpoMessaging/yarn.lock +++ b/examples/ExpoMessaging/yarn.lock @@ -1507,17 +1507,24 @@ dependencies: regenerator-runtime "^0.13.11" -"@babel/runtime@^7.16.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0": +"@babel/runtime@^7.17.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" + integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb" integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ== dependencies: regenerator-runtime "^0.13.11" -"@babel/runtime@^7.17.2": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" - integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== +"@babel/runtime@^7.27.0": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762" + integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw== dependencies: regenerator-runtime "^0.14.0" @@ -2881,13 +2888,19 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/jsonwebtoken@~9.0.0": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#9eeb56c76dd555039be2a3972218de5bd3b8d83e" - integrity sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q== +"@types/jsonwebtoken@^9.0.8", "@types/jsonwebtoken@~9.0.0": + version "9.0.9" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz#a4c3a446c0ebaaf467a58398382616f416345fb3" + integrity sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ== dependencies: + "@types/ms" "*" "@types/node" "*" +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" @@ -2900,25 +2913,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.5.tgz#26d295f3570323b2837d322180dfbf1ba156fefb" integrity sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ== -"@types/prop-types@*": - version "15.7.5" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== - -"@types/react@>=16.0.0": - version "18.2.7" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.7.tgz#dfb4518042a3117a045b8c222316f83414a783b3" - integrity sha512-ojrXpSH2XFCmHm7Jy3q44nXDyN54+EYKP2lBhJ2bqfyPj6cIUW/FZW/Csdia34NQgq7KYcAlHi5184m4X88+yw== +"@types/react@>=16.0.0", "@types/react@^19.0.0": + version "19.0.10" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.10.tgz#d0c66dafd862474190fe95ce11a68de69ed2b0eb" + integrity sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g== dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" csstype "^3.0.2" -"@types/scheduler@*": - version "0.16.3" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" - integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== - "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -2931,6 +2932,13 @@ dependencies: "@types/node" "*" +"@types/ws@^8.5.14": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.0.tgz#8a2ec491d6f0685ceaab9a9b7ff44146236993b5" + integrity sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -4307,11 +4315,6 @@ expo-linking@~7.0.5: expo-constants "~17.0.5" invariant "^2.2.4" -expo-media-library@~17.0.6: - version "17.0.6" - resolved "https://registry.yarnpkg.com/expo-media-library/-/expo-media-library-17.0.6.tgz#355f5f5abf0b5b35cdf009f18567cbba12d8dc82" - integrity sha512-LUnfrddmee1xLOkyG2NN1l9xQbtvMX3fbM1brEGHg0SKSndvjod3FQdhTzZEYAariqW2RSxQR8v1IsheIoLQXg== - expo-modules-autolinking@2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-2.0.8.tgz#b00c10ebb589ce2220548bbaee4865db1cf1f1f7" @@ -4369,6 +4372,11 @@ expo-status-bar@~2.0.1: resolved "https://registry.yarnpkg.com/expo-status-bar/-/expo-status-bar-2.0.1.tgz#fc07726346dc30fbb68aadb0d7890b34fba42eee" integrity sha512-AkIPX7jWHRPp83UBZ1iXtVvyr0g+DgBVvIXTtlmPtmUsm8Vq9Bb5IGj86PW8osuFlgoTVAg7HI/+Ok7yEYwiRg== +expo-video@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/expo-video/-/expo-video-2.0.5.tgz#5c5311b0a20dcb7897d06cd4f9d7fb0ce13711d9" + integrity sha512-K5Q4bFKtYq0wEC38mckWUYeaTXsmhl6duidhSdbA63VBy6cwxDOk8uPsFPTQD3FXKJg6wFB0z8ZUASSPuUaY5A== + expo@~52.0.36: version "52.0.37" resolved "https://registry.yarnpkg.com/expo/-/expo-52.0.37.tgz#159b5f3f9bedb2c56b36a815b8679a2602a406cc" @@ -5153,6 +5161,11 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +isomorphic-ws@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== + istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -5407,15 +5420,21 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonwebtoken@~9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" - integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw== +jsonwebtoken@^9.0.2, jsonwebtoken@~9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== dependencies: jws "^3.2.2" - lodash "^4.17.21" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" ms "^2.1.1" - semver "^7.3.8" + semver "^7.5.4" jwa@^1.4.1: version "1.4.1" @@ -5535,6 +5554,11 @@ linkifyjs@^4.1.1: resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.1.tgz#73d427e3bbaaf4ca8e71c589ad4ffda11a9a5fde" integrity sha512-zFN/CTVmbcVef+WaDXT63dNzzkfRBKT1j464NJQkV7iSgJU0sLBus9W0HBwnXK13/hf168pbrx/V/bjEHOXNHA== +linkifyjs@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08" + integrity sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw== + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -5567,6 +5591,41 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" @@ -5903,6 +5962,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.7.tgz#0b7a98b08c63bd3c10251e797d67840c9bde9f13" + integrity sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -7053,7 +7117,7 @@ semver@^7.1.3, semver@~7.6.3: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== -semver@^7.3.5, semver@^7.3.8: +semver@^7.3.5: version "7.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== @@ -7358,10 +7422,10 @@ stream-buffers@2.2.x, stream-buffers@~2.2.0: version "0.0.0" uid "" -stream-chat-react-native-core@6.6.8: - version "6.6.8" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-6.6.8.tgz#483ade63ba051426480ab2dfd8ab3b248b90ae88" - integrity sha512-F8S70DHaiit6BEdKOkSMHq2bjMONhrouvJ+szBQuE430EJDgUlc2VErHk3yJCzqIt5lwfVZktjHuqSIOGVg5LQ== +stream-chat-react-native-core@6.7.4: + version "6.7.4" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-6.7.4.tgz#9709465e161e32cf358034a82c284cc16369397a" + integrity sha512-YCAXBfdbazFIpO1EI9Czd2QYaFCJX+i9q710L3xPB4mRuaNl2SL8kH+RTj4Ur9Fyl3u1fcDI+1o/iYnaap1Uwg== dependencies: "@gorhom/bottom-sheet" "^5.1.1" dayjs "1.10.5" @@ -7382,11 +7446,11 @@ stream-chat-react-native-core@6.6.8: uid "" stream-chat@^8.57.6: - version "8.57.6" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.57.6.tgz#dd3a4a0a024ee44bfa6ae7ca15648e6c9f2e498f" - integrity sha512-+zkC5DtxvdkEBAv7dCzzO4WWosjaM15EjsH3q4xD1nX/yf2qT6BMhPJguM+CUWxwlFh32X0KR+ARtD0ChPqtnA== + version "8.60.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.60.0.tgz#b67d4fbb185da53fb8ac5fc5759986d6ad7e19a3" + integrity sha512-7FpO7Wno++r+n+x9aFuXtGYtNO06CIMd2Bxe3doYZLhMfS0nuaXloeFlGcMT0r4U/6bnguz1qQdDJUPNQAS8bQ== dependencies: - "@babel/runtime" "^7.16.3" + "@babel/runtime" "^7.27.0" "@types/jsonwebtoken" "~9.0.0" "@types/ws" "^7.4.0" axios "^1.6.0" @@ -7396,6 +7460,21 @@ stream-chat@^8.57.6: jsonwebtoken "~9.0.0" ws "^7.5.10" +stream-chat@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.0.0.tgz#cb22dcb8b7f070c623a13b6b75b212d560534d6c" + integrity sha512-I4+/DEp7dP3WBgRmqHaLswL+Y2fyQkUWJhYBS5zx4bpu1cYM6WEir9HYjToDNuJjltqa/FFIEF/tMPWr7iTc0A== + dependencies: + "@types/jsonwebtoken" "^9.0.8" + "@types/ws" "^8.5.14" + axios "^1.6.0" + base64-js "^1.5.1" + form-data "^4.0.0" + isomorphic-ws "^5.0.0" + jsonwebtoken "^9.0.2" + linkifyjs "^4.2.0" + ws "^8.18.1" + stream-slice@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/stream-slice/-/stream-slice-0.1.2.tgz#2dc4f4e1b936fb13f3eb39a2def1932798d07a4b" @@ -8085,6 +8164,11 @@ ws@^8.12.1: resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== +ws@^8.18.1: + version "8.18.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb" + integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w== + xcode@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/xcode/-/xcode-3.0.1.tgz#3efb62aac641ab2c702458f9a0302696146aa53c" diff --git a/examples/SampleApp/App.tsx b/examples/SampleApp/App.tsx index 509d92ab0c..1128c7bbf3 100644 --- a/examples/SampleApp/App.tsx +++ b/examples/SampleApp/App.tsx @@ -6,7 +6,6 @@ import { createStackNavigator } from '@react-navigation/stack'; import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'; import { Chat, - MessageType, OverlayProvider, SqliteClient, ThemeProvider, @@ -36,7 +35,7 @@ import { SharedGroupsScreen } from './src/screens/SharedGroupsScreen'; import { ThreadScreen } from './src/screens/ThreadScreen'; import { UserSelectorScreen } from './src/screens/UserSelectorScreen'; -import type { StreamChat } from 'stream-chat'; +import type { LocalMessage, StreamChat } from 'stream-chat'; if (__DEV__) { DevSettings.addMenuItem('Reset local DB (offline storage)', () => { @@ -45,11 +44,7 @@ if (__DEV__) { }); } -import type { - StackNavigatorParamList, - StreamChatGenerics, - UserSelectorParamList, -} from './src/types'; +import type { StackNavigatorParamList, UserSelectorParamList } from './src/types'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { navigateToChannel, RootNavigationRef } from './src/utils/RootNavigation'; import FastImage from 'react-native-fast-image'; @@ -107,18 +102,16 @@ const App = () => { } } }); - messaging - .getInitialNotification() - .then((remoteMessage) => { - if (remoteMessage) { - // Notification caused app to open from quit state on iOS - const channelId = remoteMessage.data?.channel_id as string; - if (channelId) { - // this will make the app to start with the channel screen with this channel id - initialChannelIdGlobalRef.current = channelId; - } + messaging.getInitialNotification().then((remoteMessage) => { + if (remoteMessage) { + // Notification caused app to open from quit state on iOS + const channelId = remoteMessage.data?.channel_id as string; + if (channelId) { + // this will make the app to start with the channel screen with this channel id + initialChannelIdGlobalRef.current = channelId; } - }); + } + }); return () => { unsubscribeOnNotificationOpen(); unsubscribeForegroundEvent(); @@ -171,21 +164,23 @@ const DrawerNavigator: React.FC = () => ( ); +const isMessageAIGenerated = (message: LocalMessage) => !!message.ai_generated; + const DrawerNavigatorWrapper: React.FC<{ - chatClient: StreamChat; + chatClient: StreamChat; }> = ({ chatClient }) => { const { bottom } = useSafeAreaInsets(); const streamChatTheme = useStreamChatTheme(); return ( - bottomInset={bottom} value={{ style: streamChatTheme }}> - + + !!message.ai_generated} + isMessageAIGenerated={isMessageAIGenerated} > diff --git a/examples/SampleApp/ios/Podfile.lock b/examples/SampleApp/ios/Podfile.lock index b4ed7bd87e..269ca91dd2 100644 --- a/examples/SampleApp/ios/Podfile.lock +++ b/examples/SampleApp/ios/Podfile.lock @@ -1475,6 +1475,27 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-image-picker (8.2.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-netinfo (11.4.1): - React-Core - react-native-safe-area-context (5.2.0): @@ -2213,7 +2234,7 @@ PODS: - libwebp (~> 1.0) - SDWebImage/Core (~> 5.10) - SocketRocket (0.7.1) - - stream-chat-react-native (6.6.8): + - stream-chat-react-native (6.7.3): - DoubleConversion - glog - hermes-engine @@ -2284,6 +2305,7 @@ DEPENDENCIES: - react-native-blob-util (from `../node_modules/react-native-blob-util`) - "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)" - "react-native-document-picker (from `../node_modules/@react-native-documents/picker`)" + - react-native-image-picker (from `../node_modules/react-native-image-picker`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-video (from `../node_modules/react-native-video`) @@ -2439,6 +2461,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-camera-roll/camera-roll" react-native-document-picker: :path: "../node_modules/@react-native-documents/picker" + react-native-image-picker: + :path: "../node_modules/react-native-image-picker" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" react-native-safe-area-context: @@ -2554,90 +2578,91 @@ SPEC CHECKSUMS: hermes-engine: b417d2b2aee3b89b58e63e23a51e02be91dc876d libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - op-sqlite: 2e34a191af7e843608357671c94a6e2befd4b986 + op-sqlite: c33561ea312a2ae38aae032fd3a42635dc6b57e8 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 - RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 + RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 RCTDeprecation: b2eecf2d60216df56bc5e6be5f063826d3c1ee35 RCTRequired: 78522de7dc73b81f3ed7890d145fa341f5bb32ea RCTTypeSafety: c135dd2bf50402d87fd12884cbad5d5e64850edd React: b229c49ed5898dab46d60f61ed5a0bfa2ee2fadb React-callinvoker: 2ac508e92c8bd9cf834cc7d7787d94352e4af58f React-Codegen: 4b8b4817cea7a54b83851d4c1f91f79aa73de30a - React-Core: 325b4f6d9162ae8b9a6ff42fe78e260eb124180d - React-CoreModules: 558041e5258f70cd1092f82778d07b8b2ff01897 - React-cxxreact: 8fff17cbe76e6a8f9991b59552e1235429f9c74b + React-Core: 13cdd1558d0b3f6d9d5a22e14d89150280e79f02 + React-CoreModules: b07a6744f48305405e67c845ebf481b6551b712a + React-cxxreact: 1055a86c66ac35b4e80bd5fb766aed5f494dfff4 React-debug: c76e92776a86622209279fe6d24a0147584444ed - React-defaultsnativemodule: 111fb1efc95c2bd0ee18e38e9f7b57d678e6f932 - React-domnativemodule: d5154a815306fd6050ee9346a1490d2fb17eb0e5 - React-Fabric: 51ac32f0a6790b1d3b14d90c6870e5ce5bb3854a - React-FabricComponents: 1094d6a3c2566b3c56951331c44d7d3960570ac8 - React-FabricImage: 6b210ad3c72704a9ad60dde66c397ce6257333f4 + React-defaultsnativemodule: c2e3ac39909241374c3322eb2be33f4c15fe6be4 + React-domnativemodule: 240b3c95b5300cc6537594e73ebc6e8e77585b74 + React-Fabric: 3b403ca25f74d54454b31d1d2627050e0777d42c + React-FabricComponents: 154740cfcd57943709a9d0343769d17173c0ac9c + React-FabricImage: 0863e39cea98f3ca2f8c3d92984660795cec84ae React-featureflags: efb93a998907e4ad5b88f6ed77cc140914d5c36d - React-featureflagsnativemodule: a74b09429c2e7a57412d78cc159ab86ae4f15db9 - React-graphics: 17ef0ee3ef4a4c1774cc82f1f477ecef4d67c73f - React-hermes: a9a0c8377627b5506ef9a7b6f60a805c306e3f51 - React-idlecallbacksnativemodule: 0711ec5eb53c7f790641fa00e5f6ec0355d3159b - React-ImageManager: 23b4701408390428724f0e0ebb2cbed7b37c2b24 - React-jserrorhandler: e21b438ef8b99ea8bf070ff35f00bc0215b5f769 - React-jsi: f3f51595cc4c089037b536368f016d4742bf9cf7 - React-jsiexecutor: cca6c232db461e2fd213a11e9364cfa6fdaa20eb - React-jsinspector: 8a3c2637b84ebec478f46a43432a522d7489410f - React-jsinspectortracing: ee0215d2db753cc10f45fc9aa86557718d0b16fb - React-jsitracing: 258be1fd259141f6aa43012c20c70ebc02e32087 - React-logger: 018826bfd51b9f18e87f67db1590bc510ad20664 - React-Mapbuffer: 9fbb496e7d6f7c34d5e617365ee778bf96d14eae - React-microtasksnativemodule: 36adde22631838680d1be62776e8ccb83186c06a - react-native-blob-util: d03eaad9fd1bbe90bd0eedb5bad3333215976086 - react-native-cameraroll: 10054f480dfd6e0bd02fdf08fb6d82f80b362575 - react-native-document-picker: 78c262a7f9f77df2380378aa4b3413b8646ce91b - react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187 - react-native-safe-area-context: 02e0f487c16ccf1acc8a666bed2318ceeb5dc14c - react-native-video: 16b5c395d05f8af23e16bfe3dc0794a5514c882f - React-NativeModulesApple: ec44c21ae0bbb5f9a2df72db00294e33a00e07f0 - React-perflogger: 9e8d3c0dc0194eb932162812a168aa5dc662f418 - React-performancetimeline: 350424518f433dd43f063dc5f2cf3195c1a5b60f + React-featureflagsnativemodule: 51116d72aafea30860f315702d17eb76bbb725a3 + React-graphics: 91d9920451f633d64d31948da3ba0377b6eda8de + React-hermes: 71186f872c932e4574d5feb3ed754dda63a0b3bd + React-idlecallbacksnativemodule: 19bf1fa4b2b66fe1898ac1d185129cdcc3221c7c + React-ImageManager: 7dc7bfca8e9ecb9a7436b8a89a143a193ef5adcf + React-jserrorhandler: d8640792495ac2d78e73acbcc77a8439d1eedfef + React-jsi: 0775a66820496769ad83e629f0f5cce621a57fc7 + React-jsiexecutor: 2cf5ba481386803f3c88b85c63fa102cba5d769e + React-jsinspector: d1d9f215c7431b286acc12e83cdf0d90c265f0ed + React-jsinspectortracing: c4c1cceb9a9c266ce849c82332e35cc57ee9dae9 + React-jsitracing: 267618eec9c362658a4587c5ddcfb41b2e00c403 + React-logger: 795cd5055782db394f187f9db0477d4b25b44291 + React-Mapbuffer: 0df2a235bd0182f5cbed6c5f095e66deca12e335 + React-microtasksnativemodule: b31e56a980634f383221bfefd5111d04c14c110b + react-native-blob-util: 875bbeee07e4ada135e4edf9fc7b22acf8d9721d + react-native-cameraroll: cdc91c4c953d1a18aa3ce88b5a25698025c8c4d2 + react-native-document-picker: 19be73c0423e4bc886cef74ec282eff750698013 + react-native-image-picker: 1c620a65f900a47d6d12ec94874c6a1820ebea7d + react-native-netinfo: f0a9899081c185db1de5bb2fdc1c88c202a059ac + react-native-safe-area-context: 0b43456abcaaa3c8323bbfafe9c5f0f9511219d2 + react-native-video: a225b4d4d3286f3253dc7b00a62e7c8e59d04d51 + React-NativeModulesApple: b74b4e3004104429461593fe460ad790cc4928c2 + React-perflogger: ab51b7592532a0ea45bf6eed7e6cae14a368b678 + React-performancetimeline: 37192fd1019c3b3b597a877dff12f3af68305c34 React-RCTActionSheet: 592674cf61142497e0e820688f5a696e41bf16dd - React-RCTAnimation: e6d669872f9b3b4ab9527aab283b7c49283236b7 - React-RCTAppDelegate: de2343fe08be4c945d57e0ecce44afcc7dd8fc03 - React-RCTBlob: 3e2dce94c56218becc4b32b627fc2293149f798d - React-RCTFabric: adad07a08efb186bc1046041207527927524170d - React-RCTFBReactNativeSpec: d10ca5e0ccbfeac8c047361fedf8e4ac653887b6 - React-RCTImage: dc04b176c022d12a8f55ae7a7279b1e091066ae0 - React-RCTLinking: 88f5e37fe4f26fbc80791aa2a5f01baf9b9a3fd5 - React-RCTNetwork: f213693565efbd698b8e9c18d700a514b49c0c8e - React-RCTSettings: a2d32a90c45a3575568cad850abc45924999b8a5 - React-RCTText: 54cdcd1cbf6f6a91dc6317f5d2c2b7fc3f6bf7a0 - React-RCTVibration: 11dae0e7f577b5807bb7d31e2e881eb46f854fd4 + React-RCTAnimation: 8fbb8dba757b49c78f4db403133ab6399a4ce952 + React-RCTAppDelegate: 7f88baa8cb4e5d6c38bb4d84339925c70c9ac864 + React-RCTBlob: f89b162d0fe6b570a18e755eb16cbe356d3c6d17 + React-RCTFabric: f2151588dc1dc884b34b8660d72ef5237aa4b10e + React-RCTFBReactNativeSpec: 8c29630c2f379c729300e4c1e540f3d1b78d1936 + React-RCTImage: ccac9969940f170503857733f9a5f63578e106e1 + React-RCTLinking: d82427bbf18415a3732105383dff119131cadd90 + React-RCTNetwork: 12ad4d0fbde939e00251ca5ca890da2e6825cc3c + React-RCTSettings: e7865bf9f455abf427da349c855f8644b5c39afa + React-RCTText: 2cdfd88745059ec3202a0842ea75a956c7d6f27d + React-RCTVibration: a3a1458e6230dfd64b3768ebc0a4aac430d9d508 React-rendererconsistency: aa476d937c91886dd8b2ddde3191c775585ae47a - React-rendererdebug: df10d858ac7709b9c8349d952474b0746092c690 + React-rendererdebug: 5a2219e0ceb78f4ffe9ee2d80fa260bb5bac50b2 React-rncore: 517c6c3647d45de81a7920b6959adf14fed2a5a5 - React-RuntimeApple: 6922a0861c3fc4c7d544fc7d1d5cb38c779d1264 - React-RuntimeCore: 41a95876d16630ce00946eaaee7ffd5222242b44 + React-RuntimeApple: 40809bf5975c265b990dec2725f2cfb61f1afc75 + React-RuntimeCore: 375c2645e924fdca875918f07ed987653c517edc React-runtimeexecutor: a188df372373baf5066e6e229177836488799f80 - React-RuntimeHermes: f2ca409c03c36bb3dcbf61bdfa2636501f9faebd - React-runtimescheduler: 7ae10fa81428c2479e0a5534943dacb8e34c9d52 + React-RuntimeHermes: 2de8d61ec25d950ae4aebcab1a895e0bb8b18c95 + React-runtimescheduler: e8b49a60eca68a3513c259879a352ed010fed255 React-timing: e56b95cb12c6fb9146be7ba3d671cf6b5d17b2e0 - React-utils: 6eabecc0e7d7bcf21b6b33357bc1fe8ae13c7c4c - ReactAppDependencyProvider: a1fb08dfdc7ebc387b2e54cfc9decd283ed821d8 - ReactCodegen: 0f8899ac1bad260bf3b362ee848ef67a70b5a306 - ReactCommon: a30b578194de911fbe1698efb8247bfe4cb6abff - RNAudioRecorderPlayer: 11df0c7b614e9767ef24d896465c3a758c592de7 - RNCAsyncStorage: 849b77e6ab3eb838361a902b492993056924faab - RNFastImage: 462a183c4b0b6b26fdfd639e1ed6ba37536c3b87 - RNFBApp: b5626640e0f4b4fe5be4f44375df703c0d62ee4b - RNFBMessaging: 8e38f5ca846497f8a9c91d33f311c00ca52d119c - RNGestureHandler: f7b3a72c099e1e29b5b81688678bc9108d44057c - RNNotifee: 5e3b271e8ea7456a36eec994085543c9adca9168 - RNReactNativeHapticFeedback: eb5395b503c7a8f10de5e6722ef8afd3c61bc4f5 - RNReanimated: fe5c52894886953248a81a10b2a9b6eeb5398d61 - RNScreens: fc78b9b5a1274426d7a59b7d07c272bba13604fa - RNShare: dcef43a8864fcc114fd582edba7832a906fd318d - RNSVG: 71e35e78add645b84b52b0c6f203f91028e1ab5e + React-utils: 8ad62100a8780798a380b769e968c4764bad1f4b + ReactAppDependencyProvider: f2e81d80afd71a8058589e19d8a134243fa53f17 + ReactCodegen: 299e99fc57c93edc7c5396ef1a39a3a4d494f25d + ReactCommon: c8fdbc582b98a07daf201cd95c1da75dd029f3ee + RNAudioRecorderPlayer: 224c7de87722938aedce04000d09baa633148f5b + RNCAsyncStorage: dac011cac81189c2b3b8654f3db97d2b6362d165 + RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8 + RNFBApp: 60366dd9d6bb01327607e1561a32508592d76db9 + RNFBMessaging: 9465c2e3adb5e02cae8d40048306a30aea7f55cf + RNGestureHandler: 0a16f3f13829c01268ae55610a40b57b713c8161 + RNNotifee: 4a6ee5c7deaf00e005050052d73ee6315dff7ec9 + RNReactNativeHapticFeedback: a49e613d48d721c99cad9689a490554104c22154 + RNReanimated: c9f295fb1679867288d238bfaf3ea39225c95e1b + RNScreens: 77f93ec55b749c49549b447527ebf78e990125f3 + RNShare: 12d13ebc179faf22534c605d17b2c2fa40191850 + RNSVG: 05776cf3f0d52d3f8e7ebee34b2189da7b8638ff SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - stream-chat-react-native: fe01622d1cbabaee9a0f65b5e469041b1e7868a4 + stream-chat-react-native: e6555aac04d25ad09d9f219360dda7a00eb0d9d1 Yoga: be02ca501b03c79d7027a6bbbd0a8db985034f11 PODFILE CHECKSUM: 4f662370295f8f9cee909f1a4c59a614999a209d diff --git a/examples/SampleApp/package.json b/examples/SampleApp/package.json index 26ea973741..c202afbc50 100644 --- a/examples/SampleApp/package.json +++ b/examples/SampleApp/package.json @@ -42,6 +42,7 @@ "react-native-fast-image": "^8.6.3", "react-native-gesture-handler": "^2.24.0", "react-native-haptic-feedback": "^2.3.3", + "react-native-image-picker": "^8.2.0", "react-native-reanimated": "^3.17.1", "react-native-safe-area-context": "^5.2.0", "react-native-screens": "^4.9.1", diff --git a/examples/SampleApp/src/ChatUsers.ts b/examples/SampleApp/src/ChatUsers.ts index 6b1b37be5c..dec7d186c1 100644 --- a/examples/SampleApp/src/ChatUsers.ts +++ b/examples/SampleApp/src/ChatUsers.ts @@ -1,5 +1,4 @@ import { UserResponse } from 'stream-chat'; -import { StreamChatGenerics } from './types'; export const USER_TOKENS: Record = { e2etest1: @@ -27,7 +26,7 @@ export const USER_TOKENS: Record = { rodolphe: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9kb2xwaGUifQ.tLl-I8ADBhTKB-x5FB9jK4-am0dELLXgydM6VN9rTL8', }; -export const USERS: Record> = { +export const USERS: Record = { neil: { id: 'neil', image: 'https://ca.slack-edge.com/T02RM6X6B-U01173D1D5J-0dead6eea6ea-512', diff --git a/examples/SampleApp/src/components/AddMemberBottomSheet.tsx b/examples/SampleApp/src/components/AddMemberBottomSheet.tsx index f3e4d00800..8239fa2155 100644 --- a/examples/SampleApp/src/components/AddMemberBottomSheet.tsx +++ b/examples/SampleApp/src/components/AddMemberBottomSheet.tsx @@ -21,8 +21,6 @@ import { usePaginatedUsers } from '../hooks/usePaginatedUsers'; import type { UserResponse } from 'stream-chat'; -import { StreamChatGenerics } from '../types'; - const styles = StyleSheet.create({ container: { height: 300, @@ -94,7 +92,7 @@ export const AddMemberBottomSheet: React.FC = () => { return null; } - const addMember = async (user: UserResponse) => { + const addMember = async (user: UserResponse) => { setAddMemberQueryInProgress(true); try { diff --git a/examples/SampleApp/src/components/ChannelInfoOverlay.tsx b/examples/SampleApp/src/components/ChannelInfoOverlay.tsx index 7c58473d77..ea95c21fbb 100644 --- a/examples/SampleApp/src/components/ChannelInfoOverlay.tsx +++ b/examples/SampleApp/src/components/ChannelInfoOverlay.tsx @@ -29,6 +29,7 @@ import { useTheme, useViewport, } from 'stream-chat-react-native'; +import { ChannelMemberResponse } from 'stream-chat'; import { useAppOverlayContext } from '../context/AppOverlayContext'; import { useBottomSheetOverlayContext } from '../context/BottomSheetOverlayContext'; @@ -224,9 +225,9 @@ export const ChannelInfoOverlay = (props: ChannelInfoOverlayProps) => { : 0; const channelName = channel ? channel.data?.name || - Object.values(channel.state.members) + Object.values(channel.state.members) .slice(0) - .reduce((returnString, currentMember, index, originalArray) => { + .reduce((returnString, currentMember, index, originalArray) => { const returnStringLength = returnString.length; const currentMemberName = currentMember.user?.name || currentMember.user?.id || 'Unknown User'; @@ -246,7 +247,7 @@ export const ChannelInfoOverlay = (props: ChannelInfoOverlayProps) => { }, '') : ''; const otherMembers = channel - ? Object.values(channel.state.members).filter((member) => member.user?.id !== clientId) + ? Object.values(channel.state.members).filter((member) => member.user?.id !== clientId) : []; return ( @@ -292,14 +293,14 @@ export const ChannelInfoOverlay = (props: ChannelInfoOverlayProps) => { ? 'Online' : `Last Seen ${dayjs(otherMembers[0].user?.last_active).fromNow()}` : `${Object.keys(channel.state.members).length} Members, ${ - Object.values(channel.state.members).filter( + Object.values(channel.state.members).filter( (member) => !!member.user?.online, ).length } Online`} (channel.state.members) .map((member) => member.user) .sort((a, b) => !!a?.online && !b?.online diff --git a/examples/SampleApp/src/components/ChannelPreview.tsx b/examples/SampleApp/src/components/ChannelPreview.tsx index a129eaec59..eab30d5f08 100644 --- a/examples/SampleApp/src/components/ChannelPreview.tsx +++ b/examples/SampleApp/src/components/ChannelPreview.tsx @@ -22,7 +22,7 @@ import { useChannelInfoOverlayContext } from '../context/ChannelInfoOverlayConte import type { StackNavigationProp } from '@react-navigation/stack'; -import type { StackNavigatorParamList, StreamChatGenerics } from '../types'; +import type { StackNavigatorParamList } from '../types'; import { ChannelState } from 'stream-chat'; const styles = StyleSheet.create({ @@ -71,7 +71,7 @@ const CustomChannelPreviewStatus = ( ); }; -export const ChannelPreview: React.FC> = ( +export const ChannelPreview: React.FC = ( props, ) => { const { channel } = props; @@ -82,7 +82,7 @@ export const ChannelPreview: React.FC(); + const { client } = useChatContext(); const navigation = useNavigation(); diff --git a/examples/SampleApp/src/components/MessageSearch/MessageSearchList.tsx b/examples/SampleApp/src/components/MessageSearch/MessageSearchList.tsx index 0df48bad0f..b3e91999c3 100644 --- a/examples/SampleApp/src/components/MessageSearch/MessageSearchList.tsx +++ b/examples/SampleApp/src/components/MessageSearch/MessageSearchList.tsx @@ -8,7 +8,7 @@ import { DEFAULT_PAGINATION_LIMIT } from '../../utils/constants'; import type { MessageResponse } from 'stream-chat'; -import type { StackNavigatorParamList, StreamChatGenerics } from '../../types'; +import type { StackNavigatorParamList } from '../../types'; dayjs.extend(calendar); @@ -52,13 +52,13 @@ export type MessageSearchListProps = { EmptySearchIndicator: React.ComponentType; loading: boolean; loadMore: () => void; - messages: MessageResponse[] | undefined; + messages: MessageResponse[] | undefined; refreshing?: boolean; refreshList?: () => void; showResultCount?: boolean; }; export const MessageSearchList: React.FC = React.forwardRef( - (props, scrollRef: React.Ref> | null>) => { + (props, scrollRef: React.Ref | null>) => { const { EmptySearchIndicator, loading, diff --git a/examples/SampleApp/src/components/NewDirectMessagingSendButton.tsx b/examples/SampleApp/src/components/NewDirectMessagingSendButton.tsx index 0d1037e5ec..07f4e93bc0 100644 --- a/examples/SampleApp/src/components/NewDirectMessagingSendButton.tsx +++ b/examples/SampleApp/src/components/NewDirectMessagingSendButton.tsx @@ -4,7 +4,6 @@ import { TouchableOpacity } from 'react-native-gesture-handler'; import { useNavigation } from '@react-navigation/core'; import { - DefaultStreamChatGenerics, MessageInputContextValue, Search, SendRight, @@ -16,20 +15,15 @@ import { import { NewDirectMessagingScreenNavigationProp } from '../screens/NewDirectMessagingScreen'; -import { StreamChatGenerics as LocalStreamChatGenerics } from '../types'; import { useUserSearchContext } from '../context/UserSearchContext'; import { useAppContext } from '../context/AppContext'; -type NewDirectMessagingSendButtonPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'giphyActive' | 'sendMessage'> & { +type NewDirectMessagingSendButtonPropsWithContext = Pick & { /** Disables the button */ disabled: boolean; }; -const SendButtonWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: NewDirectMessagingSendButtonPropsWithContext, +const SendButtonWithContext = ( + props: NewDirectMessagingSendButtonPropsWithContext, ) => { const { disabled = false, giphyActive, sendMessage } = props; const { @@ -53,9 +47,9 @@ const SendButtonWithContext = < ); }; -const areEqual = ( - prevProps: NewDirectMessagingSendButtonPropsWithContext, - nextProps: NewDirectMessagingSendButtonPropsWithContext, +const areEqual = ( + prevProps: NewDirectMessagingSendButtonPropsWithContext, + nextProps: NewDirectMessagingSendButtonPropsWithContext, ) => { const { disabled: prevDisabled, @@ -91,20 +85,18 @@ const MemoizedNewDirectMessagingSendButton = React.memo( areEqual, ) as typeof SendButtonWithContext; -export type SendButtonProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; +export type SendButtonProps = Partial; /** * UI Component for send button in MessageInput component. */ -export const NewDirectMessagingSendButton = (props: SendButtonProps) => { +export const NewDirectMessagingSendButton = (props: SendButtonProps) => { const { chatClient } = useAppContext(); const navigation = useNavigation(); - const { channel } = useChannelContext(); + const { channel } = useChannelContext(); const { selectedUserIds, reset } = useUserSearchContext(); - const { giphyActive, text } = useMessageInputContext(); + const { giphyActive, text } = useMessageInputContext(); const sendMessage = async () => { if (!channel) { @@ -133,7 +125,7 @@ export const NewDirectMessagingSendButton = (props: SendButtonProps + export const ThreadsUnreadCountBadge: React.FC = () => { const { chatClient } = useAppContext(); - const { unreadCount } = useStateStore(chatClient?.threads?.state, selector); + const { unreadCount } = useStateStore(chatClient?.threads?.state, selector) ?? { unreadCount: 0 }; return ; }; diff --git a/examples/SampleApp/src/components/UserInfoOverlay.tsx b/examples/SampleApp/src/components/UserInfoOverlay.tsx index 90831ccca2..a0f9f59598 100644 --- a/examples/SampleApp/src/components/UserInfoOverlay.tsx +++ b/examples/SampleApp/src/components/UserInfoOverlay.tsx @@ -35,7 +35,6 @@ import { useAppOverlayContext } from '../context/AppOverlayContext'; import { useBottomSheetOverlayContext } from '../context/BottomSheetOverlayContext'; import { useUserInfoOverlayContext } from '../context/UserInfoOverlayContext'; -import type { StreamChatGenerics } from '../types'; import { useAppContext } from '../context/AppContext'; import { UserResponse } from 'stream-chat'; @@ -105,7 +104,7 @@ export const UserInfoOverlay = (props: UserInfoOverlayProps) => { const { overlayOpacity, visible } = props; const { chatClient } = useAppContext(); const { overlay, setOverlay } = useAppOverlayContext(); - const { client } = useChatContext(); + const { client } = useChatContext(); const { setData } = useBottomSheetOverlayContext(); const { data, reset } = useUserInfoOverlayContext(); const { vh } = useViewport(); @@ -298,7 +297,6 @@ export const UserInfoOverlay = (props: UserInfoOverlayProps) => { // Check if the channel already exists. const channels = await client.queryChannels({ - distinct: true, members, }); @@ -351,7 +349,6 @@ export const UserInfoOverlay = (props: UserInfoOverlayProps) => { // Check if the channel already exists. const channels = await client.queryChannels({ - distinct: true, members, }); diff --git a/examples/SampleApp/src/components/UserSearch/SelectedUserTag.tsx b/examples/SampleApp/src/components/UserSearch/SelectedUserTag.tsx index c1ae1d5f8f..4ea60b956e 100644 --- a/examples/SampleApp/src/components/UserSearch/SelectedUserTag.tsx +++ b/examples/SampleApp/src/components/UserSearch/SelectedUserTag.tsx @@ -4,7 +4,6 @@ import { useTheme } from 'stream-chat-react-native'; import type { UserResponse } from 'stream-chat'; -import type { StreamChatGenerics } from '../../types'; const styles = StyleSheet.create({ tagContainer: { @@ -29,7 +28,7 @@ const styles = StyleSheet.create({ type SelectedUserTagProps = { index: number; onPress: () => void; - tag: UserResponse; + tag: UserResponse; disabled?: boolean; }; diff --git a/examples/SampleApp/src/components/UserSearch/UserGridItem.tsx b/examples/SampleApp/src/components/UserSearch/UserGridItem.tsx index fdb7afa4ca..c61449e923 100644 --- a/examples/SampleApp/src/components/UserSearch/UserGridItem.tsx +++ b/examples/SampleApp/src/components/UserSearch/UserGridItem.tsx @@ -5,7 +5,6 @@ import { Avatar, Close, useTheme } from 'stream-chat-react-native'; import type { UserResponse } from 'stream-chat'; -import type { StreamChatGenerics } from '../../types'; const presenceIndicator = { cx: 7, cy: 7, r: 5 }; @@ -40,7 +39,7 @@ const styles = StyleSheet.create({ export type UserGridItemProps = { onPress: () => void; - user: UserResponse; + user: UserResponse; removeButton?: boolean; }; @@ -57,7 +56,6 @@ export const UserGridItem: React.FC = ({ return ( []; + results?: UserResponse[]; showOnlineStatus?: boolean; - toggleSelectedUser?: (user: UserResponse) => void; + toggleSelectedUser?: (user: UserResponse) => void; }; export const UserSearchResults: React.FC = ({ @@ -87,7 +86,7 @@ export const UserSearchResults: React.FC = ({ } = useUserSearchContext(); const [sections, setSections] = useState< Array<{ - data: UserResponse[]; + data: UserResponse[]; title: string; }> >([]); @@ -113,7 +112,7 @@ export const UserSearchResults: React.FC = ({ useEffect(() => { const newSections: { [key: string]: { - data: UserResponse[]; + data: UserResponse[]; title: string; }; } = {}; @@ -204,7 +203,7 @@ export const UserSearchResults: React.FC = ({ }, ]} > - + ) => void; - results: UserResponse[]; + onPress: (user: UserResponse) => void; + results: UserResponse[]; gridSize?: number; loading?: boolean; loadMore?: () => void; diff --git a/examples/SampleApp/src/context/AppContext.ts b/examples/SampleApp/src/context/AppContext.ts index f50d94f24d..0ca2b2300a 100644 --- a/examples/SampleApp/src/context/AppContext.ts +++ b/examples/SampleApp/src/context/AppContext.ts @@ -2,10 +2,10 @@ import React from 'react'; import type { StreamChat } from 'stream-chat'; -import type { LoginConfig, StreamChatGenerics } from '../types'; +import type { LoginConfig } from '../types'; type AppContextType = { - chatClient: StreamChat | null; + chatClient: StreamChat | null; loginUser: (config: LoginConfig) => void; logout: () => void; switchUser: (userId?: string) => void; diff --git a/examples/SampleApp/src/context/BottomSheetOverlayContext.tsx b/examples/SampleApp/src/context/BottomSheetOverlayContext.tsx index 437b4f668a..16bb3f3110 100644 --- a/examples/SampleApp/src/context/BottomSheetOverlayContext.tsx +++ b/examples/SampleApp/src/context/BottomSheetOverlayContext.tsx @@ -2,14 +2,13 @@ import React, { PropsWithChildren, useContext, useState } from 'react'; import type { ChannelContextValue } from 'stream-chat-react-native'; -import type { StreamChatGenerics } from '../types'; export const isAddMemberBottomSheetData = ( data: BottomSheetOverlayData, -): data is Pick, 'channel'> => 'channel' in data; +): data is Pick => 'channel' in data; export type BottomSheetOverlayData = - | Pick, 'channel'> + | Pick | { onConfirm: () => void; title: string; diff --git a/examples/SampleApp/src/context/ChannelInfoOverlayContext.tsx b/examples/SampleApp/src/context/ChannelInfoOverlayContext.tsx index 59646feb63..dc73658fea 100644 --- a/examples/SampleApp/src/context/ChannelInfoOverlayContext.tsx +++ b/examples/SampleApp/src/context/ChannelInfoOverlayContext.tsx @@ -4,7 +4,7 @@ import { ChannelState } from 'stream-chat'; import type { StackNavigationProp } from '@react-navigation/stack'; import type { ChannelContextValue } from 'stream-chat-react-native'; -import type { StackNavigatorParamList, StreamChatGenerics } from '../types'; +import type { StackNavigatorParamList } from '../types'; type ChannelListScreenNavigationProp = StackNavigationProp< StackNavigatorParamList, @@ -12,10 +12,10 @@ type ChannelListScreenNavigationProp = StackNavigationProp< >; export type ChannelInfoOverlayData = Partial< - Pick, 'channel'> + Pick > & { clientId?: string; - membership?: ChannelState['membership']; + membership?: ChannelState['membership']; navigation?: ChannelListScreenNavigationProp; }; diff --git a/examples/SampleApp/src/context/UserInfoOverlayContext.tsx b/examples/SampleApp/src/context/UserInfoOverlayContext.tsx index f905fb380a..da5ebdb4a5 100644 --- a/examples/SampleApp/src/context/UserInfoOverlayContext.tsx +++ b/examples/SampleApp/src/context/UserInfoOverlayContext.tsx @@ -4,7 +4,7 @@ import type { StackNavigationProp } from '@react-navigation/stack'; import type { ChannelState } from 'stream-chat'; import type { ChannelContextValue } from 'stream-chat-react-native'; -import type { StackNavigatorParamList, StreamChatGenerics } from '../types'; +import type { StackNavigatorParamList } from '../types'; type GroupChannelDetailsScreenNavigationProp = StackNavigationProp< StackNavigatorParamList, @@ -12,9 +12,9 @@ type GroupChannelDetailsScreenNavigationProp = StackNavigationProp< >; export type UserInfoOverlayData = Partial< - Pick, 'channel'> + Pick > & { - member?: ChannelState['members'][0]; + member?: ChannelState['members'][0]; navigation?: GroupChannelDetailsScreenNavigationProp; }; diff --git a/examples/SampleApp/src/custom-types.d.ts b/examples/SampleApp/src/custom-types.d.ts new file mode 100644 index 0000000000..2dfa7528d4 --- /dev/null +++ b/examples/SampleApp/src/custom-types.d.ts @@ -0,0 +1,45 @@ +import { + DefaultAttachmentData, + DefaultChannelData, + DefaultCommandData, + DefaultEventData, + DefaultMemberData, + DefaultMessageData, + DefaultPollData, + DefaultPollOptionData, + DefaultReactionData, + DefaultThreadData, + DefaultUserData, +} from 'stream-chat-react-native'; + +declare module 'stream-chat' { + /* eslint-disable @typescript-eslint/no-empty-object-type */ + + interface CustomAttachmentData extends DefaultAttachmentData { + id?: string; + } + + interface CustomChannelData extends DefaultChannelData {} + + interface CustomCommandData extends DefaultCommandData {} + + interface CustomEventData extends DefaultEventData {} + + interface CustomMemberData extends DefaultMemberData {} + + interface CustomUserData extends DefaultUserData {} + + interface CustomMessageData extends DefaultMessageData { + ai_generated?: boolean; + } + + interface CustomPollOptionData extends DefaultPollOptionData {} + + interface CustomPollData extends DefaultPollData {} + + interface CustomReactionData extends DefaultReactionData {} + + interface CustomThreadData extends DefaultThreadData {} + + /* eslint-enable @typescript-eslint/no-empty-object-type */ +} diff --git a/examples/SampleApp/src/hooks/useChannelMembersStatus.ts b/examples/SampleApp/src/hooks/useChannelMembersStatus.ts index e7fc8dc6a8..92db54fbf7 100644 --- a/examples/SampleApp/src/hooks/useChannelMembersStatus.ts +++ b/examples/SampleApp/src/hooks/useChannelMembersStatus.ts @@ -5,9 +5,7 @@ import { getUserActivityStatus } from '../utils/getUserActivityStatus'; import type { Channel } from 'stream-chat'; -import type { StreamChatGenerics } from '../types'; - -export const useChannelMembersStatus = (channel: Channel) => { +export const useChannelMembersStatus = (channel: Channel) => { const watchersCount = channel.state.watcher_count; const memberCount = channel?.data?.member_count; diff --git a/examples/SampleApp/src/hooks/useChatClient.ts b/examples/SampleApp/src/hooks/useChatClient.ts index e22502c72b..7e11e8235a 100644 --- a/examples/SampleApp/src/hooks/useChatClient.ts +++ b/examples/SampleApp/src/hooks/useChatClient.ts @@ -6,7 +6,7 @@ import { SqliteClient } from 'stream-chat-react-native'; import { USER_TOKENS, USERS } from '../ChatUsers'; import AsyncStore from '../utils/AsyncStore'; -import type { LoginConfig, StreamChatGenerics } from '../types'; +import type { LoginConfig } from '../types'; import { PermissionsAndroid, Platform } from 'react-native'; const messaging = getMessaging(); @@ -30,10 +30,10 @@ const requestAndroidPermission = async () => { }; export const useChatClient = () => { - const [chatClient, setChatClient] = useState | null>(null); + const [chatClient, setChatClient] = useState(null); const [isConnecting, setIsConnecting] = useState(true); - const unsubscribePushListenersRef = useRef<() => void>(); + const unsubscribePushListenersRef = useRef<() => void>(undefined); /** * @param config the user login config @@ -42,7 +42,7 @@ export const useChatClient = () => { const loginUser = async (config: LoginConfig) => { // unsubscribe from previous push listeners unsubscribePushListenersRef.current?.(); - const client = StreamChat.getInstance(config.apiKey, { + const client = StreamChat.getInstance(config.apiKey, { timeout: 6000, // logger: (type, msg) => console.log(type, msg) }); diff --git a/examples/SampleApp/src/hooks/usePaginatedAttachments.ts b/examples/SampleApp/src/hooks/usePaginatedAttachments.ts index 6a845c5396..54413afe4e 100644 --- a/examples/SampleApp/src/hooks/usePaginatedAttachments.ts +++ b/examples/SampleApp/src/hooks/usePaginatedAttachments.ts @@ -4,10 +4,8 @@ import { useAppContext } from '../context/AppContext'; import type { Channel, MessageResponse } from 'stream-chat'; -import type { StreamChatGenerics } from '../types'; - export const usePaginatedAttachments = ( - channel: Channel, + channel: Channel, attachmentType: string, ) => { const { chatClient } = useAppContext(); @@ -15,7 +13,7 @@ export const usePaginatedAttachments = ( const hasMoreResults = useRef(true); const queryInProgress = useRef(false); const [loading, setLoading] = useState(true); - const [messages, setMessages] = useState[]>([]); + const [messages, setMessages] = useState([]); const fetchAttachments = async () => { if (queryInProgress.current) { diff --git a/examples/SampleApp/src/hooks/usePaginatedPinnedMessages.ts b/examples/SampleApp/src/hooks/usePaginatedPinnedMessages.ts index fbdaa8d319..2cfa5d73d9 100644 --- a/examples/SampleApp/src/hooks/usePaginatedPinnedMessages.ts +++ b/examples/SampleApp/src/hooks/usePaginatedPinnedMessages.ts @@ -4,17 +4,16 @@ import { useAppContext } from '../context/AppContext'; import type { Channel, MessageResponse } from 'stream-chat'; -import type { StreamChatGenerics } from '../types'; import { DEFAULT_PAGINATION_LIMIT } from '../utils/constants'; -export const usePaginatedPinnedMessages = (channel: Channel) => { +export const usePaginatedPinnedMessages = (channel: Channel) => { const { chatClient } = useAppContext(); const offset = useRef(0); const hasMoreResults = useRef(true); const queryInProgress = useRef(false); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); - const [messages, setMessages] = useState[]>([]); + const [messages, setMessages] = useState([]); const fetchPinnedMessages = async () => { if (queryInProgress.current) { diff --git a/examples/SampleApp/src/hooks/usePaginatedSearchedMessages.ts b/examples/SampleApp/src/hooks/usePaginatedSearchedMessages.ts index 017f3e975d..d7c2757b29 100644 --- a/examples/SampleApp/src/hooks/usePaginatedSearchedMessages.ts +++ b/examples/SampleApp/src/hooks/usePaginatedSearchedMessages.ts @@ -4,16 +4,15 @@ import { useAppContext } from '../context/AppContext'; import type { MessageFilters, MessageResponse } from 'stream-chat'; -import type { StreamChatGenerics } from '../types'; import { DEFAULT_PAGINATION_LIMIT } from '../utils/constants'; export const usePaginatedSearchedMessages = ( - messageFilters: string | MessageFilters = {}, + messageFilters: string | MessageFilters = {}, ) => { const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(false); - const [messages, setMessages] = useState[]>(); + const [messages, setMessages] = useState(); const offset = useRef(0); const hasMoreResults = useRef(true); const queryInProgress = useRef(false); diff --git a/examples/SampleApp/src/hooks/usePaginatedUsers.ts b/examples/SampleApp/src/hooks/usePaginatedUsers.ts index d36ab1c75d..3b375f4461 100644 --- a/examples/SampleApp/src/hooks/usePaginatedUsers.ts +++ b/examples/SampleApp/src/hooks/usePaginatedUsers.ts @@ -4,41 +4,40 @@ import { useAppContext } from '../context/AppContext'; import type { UserFilters, UserResponse } from 'stream-chat'; -import type { StreamChatGenerics } from '../types'; export type PaginatedUsers = { clearText: () => void; - initialResults: UserResponse[] | null; + initialResults: UserResponse[] | null; loading: boolean; loadMore: () => void; onChangeSearchText: (newText: string) => void; onFocusInput: () => void; removeUser: (index: number) => void; reset: () => void; - results: UserResponse[]; + results: UserResponse[]; searchText: string; selectedUserIds: string[]; - selectedUsers: UserResponse[]; + selectedUsers: UserResponse[]; setInitialResults: React.Dispatch< - React.SetStateAction[] | null> + React.SetStateAction >; - setResults: React.Dispatch[]>>; + setResults: React.Dispatch>; setSearchText: React.Dispatch>; - setSelectedUsers: React.Dispatch[]>>; - toggleUser: (user: UserResponse) => void; + setSelectedUsers: React.Dispatch>; + toggleUser: (user: UserResponse) => void; }; export const usePaginatedUsers = (): PaginatedUsers => { const { chatClient } = useAppContext(); - const [initialResults, setInitialResults] = useState[] | null>( + const [initialResults, setInitialResults] = useState( null, ); const [loading, setLoading] = useState(true); - const [results, setResults] = useState[]>([]); + const [results, setResults] = useState([]); const [searchText, setSearchText] = useState(''); const [selectedUserIds, setSelectedUserIds] = useState([]); - const [selectedUsers, setSelectedUsers] = useState[]>([]); + const [selectedUsers, setSelectedUsers] = useState([]); const hasMoreResults = useRef(true); const offset = useRef(0); @@ -51,7 +50,7 @@ export const usePaginatedUsers = (): PaginatedUsers => { setSelectedUsers([]); }; - const addUser = (user: UserResponse) => { + const addUser = (user: UserResponse) => { setSelectedUsers([...selectedUsers, user]); setSelectedUserIds((prevSelectedUserIds) => { prevSelectedUserIds.push(user.id); @@ -79,7 +78,7 @@ export const usePaginatedUsers = (): PaginatedUsers => { }); }; - const toggleUser = (user: UserResponse) => { + const toggleUser = (user: UserResponse) => { if (!user.id) { return; } diff --git a/examples/SampleApp/src/screens/ChannelFilesScreen.tsx b/examples/SampleApp/src/screens/ChannelFilesScreen.tsx index a47c9d54d1..cde55c465a 100644 --- a/examples/SampleApp/src/screens/ChannelFilesScreen.tsx +++ b/examples/SampleApp/src/screens/ChannelFilesScreen.tsx @@ -1,11 +1,10 @@ import React, { useEffect, useState } from 'react'; -import { SectionList, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { Alert, SectionList, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import Dayjs from 'dayjs'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { FileIcon, getFileSizeDisplayText, - goToURL, ThemeProvider, useTheme, } from 'stream-chat-react-native'; @@ -17,7 +16,7 @@ import { File } from '../icons/File'; import type { RouteProp } from '@react-navigation/native'; import type { Attachment } from 'stream-chat'; -import type { StackNavigatorParamList, StreamChatGenerics } from '../types'; +import type { StackNavigatorParamList } from '../types'; const styles = StyleSheet.create({ container: { @@ -90,7 +89,7 @@ export const ChannelFilesScreen: React.FC = ({ const [sections, setSections] = useState< Array<{ - data: Attachment[]; + data: Attachment[]; title: string; }> >([]); @@ -99,7 +98,7 @@ export const ChannelFilesScreen: React.FC = ({ const newSections: Record< string, { - data: Attachment[]; + data: Attachment[]; title: string; } > = {}; @@ -139,14 +138,16 @@ export const ChannelFilesScreen: React.FC = ({ {(sections.length > 0 || !loading) && ( - > + contentContainerStyle={styles.sectionContentContainer} ListEmptyComponent={EmptyListComponent} onEndReached={loadMore} renderItem={({ index, item: attachment, section }) => ( goToURL(attachment.asset_url)} + onPress={() => { + Alert.alert('Not implemented.'); + }} style={{ borderBottomColor: border, borderBottomWidth: index === section.data.length - 1 ? 0 : 1, diff --git a/examples/SampleApp/src/screens/ChannelImagesScreen.tsx b/examples/SampleApp/src/screens/ChannelImagesScreen.tsx index e1914ebc9a..fd124f72a4 100644 --- a/examples/SampleApp/src/screens/ChannelImagesScreen.tsx +++ b/examples/SampleApp/src/screens/ChannelImagesScreen.tsx @@ -26,7 +26,7 @@ import { Picture } from '../icons/Picture'; import type { RouteProp } from '@react-navigation/native'; import type { Attachment } from 'stream-chat'; -import type { StackNavigatorParamList, StreamChatGenerics } from '../types'; +import type { StackNavigatorParamList } from '../types'; const screen = Dimensions.get('screen').width; @@ -70,7 +70,7 @@ export const ChannelImagesScreen: React.FC = ({ messages: images, setMessages: setImages, setSelectedMessage: setImage, - } = useImageGalleryContext(); + } = useImageGalleryContext(); const { setOverlay } = useOverlayContext(); const { loading, loadMore, messages } = usePaginatedAttachments(channel, 'image'); const { @@ -110,9 +110,9 @@ export const ChannelImagesScreen: React.FC = ({ * Photos array created from all currently available * photo attachments */ - const photos = messages.reduce((acc: Photo[], cur) => { + const photos = messages.reduce((acc: Photo[], cur) => { const attachmentImages = - (cur.attachments as Attachment[])?.filter( + (cur.attachments as Attachment[])?.filter( (attachment) => attachment.type === 'image' && !attachment.title_link && @@ -151,7 +151,7 @@ export const ChannelImagesScreen: React.FC = ({ */ const imageString = messagesWithImages .map((message) => - (message.attachments as Attachment[]) + (message.attachments as Attachment[]) .map((attachment) => attachment.image_url || attachment.thumb_url || '') .join(), ) diff --git a/examples/SampleApp/src/screens/ChannelListScreen.tsx b/examples/SampleApp/src/screens/ChannelListScreen.tsx index 7662df6e58..b1aa577a47 100644 --- a/examples/SampleApp/src/screens/ChannelListScreen.tsx +++ b/examples/SampleApp/src/screens/ChannelListScreen.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; +import React, { RefObject, useCallback, useMemo, useRef, useState } from 'react'; import { FlatList, FlatListProps, @@ -19,8 +19,6 @@ import { usePaginatedSearchedMessages } from '../hooks/usePaginatedSearchedMessa import type { ChannelSort } from 'stream-chat'; -import type { StreamChatGenerics } from '../types'; - const styles = StyleSheet.create({ channelListContainer: { height: '100%', @@ -61,7 +59,7 @@ const baseFilters = { type: 'messaging', }; -const sort: ChannelSort = [ +const sort: ChannelSort = [ { pinned_at: -1 }, { last_message_at: -1 }, { updated_at: -1 }, @@ -85,7 +83,7 @@ export const ChannelListScreen: React.FC = () => { } = useTheme(); const searchInputRef = useRef(null); - const scrollRef = useRef> | null>(null); + const scrollRef = useRef | null>(null); const [searchInputText, setSearchInputText] = useState(''); const [searchQuery, setSearchQuery] = useState(''); @@ -104,7 +102,7 @@ export const ChannelListScreen: React.FC = () => { [chatClientUserId], ); - useScrollToTop(scrollRef); + useScrollToTop(scrollRef as RefObject>); // eslint-disable-next-line react/no-unstable-nested-components const EmptySearchIndicator = () => ( @@ -116,7 +114,7 @@ export const ChannelListScreen: React.FC = () => { ); - const additionalFlatListProps = useMemo>>>( + const additionalFlatListProps = useMemo>>( () => ({ getItemLayout: (_: unknown, index: number) => ({ index, @@ -138,7 +136,7 @@ export const ChannelListScreen: React.FC = () => { ); const setScrollRef = useCallback( - () => (ref: FlatList> | null) => { + () => (ref: FlatList | null) => { scrollRef.current = ref; }, [], @@ -217,7 +215,7 @@ export const ChannelListScreen: React.FC = () => { )} - + ; + channel: StreamChatChannel; }; const ChannelHeader: React.FC = ({ channel }) => { @@ -115,12 +115,12 @@ export const ChannelScreen: React.FC = ({ }, } = useTheme(); - const [channel, setChannel] = useState | undefined>( + const [channel, setChannel] = useState( channelFromProp, ); const [selectedThread, setSelectedThread] = - useState['thread']>(); + useState(); useEffect(() => { const initChannel = async () => { @@ -168,7 +168,7 @@ export const ChannelScreen: React.FC = ({ thread={selectedThread} > - + diff --git a/examples/SampleApp/src/screens/GroupChannelDetailsScreen.tsx b/examples/SampleApp/src/screens/GroupChannelDetailsScreen.tsx index 77f5a8582b..2bddc63cb2 100644 --- a/examples/SampleApp/src/screens/GroupChannelDetailsScreen.tsx +++ b/examples/SampleApp/src/screens/GroupChannelDetailsScreen.tsx @@ -38,7 +38,7 @@ import { getUserActivityStatus } from '../utils/getUserActivityStatus'; import type { StackNavigationProp } from '@react-navigation/stack'; import type { Channel, UserResponse } from 'stream-chat'; -import type { StackNavigatorParamList, StreamChatGenerics } from '../types'; +import type { StackNavigatorParamList } from '../types'; import { Pin } from '../icons/Pin'; import { NewGroupIcon } from '../icons/NewGroupIcon'; @@ -177,7 +177,7 @@ export const GroupChannelDetailsScreen: React.FC = ({ const [textInputFocused, setTextInputFocused] = useState(false); const membersStatus = useChannelMembersStatus(channel); - const displayName = useChannelPreviewDisplayName(channel, 30); + const displayName = useChannelPreviewDisplayName(channel, 30); const allMembersLength = allMembers.length; useEffect(() => { @@ -368,7 +368,7 @@ export const GroupChannelDetailsScreen: React.FC = ({ await channel.update({ ...channel.data, name: groupName, - } as Parameters['update']>[0]); + } as Parameters[0]); if (textInputRef.current) { textInputRef.current.blur(); } diff --git a/examples/SampleApp/src/screens/MentionsScreen.tsx b/examples/SampleApp/src/screens/MentionsScreen.tsx index d1367d9dac..11c01155bd 100644 --- a/examples/SampleApp/src/screens/MentionsScreen.tsx +++ b/examples/SampleApp/src/screens/MentionsScreen.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useRef } from 'react'; +import React, { RefObject, useMemo, useRef } from 'react'; import { FlatList, StyleSheet, Text, View } from 'react-native'; import { AtMentions, useTheme } from 'stream-chat-react-native'; import { MessageResponse } from 'stream-chat'; @@ -12,7 +12,6 @@ import type { StackNavigationProp } from '@react-navigation/stack'; import type { BottomTabNavigatorParamList } from '../types'; import { useAppContext } from '../context/AppContext'; -import type { StreamChatGenerics } from '../types'; const styles = StyleSheet.create({ container: { @@ -64,9 +63,9 @@ export const MentionsScreen: React.FC = () => { [chatClient], ); - const scrollRef = useRef> | null>(null); + const scrollRef = useRef | null>(null); - useScrollToTop(scrollRef); + useScrollToTop(scrollRef as RefObject>); const { loading, loadMore, messages, refreshing, refreshList } = usePaginatedSearchedMessages(messageFilters); diff --git a/examples/SampleApp/src/screens/NewDirectMessagingScreen.tsx b/examples/SampleApp/src/screens/NewDirectMessagingScreen.tsx index 306f81cdb9..228129e300 100644 --- a/examples/SampleApp/src/screens/NewDirectMessagingScreen.tsx +++ b/examples/SampleApp/src/screens/NewDirectMessagingScreen.tsx @@ -22,7 +22,7 @@ import type { StackNavigationProp } from '@react-navigation/stack'; import type { Channel as StreamChatChannel } from 'stream-chat'; import { NewDirectMessagingSendButton } from '../components/NewDirectMessagingSendButton'; -import type { StackNavigatorParamList, StreamChatGenerics } from '../types'; +import type { StackNavigatorParamList } from '../types'; const styles = StyleSheet.create({ container: { @@ -136,7 +136,7 @@ export const NewDirectMessagingScreen: React.FC = const messageInputRef = useRef(null); const searchInputRef = useRef(null); - const currentChannel = useRef>(); + const currentChannel = useRef(undefined); const isDraft = useRef(true); const [focusOnMessageInput, setFocusOnMessageInput] = useState(false); @@ -163,7 +163,6 @@ export const NewDirectMessagingScreen: React.FC = // Check if the channel already exists. const channels = await chatClient.queryChannels({ - distinct: true, members, }); @@ -312,7 +311,7 @@ export const NewDirectMessagingScreen: React.FC = }, ]} > - + { setFocusOnMessageInput(true); diff --git a/examples/SampleApp/src/screens/SharedGroupsScreen.tsx b/examples/SampleApp/src/screens/SharedGroupsScreen.tsx index bc082eedbb..c0c0894e97 100644 --- a/examples/SampleApp/src/screens/SharedGroupsScreen.tsx +++ b/examples/SampleApp/src/screens/SharedGroupsScreen.tsx @@ -18,7 +18,7 @@ import { ScreenHeader } from '../components/ScreenHeader'; import { useAppContext } from '../context/AppContext'; import { Contacts } from '../icons/Contacts'; -import type { StackNavigatorParamList, StreamChatGenerics } from '../types'; +import type { StackNavigatorParamList } from '../types'; const styles = StyleSheet.create({ container: { @@ -54,7 +54,7 @@ const styles = StyleSheet.create({ }, }); -type CustomPreviewProps = ChannelPreviewMessengerProps; +type CustomPreviewProps = ChannelPreviewMessengerProps; const CustomPreview: React.FC = ({ channel }) => { const { chatClient } = useAppContext(); @@ -107,16 +107,12 @@ const CustomPreview: React.FC = ({ channel }) => { {displayAvatar.images ? ( ) : ( { ); }; -type ListComponentProps = ChannelListMessengerProps; +type ListComponentProps = ChannelListMessengerProps; // If the length of channels is 1, which means we only got 1:1-distinct channel, // And we don't want to show 1:1-distinct channel in this list. diff --git a/examples/SampleApp/src/screens/ThreadScreen.tsx b/examples/SampleApp/src/screens/ThreadScreen.tsx index d5df5d40ff..9f7d27d75e 100644 --- a/examples/SampleApp/src/screens/ThreadScreen.tsx +++ b/examples/SampleApp/src/screens/ThreadScreen.tsx @@ -4,7 +4,7 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { Channel, Thread, - ThreadContextValue, + ThreadType, useAttachmentPickerContext, useTheme, useTypingString, @@ -15,8 +15,8 @@ import { ScreenHeader } from '../components/ScreenHeader'; import type { RouteProp } from '@react-navigation/native'; -import type { StackNavigatorParamList, StreamChatGenerics } from '../types'; -import { ThreadState } from 'stream-chat'; +import type { StackNavigatorParamList } from '../types'; +import { LocalMessage, ThreadState, UserResponse } from 'stream-chat'; const selector = (nextValue: ThreadState) => ({ parentMessage: nextValue.parentMessage }) as const; @@ -33,20 +33,17 @@ type ThreadScreenProps = { }; export type ThreadHeaderProps = { - thread: ThreadContextValue['thread']; + thread: LocalMessage | ThreadType; }; const ThreadHeader: React.FC = ({ thread }) => { const typing = useTypingString(); - let subtitleText = thread?.user?.name; + let subtitleText = ((thread as LocalMessage)?.user as UserResponse)?.name; const { parentMessage } = - useStateStore( - (thread?.threadInstance as ThreadContextValue['threadInstance'])?.state, - selector, - ) || {}; + useStateStore((thread as ThreadType)?.threadInstance?.state, selector) || {}; if (subtitleText == null) { - subtitleText = parentMessage?.user?.name; + subtitleText = (parentMessage?.user as UserResponse)?.name; } return ( @@ -78,7 +75,7 @@ export const ThreadScreen: React.FC = ({ return ( - + = ({ > - /> + diff --git a/examples/SampleApp/src/types.ts b/examples/SampleApp/src/types.ts index 02bf4e24bf..aeec68cf85 100644 --- a/examples/SampleApp/src/types.ts +++ b/examples/SampleApp/src/types.ts @@ -1,39 +1,7 @@ -import type { Immutable } from 'seamless-immutable'; -import type { Channel, UserResponse } from 'stream-chat'; -import type { ThreadContextValue } from 'stream-chat-react-native'; +import type { Channel, LocalMessage, UserResponse } from 'stream-chat'; +import type { ThreadType } from 'stream-chat-react-native'; import type { Theme } from '@react-navigation/native'; -export type LocalAttachmentType = { - file_size?: number; - mime_type?: string; -}; -export type LocalChannelType = Record; -export type LocalCommandType = string & {}; -export type LocalEventType = Record; -export type LocalMessageType = Record; -export type LocalReactionType = Record; -export type LocalUserType = { - image?: string; -}; -type LocalPollOptionType = Record; -type LocalPollType = Record; -type LocalMemberType = Record; - -export type StreamChatGenerics = { - attachmentType: LocalAttachmentType; - channelType: LocalChannelType; - commandType: LocalCommandType; - eventType: LocalEventType; - memberType: LocalMemberType; - messageType: LocalMessageType; - pollOptionType: LocalPollOptionType; - pollType: LocalPollType; - reactionType: LocalReactionType; - userType: LocalUserType; -} - -// export type StreamChatGenerics = DefaultGenerics; - export type DrawerNavigatorParamList = { HomeScreen: undefined; UserSelectorScreen: undefined; @@ -41,36 +9,36 @@ export type DrawerNavigatorParamList = { export type StackNavigatorParamList = { ChannelFilesScreen: { - channel: Channel; + channel: Channel; }; ChannelImagesScreen: { - channel: Channel; + channel: Channel; }; ChannelListScreen: undefined; ChannelPinnedMessagesScreen: { - channel: Channel; + channel: Channel; }; ChannelScreen: { - channel?: Channel; + channel?: Channel; channelId?: string; messageId?: string; }; GroupChannelDetailsScreen: { - channel: Channel; + channel: Channel; }; MessagingScreen: undefined; NewDirectMessagingScreen: undefined; NewGroupChannelAddMemberScreen: undefined; NewGroupChannelAssignNameScreen: undefined; OneOnOneChannelDetailScreen: { - channel: Channel; + channel: Channel; }; SharedGroupsScreen: { - user: Immutable> | UserResponse; + user: UserResponse; }; ThreadScreen: { - channel: Channel; - thread: ThreadContextValue['thread']; + channel: Channel; + thread: LocalMessage | ThreadType; }; }; diff --git a/examples/SampleApp/src/utils/RootNavigation.ts b/examples/SampleApp/src/utils/RootNavigation.ts index 1684b85393..381ca2d436 100644 --- a/examples/SampleApp/src/utils/RootNavigation.ts +++ b/examples/SampleApp/src/utils/RootNavigation.ts @@ -2,7 +2,8 @@ import React from 'react'; import { CommonActions, NavigationContainerRef } from '@react-navigation/native'; import { StackNavigatorParamList } from '../types'; -export const RootNavigationRef = React.createRef(); +export const RootNavigationRef = + React.createRef>(); export const navigateToChannel = (channelId: string | null | undefined) => { if (!channelId || !RootNavigationRef.current) { @@ -27,7 +28,6 @@ export const navigateToChannel = (channelId: string | null | undefined) => { } else { // navigate to channel screen return CommonActions.navigate({ - key: `${Date.now()}`, name: 'ChannelScreen', params: { channelId }, }); diff --git a/examples/SampleApp/src/utils/base.tsx b/examples/SampleApp/src/utils/base.tsx index 71907384da..7c50c2bfb5 100644 --- a/examples/SampleApp/src/utils/base.tsx +++ b/examples/SampleApp/src/utils/base.tsx @@ -9,6 +9,7 @@ export type IconProps = Partial & dark?: boolean; height?: number; width?: number; + scale?: number; }; export const RootSvg = (props: IconProps) => { const { backgroundFill = 'none', children, height = 24, width = 24 } = props; diff --git a/examples/SampleApp/src/utils/getUserActivityStatus.ts b/examples/SampleApp/src/utils/getUserActivityStatus.ts index d9eeeaa591..df839c08e8 100644 --- a/examples/SampleApp/src/utils/getUserActivityStatus.ts +++ b/examples/SampleApp/src/utils/getUserActivityStatus.ts @@ -1,15 +1,12 @@ import Dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; -import { StreamChatGenerics } from '../types'; - -import type { Immutable } from 'seamless-immutable'; import type { UserResponse } from 'stream-chat'; Dayjs.extend(relativeTime); export const getUserActivityStatus = ( - user?: Immutable> | UserResponse, + user?: UserResponse, ) => { if (!user) { return ''; diff --git a/examples/SampleApp/yarn.lock b/examples/SampleApp/yarn.lock index 0fda1d1b52..098e0553c2 100644 --- a/examples/SampleApp/yarn.lock +++ b/examples/SampleApp/yarn.lock @@ -989,13 +989,20 @@ pirates "^4.0.6" source-map-support "^0.5.16" -"@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.25.0", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.17.2", "@babel/runtime@^7.25.0", "@babel/runtime@^7.8.4": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.9.tgz#aa4c6facc65b9cb3f87d75125ffd47781b475433" integrity sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg== dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.27.0": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762" + integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.25.0", "@babel/template@^7.25.9", "@babel/template@^7.26.9", "@babel/template@^7.3.3": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2" @@ -2558,10 +2565,10 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/jsonwebtoken@~9.0.0": - version "9.0.8" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.8.tgz#313490052801edfb031bb32b6bbd77cc9f230852" - integrity sha512-7fx54m60nLFUVYlxAB1xpe9CBWX2vSrk50Y6ogRJ1v5xxtba7qXTg5BgYDN5dq+yuQQ9HaVlHJyAAt1/mxryFg== +"@types/jsonwebtoken@^9.0.8", "@types/jsonwebtoken@~9.0.0": + version "9.0.9" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz#a4c3a446c0ebaaf467a58398382616f416345fb3" + integrity sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ== dependencies: "@types/ms" "*" "@types/node" "*" @@ -2616,6 +2623,13 @@ dependencies: "@types/node" "*" +"@types/ws@^8.5.14": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.0.tgz#8a2ec491d6f0685ceaab9a9b7ff44146236993b5" + integrity sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -5229,6 +5243,11 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +isomorphic-ws@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -5768,7 +5787,7 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -jsonwebtoken@~9.0.0: +jsonwebtoken@^9.0.2, jsonwebtoken@~9.0.0: version "9.0.2" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== @@ -5854,7 +5873,7 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -linkifyjs@^4.1.1: +linkifyjs@^4.1.1, linkifyjs@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08" integrity sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw== @@ -6273,6 +6292,11 @@ mime@^2.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== +mime@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.7.tgz#0b7a98b08c63bd3c10251e797d67840c9bde9f13" + integrity sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -6890,6 +6914,11 @@ react-native-haptic-feedback@^2.3.3: resolved "https://registry.yarnpkg.com/react-native-haptic-feedback/-/react-native-haptic-feedback-2.3.3.tgz#88b6876e91399a69bd1b551fe1681b2f3dc1214e" integrity sha512-svS4D5PxfNv8o68m9ahWfwje5NqukM3qLS48+WTdhbDkNUkOhP9rDfDSRHzlhk4zq+ISjyw95EhLeh8NkKX5vQ== +react-native-image-picker@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-8.2.0.tgz#d8656fdd1a0f1ad262c9c129d4f75900b685e56e" + integrity sha512-jIGllQJuJIn0YKss/JEeb0Kos1HSsnIpU+i3bYxR27sOxSyDZQyP9dKR22olssQPlfH+rGNR/Jc6xKRkhm48vw== + react-native-is-edge-to-edge@1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.6.tgz#69ec13f70d76e9245e275eed4140d0873a78f902" @@ -7530,10 +7559,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@6.7.3: - version "6.7.3" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-6.7.3.tgz#7e213c03a7414c85ec4ba9da247d9703878ab520" - integrity sha512-dYoYkVah7l4bmkI0vl7Ma6qD+xjsmeFWOdz7EVwbtR+B3HPMsgz8eRAmBj/p2xcSANxh1bPRD0iLlvD30vXyzg== +stream-chat-react-native-core@6.7.4: + version "6.7.4" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-6.7.4.tgz#9709465e161e32cf358034a82c284cc16369397a" + integrity sha512-YCAXBfdbazFIpO1EI9Czd2QYaFCJX+i9q710L3xPB4mRuaNl2SL8kH+RTj4Ur9Fyl3u1fcDI+1o/iYnaap1Uwg== dependencies: "@gorhom/bottom-sheet" "^5.1.1" dayjs "1.10.5" @@ -7558,11 +7587,11 @@ stream-chat-react-native-core@6.7.3: uid "" stream-chat@^8.57.6: - version "8.57.6" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.57.6.tgz#dd3a4a0a024ee44bfa6ae7ca15648e6c9f2e498f" - integrity sha512-+zkC5DtxvdkEBAv7dCzzO4WWosjaM15EjsH3q4xD1nX/yf2qT6BMhPJguM+CUWxwlFh32X0KR+ARtD0ChPqtnA== + version "8.60.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.60.0.tgz#b67d4fbb185da53fb8ac5fc5759986d6ad7e19a3" + integrity sha512-7FpO7Wno++r+n+x9aFuXtGYtNO06CIMd2Bxe3doYZLhMfS0nuaXloeFlGcMT0r4U/6bnguz1qQdDJUPNQAS8bQ== dependencies: - "@babel/runtime" "^7.16.3" + "@babel/runtime" "^7.27.0" "@types/jsonwebtoken" "~9.0.0" "@types/ws" "^7.4.0" axios "^1.6.0" @@ -7572,6 +7601,21 @@ stream-chat@^8.57.6: jsonwebtoken "~9.0.0" ws "^7.5.10" +stream-chat@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.0.0.tgz#cb22dcb8b7f070c623a13b6b75b212d560534d6c" + integrity sha512-I4+/DEp7dP3WBgRmqHaLswL+Y2fyQkUWJhYBS5zx4bpu1cYM6WEir9HYjToDNuJjltqa/FFIEF/tMPWr7iTc0A== + dependencies: + "@types/jsonwebtoken" "^9.0.8" + "@types/ws" "^8.5.14" + axios "^1.6.0" + base64-js "^1.5.1" + form-data "^4.0.0" + isomorphic-ws "^5.0.0" + jsonwebtoken "^9.0.2" + linkifyjs "^4.2.0" + ws "^8.18.1" + strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" @@ -8223,6 +8267,11 @@ ws@^7, ws@^7.5.10: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== +ws@^8.18.1: + version "8.18.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb" + integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w== + y18n@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" diff --git a/examples/TypeScriptMessaging/App.tsx b/examples/TypeScriptMessaging/App.tsx index 36cb7032cd..3135dd4dd7 100644 --- a/examples/TypeScriptMessaging/App.tsx +++ b/examples/TypeScriptMessaging/App.tsx @@ -27,30 +27,6 @@ import { AuthProgressLoader } from './AuthProgressLoader'; LogBox.ignoreAllLogs(true); -type LocalAttachmentType = Record; -type LocalChannelType = Record; -type LocalCommandType = string; -type LocalEventType = Record; -type LocalMessageType = Record; -type LocalReactionType = Record; -type LocalUserType = Record; -type LocalPollOptionType = Record; -type LocalPollType = Record; -type LocalMemberType = Record; - -type StreamChatGenerics = { - attachmentType: LocalAttachmentType; - channelType: LocalChannelType; - commandType: LocalCommandType; - eventType: LocalEventType; - memberType: LocalMemberType; - messageType: LocalMessageType; - pollOptionType: LocalPollOptionType; - pollType: LocalPollType; - reactionType: LocalReactionType; - userType: LocalUserType; -}; - const options = { presence: true, state: true, @@ -77,7 +53,7 @@ const filters = { type: 'messaging', }; -const sort: ChannelSort = [ +const sort: ChannelSort = [ { pinned_at: -1 }, { last_message_at: -1 }, { updated_at: -1 }, @@ -102,7 +78,7 @@ const ChannelListScreen: React.FC = ({ navigation }) => return ( - + { setChannel(channel); @@ -150,7 +126,7 @@ const ChannelScreen: React.FC = ({ navigation }) => { thread={thread} > - + { setThread(selectedThread); if (channel?.id) { @@ -200,7 +176,7 @@ const ThreadScreen: React.FC = ({ navigation }) => { justifyContent: 'flex-start', }} > - onThreadDismount={() => setThread(null)} /> + setThread(null)} /> @@ -215,12 +191,12 @@ type NavigationParamsList = ChannelRoute & ChannelListRoute & ThreadRoute; const Stack = createStackNavigator(); type AppContextType = { - channel: ChannelType | undefined; - setChannel: React.Dispatch | undefined>>; + channel: ChannelType | undefined; + setChannel: React.Dispatch>; setThread: React.Dispatch< - React.SetStateAction['thread'] | undefined> + React.SetStateAction >; - thread: ThreadContextValue['thread'] | undefined; + thread: ThreadContextValue['thread'] | undefined; }; const AppContext = React.createContext({} as AppContextType); @@ -241,7 +217,7 @@ const App = () => { } return ( - + { }; export default () => { - const [channel, setChannel] = useState>(); - const [thread, setThread] = useState['thread']>(); + const [channel, setChannel] = useState(); + const [thread, setThread] = useState(); const theme = useStreamChatTheme(); const colorScheme = useColorScheme(); diff --git a/examples/TypeScriptMessaging/custom-types.d.ts b/examples/TypeScriptMessaging/custom-types.d.ts new file mode 100644 index 0000000000..e6400ab92a --- /dev/null +++ b/examples/TypeScriptMessaging/custom-types.d.ts @@ -0,0 +1,41 @@ +import { + DefaultAttachmentData, + DefaultChannelData, + DefaultCommandData, + DefaultEventData, + DefaultMemberData, + DefaultMessageData, + DefaultPollData, + DefaultPollOptionData, + DefaultReactionData, + DefaultThreadData, + DefaultUserData, +} from 'stream-chat-react-native'; + +declare module 'stream-chat' { + /* eslint-disable @typescript-eslint/no-empty-object-type */ + + interface CustomAttachmentData extends DefaultAttachmentData {} + + interface CustomChannelData extends DefaultChannelData {} + + interface CustomCommandData extends DefaultCommandData {} + + interface CustomEventData extends DefaultEventData {} + + interface CustomMemberData extends DefaultMemberData {} + + interface CustomUserData extends DefaultUserData {} + + interface CustomMessageData extends DefaultMessageData {} + + interface CustomPollOptionData extends DefaultPollOptionData {} + + interface CustomPollData extends DefaultPollData {} + + interface CustomReactionData extends DefaultReactionData {} + + interface CustomThreadData extends DefaultThreadData {} + + /* eslint-enable @typescript-eslint/no-empty-object-type */ +} diff --git a/examples/TypeScriptMessaging/useStreamChatTheme.ts b/examples/TypeScriptMessaging/useStreamChatTheme.ts index 8c64521d70..0802c9ce30 100644 --- a/examples/TypeScriptMessaging/useStreamChatTheme.ts +++ b/examples/TypeScriptMessaging/useStreamChatTheme.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { useColorScheme } from 'react-native'; import type { DeepPartial, Theme } from 'stream-chat-react-native'; -const getChatStyle = (colorScheme: string): DeepPartial => ({ +const getChatStyle = (colorScheme: string | null | undefined): DeepPartial => ({ avatar: { image: { height: 32, diff --git a/examples/TypeScriptMessaging/yarn.lock b/examples/TypeScriptMessaging/yarn.lock index 6d93580f89..96279a2958 100644 --- a/examples/TypeScriptMessaging/yarn.lock +++ b/examples/TypeScriptMessaging/yarn.lock @@ -989,13 +989,20 @@ pirates "^4.0.6" source-map-support "^0.5.16" -"@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.25.0", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.17.2", "@babel/runtime@^7.25.0", "@babel/runtime@^7.8.4": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.9.tgz#aa4c6facc65b9cb3f87d75125ffd47781b475433" integrity sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg== dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.27.0": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762" + integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.25.0", "@babel/template@^7.25.9", "@babel/template@^7.26.9", "@babel/template@^7.3.3": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2" @@ -2047,10 +2054,10 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/jsonwebtoken@~9.0.0": - version "9.0.8" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.8.tgz#313490052801edfb031bb32b6bbd77cc9f230852" - integrity sha512-7fx54m60nLFUVYlxAB1xpe9CBWX2vSrk50Y6ogRJ1v5xxtba7qXTg5BgYDN5dq+yuQQ9HaVlHJyAAt1/mxryFg== +"@types/jsonwebtoken@^9.0.8", "@types/jsonwebtoken@~9.0.0": + version "9.0.9" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz#a4c3a446c0ebaaf467a58398382616f416345fb3" + integrity sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ== dependencies: "@types/ms" "*" "@types/node" "*" @@ -2105,6 +2112,13 @@ dependencies: "@types/node" "*" +"@types/ws@^8.5.14": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.0.tgz#8a2ec491d6f0685ceaab9a9b7ff44146236993b5" + integrity sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -4662,6 +4676,11 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +isomorphic-ws@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -5201,7 +5220,7 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -jsonwebtoken@~9.0.0: +jsonwebtoken@^9.0.2, jsonwebtoken@~9.0.0: version "9.0.2" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== @@ -5287,7 +5306,7 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -linkifyjs@^4.1.1: +linkifyjs@^4.1.1, linkifyjs@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08" integrity sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw== @@ -5689,6 +5708,11 @@ mime@^2.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== +mime@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.7.tgz#0b7a98b08c63bd3c10251e797d67840c9bde9f13" + integrity sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -6921,10 +6945,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@6.6.8: - version "6.6.8" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-6.6.8.tgz#483ade63ba051426480ab2dfd8ab3b248b90ae88" - integrity sha512-F8S70DHaiit6BEdKOkSMHq2bjMONhrouvJ+szBQuE430EJDgUlc2VErHk3yJCzqIt5lwfVZktjHuqSIOGVg5LQ== +stream-chat-react-native-core@6.7.4: + version "6.7.4" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-6.7.4.tgz#9709465e161e32cf358034a82c284cc16369397a" + integrity sha512-YCAXBfdbazFIpO1EI9Czd2QYaFCJX+i9q710L3xPB4mRuaNl2SL8kH+RTj4Ur9Fyl3u1fcDI+1o/iYnaap1Uwg== dependencies: "@gorhom/bottom-sheet" "^5.1.1" dayjs "1.10.5" @@ -6949,11 +6973,11 @@ stream-chat-react-native-core@6.6.8: uid "" stream-chat@^8.57.6: - version "8.57.6" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.57.6.tgz#dd3a4a0a024ee44bfa6ae7ca15648e6c9f2e498f" - integrity sha512-+zkC5DtxvdkEBAv7dCzzO4WWosjaM15EjsH3q4xD1nX/yf2qT6BMhPJguM+CUWxwlFh32X0KR+ARtD0ChPqtnA== + version "8.60.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.60.0.tgz#b67d4fbb185da53fb8ac5fc5759986d6ad7e19a3" + integrity sha512-7FpO7Wno++r+n+x9aFuXtGYtNO06CIMd2Bxe3doYZLhMfS0nuaXloeFlGcMT0r4U/6bnguz1qQdDJUPNQAS8bQ== dependencies: - "@babel/runtime" "^7.16.3" + "@babel/runtime" "^7.27.0" "@types/jsonwebtoken" "~9.0.0" "@types/ws" "^7.4.0" axios "^1.6.0" @@ -6963,6 +6987,21 @@ stream-chat@^8.57.6: jsonwebtoken "~9.0.0" ws "^7.5.10" +stream-chat@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.0.0.tgz#cb22dcb8b7f070c623a13b6b75b212d560534d6c" + integrity sha512-I4+/DEp7dP3WBgRmqHaLswL+Y2fyQkUWJhYBS5zx4bpu1cYM6WEir9HYjToDNuJjltqa/FFIEF/tMPWr7iTc0A== + dependencies: + "@types/jsonwebtoken" "^9.0.8" + "@types/ws" "^8.5.14" + axios "^1.6.0" + base64-js "^1.5.1" + form-data "^4.0.0" + isomorphic-ws "^5.0.0" + jsonwebtoken "^9.0.2" + linkifyjs "^4.2.0" + ws "^8.18.1" + strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" @@ -7595,6 +7634,11 @@ ws@^7, ws@^7.5.10: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== +ws@^8.18.1: + version "8.18.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb" + integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w== + y18n@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" diff --git a/package/CHANGELOG.md b/package/CHANGELOG.md index 2432911297..23f9bf9742 100644 --- a/package/CHANGELOG.md +++ b/package/CHANGELOG.md @@ -418,6 +418,7 @@ ### [5.39.3](https://github.com/GetStream/stream-chat-react-native/compare/v5.39.2...v5.39.3) (2024-10-15) + ### Bug Fixes - ChannelAvatar crash when used outside of ChannelList ([#2708](https://github.com/GetStream/stream-chat-react-native/issues/2708)) ([85f4ab2](https://github.com/GetStream/stream-chat-react-native/commit/85f4ab21adf4b0da636475f0285e3360d9b1309b)) diff --git a/package/expo-package/package.json b/package/expo-package/package.json index 9a0d1eb208..c0080f8f57 100644 --- a/package/expo-package/package.json +++ b/package/expo-package/package.json @@ -10,6 +10,7 @@ "main": "src/index.js", "types": "types/index.d.ts", "dependencies": { + "mime": "^4.0.7", "stream-chat-react-native-core": "6.7.4" }, "peerDependencies": { @@ -22,12 +23,16 @@ "expo-image-manipulator": "*", "expo-image-picker": "*", "expo-media-library": "*", - "expo-sharing": "*" + "expo-sharing": "*", + "expo-video": "*" }, "peerDependenciesMeta": { "expo-av": { "optional": true }, + "expo-video": { + "optional": true + }, "expo-clipboard": { "optional": true }, diff --git a/package/expo-package/src/optionalDependencies/AudioVideo.ts b/package/expo-package/src/optionalDependencies/AudioVideo.ts index 4505c97b69..486a47842f 100644 --- a/package/expo-package/src/optionalDependencies/AudioVideo.ts +++ b/package/expo-package/src/optionalDependencies/AudioVideo.ts @@ -1,19 +1,17 @@ -let VideoComponent; let AudioComponent; let RecordingObject; try { const audioVideoPackage = require('expo-av'); - VideoComponent = audioVideoPackage.Video; AudioComponent = audioVideoPackage.Audio; RecordingObject = audioVideoPackage.RecordingObject; } catch (e) { // do nothing } -if (!VideoComponent || !AudioComponent) { +if (!AudioComponent) { console.log( - 'Audio Video library is currently not installed. To allow in-app audio or video playback, install the "expo-av" package.', + 'Audio Video library is currently not installed. To allow in-app audio playback, install the "expo-av" package.', ); } -export { AudioComponent, VideoComponent, RecordingObject }; +export { AudioComponent, RecordingObject }; diff --git a/package/expo-package/src/optionalDependencies/Video.tsx b/package/expo-package/src/optionalDependencies/Video.tsx index ee56a3da2e..28876d528d 100644 --- a/package/expo-package/src/optionalDependencies/Video.tsx +++ b/package/expo-package/src/optionalDependencies/Video.tsx @@ -1,28 +1,71 @@ import React, { useEffect } from 'react'; -import { AudioComponent, VideoComponent } from './AudioVideo'; +import { useEventListener } from 'expo'; + +import { AudioComponent } from './AudioVideo'; + +let videoPackage; + +try { + videoPackage = require('expo-video'); +} catch (e) { + // do nothing +} + +if (!videoPackage) { + console.log( + 'The video library is currently not installed. To allow in-app video playback, install the "expo-video" package.', + ); +} + +const VideoComponent = videoPackage ? videoPackage.VideoView : null; +const useVideoPlayer = videoPackage ? videoPackage.useVideoPlayer : null; + +export const Video = videoPackage + ? ({ onLoadStart, onLoad, onEnd, onProgress, onBuffer, resizeMode, style, uri, videoRef }) => { + const player = useVideoPlayer(uri, (player) => { + player.timeUpdateEventInterval = 0.5; + videoRef.current = player; + }); + + useEventListener(player, 'statusChange', ({ status, oldStatus }) => { + if ((!oldStatus || oldStatus === 'idle') && status === 'loading') { + onLoadStart(); + } else if ((oldStatus === 'loading' || oldStatus === 'idle') && status === 'readyToPlay') { + onLoad({ duration: player.duration }); + onBuffer({ buffering: false }); + } else if (oldStatus === 'readyToPlay' && status === 'loading') { + onBuffer({ buffering: true }); + } + }); + + useEventListener(player, 'playToEnd', () => { + player.replay(); + onEnd(); + }); + + useEventListener(player, 'timeUpdate', ({ currentTime }) => + onProgress({ currentTime, seekableDuration: player.duration }), + ); -export const Video = VideoComponent - ? ({ onPlaybackStatusUpdate, paused, resizeMode, style, uri, videoRef }) => { // This is done so that the audio of the video is not muted when the phone is in silent mode for iOS. useEffect(() => { const initializeSound = async () => { - await AudioComponent.setAudioModeAsync({ - playsInSilentModeIOS: true, - }); + if (AudioComponent) { + await AudioComponent.setAudioModeAsync({ + playsInSilentModeIOS: true, + }); + } }; initializeSound(); }, []); return ( ); diff --git a/package/expo-package/src/optionalDependencies/getPhotos.ts b/package/expo-package/src/optionalDependencies/getPhotos.ts index cd858364a2..c8f244a04a 100644 --- a/package/expo-package/src/optionalDependencies/getPhotos.ts +++ b/package/expo-package/src/optionalDependencies/getPhotos.ts @@ -1,4 +1,7 @@ import { Platform } from 'react-native'; +import mime from 'mime'; + +import type { File } from 'stream-chat-react-native-core'; let MediaLibrary; @@ -13,12 +16,11 @@ if (!MediaLibrary) { 'expo-media-library is not installed. Please install it or you can choose to install expo-image-picker for native image picker.', ); } -import type { Asset } from 'stream-chat-react-native-core'; import { getLocalAssetUri } from './getLocalAssetUri'; type ReturnType = { - assets: Array & { source: 'picker' }>; + assets: File[]; endCursor: string | undefined; hasNextPage: boolean; iOSLimited: boolean; @@ -52,14 +54,13 @@ export const getPhotos = MediaLibrary const assets = await Promise.all( results.assets.map(async (asset) => { const localUri = await getLocalAssetUri(asset.id); + const mimeType = mime.getType(asset.filename); return { duration: asset.duration * 1000, height: asset.height, - id: asset.id, name: asset.filename, - originalUri: asset.uri, - source: 'picker' as const, - type: asset.mediaType, + thumb_url: asset.mediaType === 'photo' ? undefined : asset.uri, + type: mimeType, uri: localUri || asset.uri, width: asset.width, }; diff --git a/package/expo-package/src/optionalDependencies/pickDocument.ts b/package/expo-package/src/optionalDependencies/pickDocument.ts index 6c12ebe74b..5071c8d729 100644 --- a/package/expo-package/src/optionalDependencies/pickDocument.ts +++ b/package/expo-package/src/optionalDependencies/pickDocument.ts @@ -38,13 +38,21 @@ export const pickDocument = DocumentPicker // Applicable to latest version of expo-document-picker if (assets) { return { - assets, + assets: assets.map((asset) => ({ + ...asset, + type: asset.mimeType, + })), cancelled: false, }; } // Applicable to older version of expo-document-picker return { - assets: [rest], + assets: [ + { + ...rest, + type: rest.mimeType, + }, + ], cancelled: false, }; } catch (err) { diff --git a/package/expo-package/src/optionalDependencies/pickImage.ts b/package/expo-package/src/optionalDependencies/pickImage.ts index 6b3df6e6af..25ef02f531 100644 --- a/package/expo-package/src/optionalDependencies/pickImage.ts +++ b/package/expo-package/src/optionalDependencies/pickImage.ts @@ -45,11 +45,10 @@ export const pickImage = ImagePicker duration: asset.duration, name: asset.fileName, size: asset.fileSize, - source: 'picker', type: asset.mimeType, uri: asset.uri, })); - return { assets, cancelled: false, source: 'picker' }; + return { assets, cancelled: false }; } else { return { cancelled: true }; } diff --git a/package/expo-package/src/optionalDependencies/takePhoto.ts b/package/expo-package/src/optionalDependencies/takePhoto.ts index 6baec01d2a..53580644ed 100644 --- a/package/expo-package/src/optionalDependencies/takePhoto.ts +++ b/package/expo-package/src/optionalDependencies/takePhoto.ts @@ -63,7 +63,6 @@ export const takePhoto = ImagePicker duration: photo.duration, // in milliseconds name: 'video_recording_' + date + '.' + photo.uri.split('.').pop(), size: photo.fileSize, - source: 'camera', type: photo.mimeType, uri: photo.uri, }; @@ -95,10 +94,8 @@ export const takePhoto = ImagePicker const date = new Date().toISOString().replace(clearFilter, '_'); return { cancelled: false, - mimeType: photo.mimeType, name: 'image_' + date + '.' + photo.uri.split('.').pop(), size: photo.fileSize, - source: 'camera', type: photo.mimeType, uri: photo.uri, ...size, diff --git a/package/expo-package/yarn.lock b/package/expo-package/yarn.lock index 76edabf830..3b073df7e5 100644 --- a/package/expo-package/yarn.lock +++ b/package/expo-package/yarn.lock @@ -1094,13 +1094,6 @@ pirates "^4.0.6" source-map-support "^0.5.16" -"@babel/runtime@^7.16.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb" - integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ== - dependencies: - regenerator-runtime "^0.13.11" - "@babel/runtime@^7.17.2", "@babel/runtime@^7.20.0": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" @@ -1108,6 +1101,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.27.0": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762" + integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.8.4": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.9.tgz#aa4c6facc65b9cb3f87d75125ffd47781b475433" @@ -1796,12 +1796,18 @@ integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== "@types/jsonwebtoken@~9.0.0": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#9eeb56c76dd555039be2a3972218de5bd3b8d83e" - integrity sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q== + version "9.0.9" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz#a4c3a446c0ebaaf467a58398382616f416345fb3" + integrity sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ== dependencies: + "@types/ms" "*" "@types/node" "*" +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" @@ -3462,14 +3468,20 @@ jsonfile@^6.0.1: graceful-fs "^4.1.6" jsonwebtoken@~9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" - integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw== + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== dependencies: jws "^3.2.2" - lodash "^4.17.21" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" ms "^2.1.1" - semver "^7.3.8" + semver "^7.5.4" jwa@^1.4.1: version "1.4.1" @@ -3609,6 +3621,41 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash@^4.17.15, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -3719,6 +3766,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.7.tgz#0b7a98b08c63bd3c10251e797d67840c9bde9f13" + integrity sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -4343,11 +4395,6 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - regenerator-runtime@^0.14.0: version "0.14.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" @@ -4532,7 +4579,7 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.8: +semver@^7.3.5: version "7.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== @@ -4733,10 +4780,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@6.7.3: - version "6.7.3" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-6.7.3.tgz#7e213c03a7414c85ec4ba9da247d9703878ab520" - integrity sha512-dYoYkVah7l4bmkI0vl7Ma6qD+xjsmeFWOdz7EVwbtR+B3HPMsgz8eRAmBj/p2xcSANxh1bPRD0iLlvD30vXyzg== +stream-chat-react-native-core@6.7.4: + version "6.7.4" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-6.7.4.tgz#9709465e161e32cf358034a82c284cc16369397a" + integrity sha512-YCAXBfdbazFIpO1EI9Czd2QYaFCJX+i9q710L3xPB4mRuaNl2SL8kH+RTj4Ur9Fyl3u1fcDI+1o/iYnaap1Uwg== dependencies: "@gorhom/bottom-sheet" "^5.1.1" dayjs "1.10.5" @@ -4753,11 +4800,11 @@ stream-chat-react-native-core@6.7.3: use-sync-external-store "^1.4.0" stream-chat@^8.57.6: - version "8.57.6" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.57.6.tgz#dd3a4a0a024ee44bfa6ae7ca15648e6c9f2e498f" - integrity sha512-+zkC5DtxvdkEBAv7dCzzO4WWosjaM15EjsH3q4xD1nX/yf2qT6BMhPJguM+CUWxwlFh32X0KR+ARtD0ChPqtnA== + version "8.60.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.60.0.tgz#b67d4fbb185da53fb8ac5fc5759986d6ad7e19a3" + integrity sha512-7FpO7Wno++r+n+x9aFuXtGYtNO06CIMd2Bxe3doYZLhMfS0nuaXloeFlGcMT0r4U/6bnguz1qQdDJUPNQAS8bQ== dependencies: - "@babel/runtime" "^7.16.3" + "@babel/runtime" "^7.27.0" "@types/jsonwebtoken" "~9.0.0" "@types/ws" "^7.4.0" axios "^1.6.0" diff --git a/package/native-package/package.json b/package/native-package/package.json index dd67073fa9..4ad8f4e23f 100644 --- a/package/native-package/package.json +++ b/package/native-package/package.json @@ -20,17 +20,17 @@ "types": "types/index.d.ts", "dependencies": { "es6-symbol": "^3.1.3", + "mime": "^4.0.7", "stream-chat-react-native-core": "6.7.4" }, "peerDependencies": { "@react-native-camera-roll/camera-roll": ">=7.8.0", "@react-native-clipboard/clipboard": ">=1.14.1", + "@react-native-documents/picker": ">=10.1.1", "@stream-io/flat-list-mvcp": ">=0.10.3", - "react-native": ">=0.67.0", + "react-native": ">=0.71.0", "react-native-audio-recorder-player": ">=3.6.4", "react-native-blob-util": ">=0.19.9", - "react-native-document-picker": ">=9.3.0", - "@react-native-documents/picker": ">=10.1.1", "react-native-haptic-feedback": ">=2.2.0", "react-native-image-picker": ">=7.1.2", "react-native-share": ">=10.2.1", @@ -49,9 +49,6 @@ "react-native-share": { "optional": true }, - "react-native-document-picker": { - "optional": true - }, "@react-native-documents/picker": { "optional": true }, diff --git a/package/native-package/src/optionalDependencies/getPhotos.ts b/package/native-package/src/optionalDependencies/getPhotos.ts index bfbf051d37..0b1c0d22c9 100644 --- a/package/native-package/src/optionalDependencies/getPhotos.ts +++ b/package/native-package/src/optionalDependencies/getPhotos.ts @@ -1,4 +1,7 @@ import { PermissionsAndroid, Platform } from 'react-native'; +import mime from 'mime'; + +import type { File } from 'stream-chat-react-native-core'; let CameraRollDependency; @@ -11,12 +14,10 @@ try { ); } -import type { Asset } from 'stream-chat-react-native-core'; - import { getLocalAssetUri } from './getLocalAssetUri'; type ReturnType = { - assets: Array & { source: 'picker' }>; + assets: File[]; endCursor: string | undefined; hasNextPage: boolean; iOSLimited: boolean; @@ -90,16 +91,22 @@ export const getPhotos = CameraRollDependency const assets = await Promise.all( results.edges.map(async (edge) => { const originalUri = edge.node?.image?.uri; - const uri = getLocalAssetUri ? await getLocalAssetUri(originalUri) : originalUri; + const type = + Platform.OS === 'ios' + ? mime.getType(edge.node.image.filename as string) + : edge.node.type; + const isImage = type.includes('image'); + + const uri = + isImage && getLocalAssetUri ? await getLocalAssetUri(originalUri) : originalUri; + return { ...edge.node.image, - duration: edge.node.image.playableDuration * 1000, - // since we include filename, fileSize in the query, we can safely assume it will be defined name: edge.node.image.filename as string, - originalUri, + duration: edge.node.image.playableDuration * 1000, + thumb_url: isImage ? undefined : originalUri, size: edge.node.image.fileSize as number, - source: 'picker' as const, - type: edge.node.type, + type, uri, }; }), diff --git a/package/native-package/src/optionalDependencies/pickDocument.ts b/package/native-package/src/optionalDependencies/pickDocument.ts index a0bab9279b..42aebe01bb 100644 --- a/package/native-package/src/optionalDependencies/pickDocument.ts +++ b/package/native-package/src/optionalDependencies/pickDocument.ts @@ -1,7 +1,7 @@ /** * Types are approximated from what we need from the DocumentPicker API. * - * For its full API, see https://github.com/rnmods/react-native-document-picker/blob/master/src/index.tsx + * For its full API, see https://github.com/react-native-documents/document-picker/blob/main/packages/document-picker/src/index.ts * */ type ResponseValue = { name: string; @@ -19,31 +19,12 @@ type DocumentPickerType = let DocumentPicker: DocumentPickerType; -let OldDocumentPicker: DocumentPickerType; -let NewDocumentPicker: DocumentPickerType; - -try { - NewDocumentPicker = require('@react-native-documents/picker'); -} catch (err) { - // we log below -} - try { - OldDocumentPicker = require('react-native-document-picker').default; + DocumentPicker = require('@react-native-documents/picker'); } catch (err) { - // we log below -} - -if (NewDocumentPicker) { - DocumentPicker = NewDocumentPicker; -} else if (OldDocumentPicker) { - DocumentPicker = OldDocumentPicker; - console.log( - "You're using the react-native-document-picker library, which is no longer supported and has moved to @react-native-documents/picker. Things might not work as intended. Please migrate to the new library as soon as possible !", - ); -} else { + // do nothing console.log( - 'Neither react-native-document-picker nor @react-native-documents/picker are installed.', + 'The @react-native-documents/picker is not installed. Installing it will enable you to pick and upload files from within your app.', ); } @@ -62,9 +43,9 @@ export const pickDocument = DocumentPicker return { assets: res.map(({ name, size, type, uri }) => ({ - mimeType: type, name, size, + type, uri, })), cancelled: false, diff --git a/package/native-package/src/optionalDependencies/pickImage.ts b/package/native-package/src/optionalDependencies/pickImage.ts index 770319b285..1d04e68753 100644 --- a/package/native-package/src/optionalDependencies/pickImage.ts +++ b/package/native-package/src/optionalDependencies/pickImage.ts @@ -26,11 +26,10 @@ export const pickImage = ImagePicker duration: asset.duration ? asset.duration * 1000 : undefined, // in milliseconds name: asset.fileName, size: asset.fileSize, - source: 'picker', type: asset.type, uri: asset.uri, })); - return { assets, cancelled: false, source: 'picker' }; + return { assets, cancelled: false }; } else { return { cancelled: true }; } diff --git a/package/native-package/src/optionalDependencies/takePhoto.ts b/package/native-package/src/optionalDependencies/takePhoto.ts index 477841ddc1..914dec735b 100644 --- a/package/native-package/src/optionalDependencies/takePhoto.ts +++ b/package/native-package/src/optionalDependencies/takePhoto.ts @@ -53,10 +53,8 @@ export const takePhoto = ImagePicker ...asset, cancelled: false, duration: asset.duration * 1000, - mimeType: asset.type, name: 'video_recording_' + date + '.' + asset.fileName.split('.').pop(), size: asset.fileSize, - source: 'camera', type: asset.type, uri: asset.uri, }; @@ -90,10 +88,8 @@ export const takePhoto = ImagePicker const date = new Date().toISOString().replace(clearFilter, '_'); return { cancelled: false, - mimeType: asset.type, name: 'video_recording_' + date + '.' + asset.fileName.split('.').pop(), size: asset.size, - source: 'camera', type: asset.type, uri: asset.uri, ...size, diff --git a/package/native-package/yarn.lock b/package/native-package/yarn.lock index 42037de3b3..91c31ab4ef 100644 --- a/package/native-package/yarn.lock +++ b/package/native-package/yarn.lock @@ -759,13 +759,6 @@ pirates "^4.0.6" source-map-support "^0.5.16" -"@babel/runtime@^7.16.3": - version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb" - integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ== - dependencies: - regenerator-runtime "^0.13.11" - "@babel/runtime@^7.17.2": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" @@ -780,6 +773,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.27.0": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762" + integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.8.4": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" @@ -1253,12 +1253,18 @@ "@types/istanbul-lib-report" "*" "@types/jsonwebtoken@~9.0.0": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#9eeb56c76dd555039be2a3972218de5bd3b8d83e" - integrity sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q== + version "9.0.9" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz#a4c3a446c0ebaaf467a58398382616f416345fb3" + integrity sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ== dependencies: + "@types/ms" "*" "@types/node" "*" +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" @@ -2422,14 +2428,20 @@ json5@^2.2.3: integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== jsonwebtoken@~9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d" - integrity sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw== + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== dependencies: jws "^3.2.2" - lodash "^4.17.21" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" ms "^2.1.1" - semver "^7.3.8" + semver "^7.5.4" jwa@^1.4.1: version "1.4.1" @@ -2496,12 +2508,47 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== -lodash@^4.17.15, lodash@^4.17.21: +lodash@^4.17.15: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -2520,13 +2567,6 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -2782,6 +2822,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.7.tgz#0b7a98b08c63bd3c10251e797d67840c9bde9f13" + integrity sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ== + minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -3167,7 +3212,7 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.2: +regenerator-runtime@^0.13.2: version "0.13.11" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== @@ -3277,12 +3322,10 @@ semver@^7.1.3: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== -semver@^7.3.8: - version "7.5.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" - integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== - dependencies: - lru-cache "^6.0.0" +semver@^7.5.4: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== send@0.19.0: version "0.19.0" @@ -3409,10 +3452,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@6.7.3: - version "6.7.3" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-6.7.3.tgz#7e213c03a7414c85ec4ba9da247d9703878ab520" - integrity sha512-dYoYkVah7l4bmkI0vl7Ma6qD+xjsmeFWOdz7EVwbtR+B3HPMsgz8eRAmBj/p2xcSANxh1bPRD0iLlvD30vXyzg== +stream-chat-react-native-core@6.7.4: + version "6.7.4" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-6.7.4.tgz#9709465e161e32cf358034a82c284cc16369397a" + integrity sha512-YCAXBfdbazFIpO1EI9Czd2QYaFCJX+i9q710L3xPB4mRuaNl2SL8kH+RTj4Ur9Fyl3u1fcDI+1o/iYnaap1Uwg== dependencies: "@gorhom/bottom-sheet" "^5.1.1" dayjs "1.10.5" @@ -3429,11 +3472,11 @@ stream-chat-react-native-core@6.7.3: use-sync-external-store "^1.4.0" stream-chat@^8.57.6: - version "8.57.6" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.57.6.tgz#dd3a4a0a024ee44bfa6ae7ca15648e6c9f2e498f" - integrity sha512-+zkC5DtxvdkEBAv7dCzzO4WWosjaM15EjsH3q4xD1nX/yf2qT6BMhPJguM+CUWxwlFh32X0KR+ARtD0ChPqtnA== + version "8.60.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.60.0.tgz#b67d4fbb185da53fb8ac5fc5759986d6ad7e19a3" + integrity sha512-7FpO7Wno++r+n+x9aFuXtGYtNO06CIMd2Bxe3doYZLhMfS0nuaXloeFlGcMT0r4U/6bnguz1qQdDJUPNQAS8bQ== dependencies: - "@babel/runtime" "^7.16.3" + "@babel/runtime" "^7.27.0" "@types/jsonwebtoken" "~9.0.0" "@types/ws" "^7.4.0" axios "^1.6.0" @@ -3707,11 +3750,6 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" diff --git a/package/package.json b/package/package.json index e19c9d442a..fb0c16ef08 100644 --- a/package/package.json +++ b/package/package.json @@ -77,16 +77,16 @@ "path": "0.12.7", "react-native-markdown-package": "1.8.2", "react-native-url-polyfill": "^1.3.0", - "stream-chat": "^8.57.6", + "stream-chat": "^9.0.0", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@op-engineering/op-sqlite": ">=9.3.0", "@react-native-community/netinfo": ">=11.3.1", - "react-native": ">=0.67.0", + "react-native": ">=0.71.0", "react-native-gesture-handler": ">=2.16.1", "react-native-reanimated": ">=3.16.0", - "react-native-svg": ">=12.3.0" + "react-native-svg": ">=13.6.0" }, "peerDependenciesMeta": { "@op-engineering/op-sqlite": { diff --git a/package/src/components/AITypingIndicatorView/AITypingIndicatorView.tsx b/package/src/components/AITypingIndicatorView/AITypingIndicatorView.tsx index 1709b8fcad..5082512924 100644 --- a/package/src/components/AITypingIndicatorView/AITypingIndicatorView.tsx +++ b/package/src/components/AITypingIndicatorView/AITypingIndicatorView.tsx @@ -7,21 +7,16 @@ import { Channel } from 'stream-chat'; import { AIStates, useAIState } from './hooks/useAIState'; import { useChannelContext, useTheme, useTranslationContext } from '../../contexts'; -import type { DefaultStreamChatGenerics } from '../../types/types'; -export type AITypingIndicatorViewProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - channel?: Channel; +export type AITypingIndicatorViewProps = { + channel?: Channel; }; -export const AITypingIndicatorView = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const AITypingIndicatorView = ({ channel: channelFromProps, -}: AITypingIndicatorViewProps) => { +}: AITypingIndicatorViewProps) => { const { t } = useTranslationContext(); - const { channel: channelFromContext } = useChannelContext(); + const { channel: channelFromContext } = useChannelContext(); const channel = channelFromProps || channelFromContext; const { aiState } = useAIState(channel); const allowedStates = { diff --git a/package/src/components/AITypingIndicatorView/hooks/useAIState.ts b/package/src/components/AITypingIndicatorView/hooks/useAIState.ts index 2c7f9c4258..de0ad7ae93 100644 --- a/package/src/components/AITypingIndicatorView/hooks/useAIState.ts +++ b/package/src/components/AITypingIndicatorView/hooks/useAIState.ts @@ -3,7 +3,6 @@ import { useEffect, useState } from 'react'; import { AIState, Channel, Event } from 'stream-chat'; import { useChatContext } from '../../../contexts'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; export const AIStates = { Error: 'AI_STATE_ERROR', @@ -18,12 +17,8 @@ export const AIStates = { * @param {Channel} channel - The channel for which we want to know the AI state. * @returns {{ aiState: AIState }} The current AI state for the given channel. */ -export const useAIState = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel?: Channel, -): { aiState: AIState } => { - const { isOnline } = useChatContext(); +export const useAIState = (channel?: Channel): { aiState: AIState } => { + const { isOnline } = useChatContext(); const [aiState, setAiState] = useState(AIStates.Idle); @@ -38,16 +33,13 @@ export const useAIState = < return; } - const indicatorChangedListener = channel.on( - 'ai_indicator.update', - (event: Event) => { - const { cid } = event; - const state = event.ai_state as AIState; - if (channel.cid === cid) { - setAiState(state); - } - }, - ); + const indicatorChangedListener = channel.on('ai_indicator.update', (event: Event) => { + const { cid } = event; + const state = event.ai_state as AIState; + if (channel.cid === cid) { + setAiState(state); + } + }); const indicatorClearedListener = channel.on('ai_indicator.clear', (event) => { const { cid } = event; diff --git a/package/src/components/Attachment/Attachment.tsx b/package/src/components/Attachment/Attachment.tsx index a7894b55f3..07ab1d6cda 100644 --- a/package/src/components/Attachment/Attachment.tsx +++ b/package/src/components/Attachment/Attachment.tsx @@ -13,14 +13,12 @@ import { } from '../../contexts/messagesContext/MessagesContext'; import { isVideoPlayerAvailable } from '../../native'; -import { DefaultStreamChatGenerics, FileTypes } from '../../types/types'; +import { FileTypes } from '../../types/types'; export type ActionHandler = (name: string, value: string) => void; -export type AttachmentPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - MessagesContextValue, +export type AttachmentPropsWithContext = Pick< + MessagesContextValue, | 'AttachmentActions' | 'Card' | 'FileAttachment' @@ -34,14 +32,10 @@ export type AttachmentPropsWithContext< /** * The attachment to render */ - attachment: AttachmentType; + attachment: AttachmentType; }; -const AttachmentWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AttachmentPropsWithContext, -) => { +const AttachmentWithContext = (props: AttachmentPropsWithContext) => { const { attachment, AttachmentActions, @@ -68,7 +62,7 @@ const AttachmentWithContext = < <> {hasAttachmentActions && ( - + )} ); @@ -79,7 +73,7 @@ const AttachmentWithContext = < <> {hasAttachmentActions && ( - + )} ) : ( @@ -99,7 +93,8 @@ const AttachmentWithContext = < return ( <> - + {/** TODO: Please rethink this, the fix is temporary. */} + ); } else { @@ -107,10 +102,7 @@ const AttachmentWithContext = < } }; -const areEqual = ( - prevProps: AttachmentPropsWithContext, - nextProps: AttachmentPropsWithContext, -) => { +const areEqual = (prevProps: AttachmentPropsWithContext, nextProps: AttachmentPropsWithContext) => { const { attachment: prevAttachment, isAttachmentEqual, @@ -145,11 +137,9 @@ const MemoizedAttachment = React.memo( areEqual, ) as typeof AttachmentWithContext; -export type AttachmentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial< +export type AttachmentProps = Partial< Pick< - MessagesContextValue, + MessagesContextValue, | 'AttachmentActions' | 'Card' | 'FileAttachment' @@ -161,16 +151,12 @@ export type AttachmentProps< | 'isAttachmentEqual' > > & - Pick, 'attachment'>; + Pick; /** * Attachment - The message attachment */ -export const Attachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AttachmentProps, -) => { +export const Attachment = (props: AttachmentProps) => { const { attachment, AttachmentActions: PropAttachmentActions, @@ -193,7 +179,7 @@ export const Attachment = < isAttachmentEqual, myMessageTheme: ContextMyMessageTheme, UrlPreview: ContextUrlPreview, - } = useMessagesContext(); + } = useMessagesContext(); if (!attachment) { return null; diff --git a/package/src/components/Attachment/AttachmentActions.tsx b/package/src/components/Attachment/AttachmentActions.tsx index f594d5375e..a860772f15 100644 --- a/package/src/components/Attachment/AttachmentActions.tsx +++ b/package/src/components/Attachment/AttachmentActions.tsx @@ -17,8 +17,6 @@ import { } from '../../contexts/messageContext/MessageContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - const styles = StyleSheet.create({ actionButton: { borderRadius: 20, @@ -33,10 +31,8 @@ const styles = StyleSheet.create({ }, }); -export type AttachmentActionsPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'actions'> & - Pick, 'handleAction'> & { +export type AttachmentActionsPropsWithContext = Pick & + Pick & { styles?: Partial<{ actionButton: StyleProp; buttonText: StyleProp; @@ -44,11 +40,7 @@ export type AttachmentActionsPropsWithContext< }>; }; -const AttachmentActionsWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AttachmentActionsPropsWithContext, -) => { +const AttachmentActionsWithContext = (props: AttachmentActionsPropsWithContext) => { const { actions, handleAction, styles: stylesProp = {} } = props; const { @@ -116,9 +108,9 @@ const AttachmentActionsWithContext = < ); }; -const areEqual = ( - prevProps: AttachmentActionsPropsWithContext, - nextProps: AttachmentActionsPropsWithContext, +const areEqual = ( + prevProps: AttachmentActionsPropsWithContext, + nextProps: AttachmentActionsPropsWithContext, ) => { const { actions: prevActions } = prevProps; const { actions: nextActions } = nextProps; @@ -133,21 +125,15 @@ const MemoizedAttachmentActions = React.memo( areEqual, ) as typeof AttachmentActionsWithContext; -export type AttachmentActionsProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Attachment & - Partial, 'handleAction'>>; +export type AttachmentActionsProps = Attachment & + Partial>; /** * AttachmentActions - The actions you can take on an attachment. * Actions in combination with attachments can be used to build [commands](https://getstream.io/chat/docs/#channel_commands). */ -export const AttachmentActions = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AttachmentActionsProps, -) => { - const { handleAction } = useMessageContext(); +export const AttachmentActions = (props: AttachmentActionsProps) => { + const { handleAction } = useMessageContext(); return ; }; diff --git a/package/src/components/Attachment/AudioAttachment.tsx b/package/src/components/Attachment/AudioAttachment.tsx index ad94d4a3c9..bd051c4434 100644 --- a/package/src/components/Attachment/AudioAttachment.tsx +++ b/package/src/components/Attachment/AudioAttachment.tsx @@ -15,7 +15,7 @@ import { VideoProgressData, VideoSeekResponse, } from '../../native'; -import { FileTypes, type FileUpload } from '../../types/types'; +import { AudioUpload, FileTypes } from '../../types/types'; import { getTrimmedAttachmentTitle } from '../../utils/getTrimmedAttachmentTitle'; import { ProgressControl } from '../ProgressControl/ProgressControl'; import { WaveProgressBar } from '../ProgressControl/WaveProgressBar'; @@ -23,7 +23,7 @@ import { WaveProgressBar } from '../ProgressControl/WaveProgressBar'; dayjs.extend(duration); export type AudioAttachmentProps = { - item: Omit; + item: Omit; onLoad: (index: string, duration: number) => void; onPlayPause: (index: string, pausedStatus?: boolean) => void; onProgress: (index: string, progress: number) => void; diff --git a/package/src/components/Attachment/Card.tsx b/package/src/components/Attachment/Card.tsx index 09e9bca733..ed8582bc9d 100644 --- a/package/src/components/Attachment/Card.tsx +++ b/package/src/components/Attachment/Card.tsx @@ -27,7 +27,7 @@ import { } from '../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { Play } from '../../icons/Play'; -import { DefaultStreamChatGenerics, FileTypes } from '../../types/types'; +import { FileTypes } from '../../types/types'; import { makeImageCompatibleUrl } from '../../utils/utils'; import { ImageBackground } from '../UIComponents/ImageBackground'; @@ -82,16 +82,11 @@ const styles = StyleSheet.create({ }, }); -export type CardPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Attachment & +export type CardPropsWithContext = Attachment & Pick & + Pick & Pick< - MessageContextValue, - 'onLongPress' | 'onPress' | 'onPressIn' | 'preventPress' - > & - Pick< - MessagesContextValue, + MessagesContextValue, 'additionalPressableProps' | 'CardCover' | 'CardFooter' | 'CardHeader' | 'myMessageTheme' > & { channelId: string | undefined; @@ -110,11 +105,7 @@ export type CardPropsWithContext< }>; }; -const CardWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: CardPropsWithContext, -) => { +const CardWithContext = (props: CardPropsWithContext) => { const { additionalPressableProps, author_name, @@ -289,10 +280,7 @@ const CardWithContext = < ); }; -const areEqual = ( - prevProps: CardPropsWithContext, - nextProps: CardPropsWithContext, -) => { +const areEqual = (prevProps: CardPropsWithContext, nextProps: CardPropsWithContext) => { const { myMessageTheme: prevMyMessageTheme } = prevProps; const { myMessageTheme: nextMyMessageTheme } = nextProps; @@ -307,17 +295,12 @@ const areEqual = = Attachment & +export type CardProps = Attachment & Partial< - Pick, 'ImageComponent'> & - Pick< - MessageContextValue, - 'onLongPress' | 'onPress' | 'onPressIn' | 'myMessageTheme' - > & + Pick & + Pick & Pick< - MessagesContextValue, + MessagesContextValue, 'additionalPressableProps' | 'CardCover' | 'CardFooter' | 'CardHeader' > >; @@ -325,16 +308,11 @@ export type CardProps< /** * UI component for card in attachments. */ -export const Card = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: CardProps, -) => { - const { ImageComponent } = useChatContext(); - const { message, onLongPress, onPress, onPressIn, preventPress } = - useMessageContext(); +export const Card = (props: CardProps) => { + const { ImageComponent } = useChatContext(); + const { message, onLongPress, onPress, onPressIn, preventPress } = useMessageContext(); const { additionalPressableProps, CardCover, CardFooter, CardHeader, myMessageTheme } = - useMessagesContext(); + useMessagesContext(); return ( = Pick< - MessageContextValue, +export type FileAttachmentPropsWithContext = Pick< + MessageContextValue, 'onLongPress' | 'onPress' | 'onPressIn' | 'preventPress' > & Pick< - MessagesContextValue, + MessagesContextValue, 'additionalPressableProps' | 'AttachmentActions' | 'FileAttachmentIcon' > & { /** The attachment to render */ - attachment: Attachment; + attachment: Attachment; attachmentSize?: number; styles?: Partial<{ container: StyleProp; @@ -59,11 +56,7 @@ export type FileAttachmentPropsWithContext< }>; }; -const FileAttachmentWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: FileAttachmentPropsWithContext, -) => { +const FileAttachmentWithContext = (props: FileAttachmentPropsWithContext) => { const { additionalPressableProps, attachment, @@ -149,22 +142,16 @@ const FileAttachmentWithContext = < ); }; -export type FileAttachmentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial, 'attachment'>> & - Pick, 'attachment'>; - -export const FileAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: FileAttachmentProps, -) => { - const { onLongPress, onPress, onPressIn, preventPress } = useMessageContext(); +export type FileAttachmentProps = Partial> & + Pick; + +export const FileAttachment = (props: FileAttachmentProps) => { + const { onLongPress, onPress, onPressIn, preventPress } = useMessageContext(); const { additionalPressableProps, AttachmentActions = AttachmentActionsDefault, FileAttachmentIcon = FileIconDefault, - } = useMessagesContext(); + } = useMessagesContext(); return ( = Pick, 'files'> & - Pick, 'Attachment' | 'AudioAttachment'> & { +export type FileAttachmentGroupPropsWithContext = Pick & + Pick & { /** * The unique id for the message with file attachments */ @@ -33,19 +31,13 @@ export type FileAttachmentGroupPropsWithContext< }>; }; -type FilesToDisplayType< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Attachment & { +type FilesToDisplayType = Attachment & { duration: number; paused: boolean; progress: number; }; -const FileAttachmentGroupWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: FileAttachmentGroupPropsWithContext, -) => { +const FileAttachmentGroupWithContext = (props: FileAttachmentGroupPropsWithContext) => { const { Attachment, AudioAttachment, files, messageId, styles: stylesProp = {} } = props; const [filesToDisplay, setFilesToDisplay] = useState([]); @@ -108,7 +100,7 @@ const FileAttachmentGroupWithContext = < {filesToDisplay.map((file, index) => ( ( - prevProps: FileAttachmentGroupPropsWithContext, - nextProps: FileAttachmentGroupPropsWithContext, +const areEqual = ( + prevProps: FileAttachmentGroupPropsWithContext, + nextProps: FileAttachmentGroupPropsWithContext, ) => { const { files: prevFiles } = prevProps; const { files: nextFiles } = nextProps; @@ -159,22 +153,17 @@ const MemoizedFileAttachmentGroup = React.memo( areEqual, ) as typeof FileAttachmentGroupWithContext; -export type FileAttachmentGroupProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial, 'messageId'>> & - Pick, 'messageId'>; +export type FileAttachmentGroupProps = Partial< + Omit +> & + Pick; -export const FileAttachmentGroup = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: FileAttachmentGroupProps, -) => { +export const FileAttachmentGroup = (props: FileAttachmentGroupProps) => { const { files: propFiles, messageId } = props; - const { files: contextFiles } = useMessageContext(); + const { files: contextFiles } = useMessageContext(); - const { Attachment = AttachmentDefault, AudioAttachment } = - useMessagesContext(); + const { Attachment = AttachmentDefault, AudioAttachment } = useMessagesContext(); const files = propFiles || contextFiles; diff --git a/package/src/components/Attachment/Gallery.tsx b/package/src/components/Attachment/Gallery.tsx index c2ecb55930..e3a8031b87 100644 --- a/package/src/components/Attachment/Gallery.tsx +++ b/package/src/components/Attachment/Gallery.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { Pressable, StyleSheet, Text, View } from 'react-native'; -import type { Attachment } from 'stream-chat'; +import type { Attachment, LocalMessage } from 'stream-chat'; import { GalleryImage } from './GalleryImage'; import { buildGallery } from './utils/buildGallery/buildGallery'; @@ -11,7 +11,6 @@ import { getGalleryImageBorderRadius } from './utils/getGalleryImageBorderRadius import { openUrlSafely } from './utils/openUrlSafely'; -import type { MessageType } from '../../components/MessageList/hooks/useMessageList'; import { useTranslationContext } from '../../contexts'; import { useChatConfigContext } from '../../contexts/chatConfigContext/ChatConfigContext'; import { @@ -33,14 +32,15 @@ import { import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useLoadingImage } from '../../hooks/useLoadingImage'; import { isVideoPlayerAvailable } from '../../native'; -import { DefaultStreamChatGenerics, FileTypes } from '../../types/types'; +import { FileTypes } from '../../types/types'; import { getUrlWithoutParams } from '../../utils/utils'; -export type GalleryPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'setSelectedMessage' | 'setMessages'> & +export type GalleryPropsWithContext = Pick< + ImageGalleryContextValue, + 'setSelectedMessage' | 'setMessages' +> & Pick< - MessageContextValue, + MessageContextValue, | 'alignment' | 'groupStyles' | 'images' @@ -52,7 +52,7 @@ export type GalleryPropsWithContext< | 'threadList' > & Pick< - MessagesContextValue, + MessagesContextValue, | 'additionalPressableProps' | 'legacyImageViewerSwipeBehaviour' | 'VideoThumbnail' @@ -76,14 +76,10 @@ export type GalleryPropsWithContext< * * TODO: Fix circular dependencies of imports */ - message?: MessageType; + message?: LocalMessage; }; -const GalleryWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: GalleryPropsWithContext, -) => { +const GalleryWithContext = (props: GalleryPropsWithContext) => { const { additionalPressableProps, alignment, @@ -237,9 +233,7 @@ const GalleryWithContext = < ); }; -type GalleryThumbnailProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +type GalleryThumbnailProps = { borderRadius: { borderBottomLeftRadius: number; borderBottomRightRadius: number; @@ -247,15 +241,15 @@ type GalleryThumbnailProps< borderTopRightRadius: number; }; colIndex: number; - imagesAndVideos: Attachment[]; + imagesAndVideos: Attachment[]; invertedDirections: boolean; - message: MessageType; + message: LocalMessage; numOfColumns: number; numOfRows: number; rowIndex: number; thumbnail: Thumbnail; } & Pick< - MessagesContextValue, + MessagesContextValue, | 'additionalPressableProps' | 'legacyImageViewerSwipeBehaviour' | 'VideoThumbnail' @@ -263,16 +257,11 @@ type GalleryThumbnailProps< | 'ImageLoadingFailedIndicator' | 'ImageReloadIndicator' > & - Pick, 'setSelectedMessage' | 'setMessages'> & - Pick< - MessageContextValue, - 'onLongPress' | 'onPress' | 'onPressIn' | 'preventPress' - > & + Pick & + Pick & Pick; -const GalleryThumbnail = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +const GalleryThumbnail = ({ additionalPressableProps, borderRadius, colIndex, @@ -295,7 +284,7 @@ const GalleryThumbnail = < setSelectedMessage, thumbnail, VideoThumbnail, -}: GalleryThumbnailProps) => { +}: GalleryThumbnailProps) => { const { theme: { colors: { overlay }, @@ -426,16 +415,14 @@ const GalleryThumbnail = < ); }; -const GalleryImageThumbnail = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +const GalleryImageThumbnail = ({ borderRadius, ImageLoadingFailedIndicator, ImageLoadingIndicator, ImageReloadIndicator, thumbnail, }: Pick< - GalleryThumbnailProps, + GalleryThumbnailProps, | 'ImageLoadingFailedIndicator' | 'ImageLoadingIndicator' | 'ImageReloadIndicator' @@ -506,10 +493,7 @@ const GalleryImageThumbnail = < ); }; -const areEqual = ( - prevProps: GalleryPropsWithContext, - nextProps: GalleryPropsWithContext, -) => { +const areEqual = (prevProps: GalleryPropsWithContext, nextProps: GalleryPropsWithContext) => { const { groupStyles: prevGroupStyles, hasThreadReplies: prevHasThreadReplies, @@ -578,18 +562,12 @@ const areEqual = = Partial>; +export type GalleryProps = Partial; /** * UI component for card in attachments. */ -export const Gallery = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: GalleryProps, -) => { +export const Gallery = (props: GalleryProps) => { const { additionalPressableProps: propAdditionalPressableProps, alignment: propAlignment, @@ -612,8 +590,7 @@ export const Gallery = < VideoThumbnail: PropVideoThumbnail, } = props; - const { setMessages, setSelectedMessage: contextSetSelectedMessage } = - useImageGalleryContext(); + const { setMessages, setSelectedMessage: contextSetSelectedMessage } = useImageGalleryContext(); const { alignment: contextAlignment, groupStyles: contextGroupStyles, @@ -625,7 +602,7 @@ export const Gallery = < preventPress: contextPreventPress, threadList: contextThreadList, videos: contextVideos, - } = useMessageContext(); + } = useMessageContext(); const { additionalPressableProps: contextAdditionalPressableProps, ImageLoadingFailedIndicator: ContextImageLoadingFailedIndicator, @@ -634,7 +611,7 @@ export const Gallery = < legacyImageViewerSwipeBehaviour, myMessageTheme: contextMyMessageTheme, VideoThumbnail: ContextVideoThumnbnail, - } = useMessagesContext(); + } = useMessagesContext(); const { setOverlay: contextSetOverlay } = useOverlayContext(); const images = propImages || contextImages; diff --git a/package/src/components/Attachment/GalleryImage.tsx b/package/src/components/Attachment/GalleryImage.tsx index 44fc81f7a2..75328eece0 100644 --- a/package/src/components/Attachment/GalleryImage.tsx +++ b/package/src/components/Attachment/GalleryImage.tsx @@ -3,12 +3,10 @@ import { Image, ImageProps } from 'react-native'; import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { getUrlWithoutParams, isLocalUrl, makeImageCompatibleUrl } from '../../utils/utils'; -export type GalleryImageWithContextProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = GalleryImageProps & Pick, 'ImageComponent'>; +export type GalleryImageWithContextProps = GalleryImageProps & + Pick; export const GalleryImageWithContext = (props: GalleryImageWithContextProps) => { const { ImageComponent = Image, uri, ...rest } = props; @@ -47,12 +45,8 @@ export type GalleryImageProps = Omit & { uri: string; }; -export const GalleryImage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: GalleryImageProps, -) => { - const { ImageComponent } = useChatContext(); +export const GalleryImage = (props: GalleryImageProps) => { + const { ImageComponent } = useChatContext(); return ; }; diff --git a/package/src/components/Attachment/Giphy.tsx b/package/src/components/Attachment/Giphy.tsx index 29eda46dce..cc60590399 100644 --- a/package/src/components/Attachment/Giphy.tsx +++ b/package/src/components/Attachment/Giphy.tsx @@ -24,7 +24,7 @@ import { import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useLoadingImage } from '../../hooks/useLoadingImage'; import { GiphyIcon, GiphyLightning } from '../../icons'; -import type { DefaultStreamChatGenerics } from '../../types/types'; + import { makeImageCompatibleUrl } from '../../utils/utils'; const styles = StyleSheet.create({ @@ -134,11 +134,12 @@ const styles = StyleSheet.create({ }, }); -export type GiphyPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'setSelectedMessage' | 'setMessages'> & +export type GiphyPropsWithContext = Pick< + ImageGalleryContextValue, + 'setSelectedMessage' | 'setMessages' +> & Pick< - MessageContextValue, + MessageContextValue, | 'handleAction' | 'isMyMessage' | 'message' @@ -147,22 +148,18 @@ export type GiphyPropsWithContext< | 'onPressIn' | 'preventPress' > & - Pick, 'ImageComponent'> & + Pick & Pick< - MessagesContextValue, + MessagesContextValue, | 'giphyVersion' | 'additionalPressableProps' | 'ImageLoadingIndicator' | 'ImageLoadingFailedIndicator' > & { - attachment: Attachment; + attachment: Attachment; } & Pick; -const GiphyWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: GiphyPropsWithContext, -) => { +const GiphyWithContext = (props: GiphyPropsWithContext) => { const { additionalPressableProps, attachment, @@ -389,10 +386,7 @@ const GiphyWithContext = < ); }; -const areEqual = ( - prevProps: GiphyPropsWithContext, - nextProps: GiphyPropsWithContext, -) => { +const areEqual = (prevProps: GiphyPropsWithContext, nextProps: GiphyPropsWithContext) => { const { attachment: { actions: prevActions, image_url: prevImageUrl, thumb_url: prevThumbUrl }, giphyVersion: prevGiphyVersion, @@ -448,31 +442,25 @@ const areEqual = = Partial> & { - attachment: Attachment; +export type GiphyProps = Partial & { + attachment: Attachment; }; /** * UI component for card in attachments. */ -export const Giphy = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: GiphyProps, -) => { +export const Giphy = (props: GiphyProps) => { const { handleAction, isMyMessage, message, onLongPress, onPress, onPressIn, preventPress } = - useMessageContext(); - const { ImageComponent } = useChatContext(); - const { additionalPressableProps, giphyVersion } = useMessagesContext(); - const { setMessages, setSelectedMessage } = useImageGalleryContext(); + useMessageContext(); + const { ImageComponent } = useChatContext(); + const { additionalPressableProps, giphyVersion } = useMessagesContext(); + const { setMessages, setSelectedMessage } = useImageGalleryContext(); const { setOverlay } = useOverlayContext(); const { ImageLoadingFailedIndicator: ContextImageLoadingFailedIndicator, ImageLoadingIndicator: ContextImageLoadingIndicator, - } = useMessagesContext(); + } = useMessagesContext(); const ImageLoadingFailedIndicator = ContextImageLoadingFailedIndicator || props.ImageLoadingFailedIndicator; const ImageLoadingIndicator = ContextImageLoadingIndicator || props.ImageLoadingIndicator; diff --git a/package/src/components/Attachment/utils/buildGallery/buildGallery.ts b/package/src/components/Attachment/utils/buildGallery/buildGallery.ts index dc2c6659c0..ad2622b0fb 100644 --- a/package/src/components/Attachment/utils/buildGallery/buildGallery.ts +++ b/package/src/components/Attachment/utils/buildGallery/buildGallery.ts @@ -12,7 +12,6 @@ import { chatConfigContextDefaultvalue, ChatConfigContextValue, } from '../../../../contexts/chatConfigContext/ChatConfigContext'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; /** * Builds and returns a gallery of optimized images to be rendered on UI. @@ -30,14 +29,12 @@ import type { DefaultStreamChatGenerics } from '../../../../types/types'; * * @return {GallerySizeAndThumbnailGrid} */ -export function buildGallery< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export function buildGallery({ images, resizableCDNHosts = chatConfigContextDefaultvalue.resizableCDNHosts, sizeConfig, }: Pick & { - images: Attachment[]; + images: Attachment[]; sizeConfig: GallerySizeConfig; }): GallerySizeAndThumbnailGrid { if (images.length === 1) { diff --git a/package/src/components/Attachment/utils/buildGallery/buildGalleryOfSingleImage.ts b/package/src/components/Attachment/utils/buildGallery/buildGalleryOfSingleImage.ts index f7a6a39c72..209d526645 100644 --- a/package/src/components/Attachment/utils/buildGallery/buildGalleryOfSingleImage.ts +++ b/package/src/components/Attachment/utils/buildGallery/buildGalleryOfSingleImage.ts @@ -5,7 +5,7 @@ import { buildThumbnail } from './buildThumbnail'; import type { GallerySizeAndThumbnailGrid, GallerySizeConfig } from './types'; import { ChatConfigContextValue } from '../../../../contexts/chatConfigContext/ChatConfigContext'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; + import { getAspectRatio } from '../getAspectRatio'; /** @@ -20,9 +20,13 @@ function clamp(number: number, min: number, max: number) { return Math.min(Math.max(number, min), max); } -function getContainerSize< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ image, sizeConfig }: { image: Attachment; sizeConfig: GallerySizeConfig }) { +function getContainerSize({ + image, + sizeConfig, +}: { + image: Attachment; + sizeConfig: GallerySizeConfig; +}) { const { original_height: height, original_width: width } = image; const { gridHeight, gridWidth, maxHeight, maxWidth, minHeight, minWidth } = sizeConfig; @@ -64,14 +68,12 @@ function getContainerSize< }; } -export function buildGalleryOfSingleImage< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export function buildGalleryOfSingleImage({ image, resizableCDNHosts, sizeConfig, }: Pick & { - image: Attachment; + image: Attachment; sizeConfig: GallerySizeConfig; }): GallerySizeAndThumbnailGrid { const container = getContainerSize({ diff --git a/package/src/components/Attachment/utils/buildGallery/buildGalleryOfThreeImages.ts b/package/src/components/Attachment/utils/buildGallery/buildGalleryOfThreeImages.ts index adbd7b5928..2f49e854d5 100644 --- a/package/src/components/Attachment/utils/buildGallery/buildGalleryOfThreeImages.ts +++ b/package/src/components/Attachment/utils/buildGallery/buildGalleryOfThreeImages.ts @@ -5,7 +5,7 @@ import { buildThumbnailGrid } from './buildThumbnailGrid'; import type { GallerySizeAndThumbnailGrid, GallerySizeConfig } from './types'; import { ChatConfigContextValue } from '../../../../contexts/chatConfigContext/ChatConfigContext'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; + import { getAspectRatio } from '../getAspectRatio'; /** function to move item to the front of the array */ @@ -20,14 +20,12 @@ function moveToFront(array: T[], item: T): T[] { return newArray; } -export function buildGalleryOfThreeImages< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export function buildGalleryOfThreeImages({ images, resizableCDNHosts, sizeConfig, }: Pick & { - images: Attachment[]; + images: Attachment[]; sizeConfig: GallerySizeConfig; }): GallerySizeAndThumbnailGrid { // Find the most ladscape and most portrait image. @@ -57,9 +55,9 @@ export function buildGalleryOfThreeImages< landscapeImageAspectRatio: 1, portraitImageAspectRatio: 1, } as { - landscapeImage: Attachment; + landscapeImage: Attachment; landscapeImageAspectRatio: number; - portraitImage: Attachment; + portraitImage: Attachment; portraitImageAspectRatio: number; }, ); diff --git a/package/src/components/Attachment/utils/buildGallery/buildGalleryOfTwoImages.ts b/package/src/components/Attachment/utils/buildGallery/buildGalleryOfTwoImages.ts index 1e30968584..7240aaed92 100644 --- a/package/src/components/Attachment/utils/buildGallery/buildGalleryOfTwoImages.ts +++ b/package/src/components/Attachment/utils/buildGallery/buildGalleryOfTwoImages.ts @@ -5,17 +5,15 @@ import { buildThumbnailGrid } from './buildThumbnailGrid'; import type { GallerySizeAndThumbnailGrid, GallerySizeConfig } from './types'; import { ChatConfigContextValue } from '../../../../contexts/chatConfigContext/ChatConfigContext'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; + import { getAspectRatio } from '../getAspectRatio'; -export function buildGalleryOfTwoImages< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export function buildGalleryOfTwoImages({ images, resizableCDNHosts, sizeConfig, }: Pick & { - images: Attachment[]; + images: Attachment[]; sizeConfig: GallerySizeConfig; }): GallerySizeAndThumbnailGrid { const aspectRatio1 = getAspectRatio(images[0]); diff --git a/package/src/components/Attachment/utils/buildGallery/buildThumbnail.ts b/package/src/components/Attachment/utils/buildGallery/buildThumbnail.ts index 0dbe2dd8ab..3132979a58 100644 --- a/package/src/components/Attachment/utils/buildGallery/buildThumbnail.ts +++ b/package/src/components/Attachment/utils/buildGallery/buildThumbnail.ts @@ -5,29 +5,24 @@ import type { Attachment } from 'stream-chat'; import type { Thumbnail } from './types'; import { ChatConfigContextValue } from '../../../../contexts/chatConfigContext/ChatConfigContext'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; import { getResizedImageUrl } from '../../../../utils/getResizedImageUrl'; import { getUrlOfImageAttachment } from '../../../../utils/getUrlOfImageAttachment'; -export type BuildThumbnailProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick & { +export type BuildThumbnailProps = Pick & { height: number; - image: Attachment; + image: Attachment; width: number; resizeMode?: ImageResizeMode; }; -export function buildThumbnail< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export function buildThumbnail({ height, image, resizableCDNHosts, resizeMode, width, -}: BuildThumbnailProps): Thumbnail { +}: BuildThumbnailProps): Thumbnail { const { original_height: originalImageHeight, original_width: originalImageWidth } = image; // Only resize if the original image is larger than the thumbnail container size. diff --git a/package/src/components/Attachment/utils/buildGallery/buildThumbnailGrid.ts b/package/src/components/Attachment/utils/buildGallery/buildThumbnailGrid.ts index 0a6f9e1e74..0b7e015fd7 100644 --- a/package/src/components/Attachment/utils/buildGallery/buildThumbnailGrid.ts +++ b/package/src/components/Attachment/utils/buildGallery/buildThumbnailGrid.ts @@ -5,8 +5,6 @@ import type { GallerySizeAndThumbnailGrid, GallerySizeConfig, ThumbnailGrid } fr import { ChatConfigContextValue } from '../../../../contexts/chatConfigContext/ChatConfigContext'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; - /** * Builds a grid of thumbnail images from image attachments. * This function take a object parameter with following properties: @@ -135,9 +133,7 @@ import type { DefaultStreamChatGenerics } from '../../../../types/types'; * * @return {GallerySizeAndThumbnailGrid} */ -export function buildThumbnailGrid< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export function buildThumbnailGrid({ grid, images, invertedDirections = false, @@ -145,7 +141,7 @@ export function buildThumbnailGrid< sizeConfig, }: Pick & { grid: number[][]; - images: Attachment[]; + images: Attachment[]; invertedDirections: boolean; sizeConfig: GallerySizeConfig; }): GallerySizeAndThumbnailGrid { diff --git a/package/src/components/Attachment/utils/getAspectRatio.ts b/package/src/components/Attachment/utils/getAspectRatio.ts index 86dceaa996..410a0d3cfc 100644 --- a/package/src/components/Attachment/utils/getAspectRatio.ts +++ b/package/src/components/Attachment/utils/getAspectRatio.ts @@ -1,6 +1,6 @@ import type { Attachment } from 'stream-chat'; -import { DefaultStreamChatGenerics, FileTypes } from '../../../types/types'; +import { FileTypes } from '../../../types/types'; /** * Returns the aspect ratio of an image attachment. @@ -8,9 +8,7 @@ import { DefaultStreamChatGenerics, FileTypes } from '../../../types/types'; * @param image Image attachment. * @returns {number} */ -export function getAspectRatio< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->(attachment: Attachment) { +export function getAspectRatio(attachment: Attachment) { if (!(attachment.type === FileTypes.Image || attachment.type === FileTypes.Video)) { throw new Error( 'getAspectRatio() can only be called on an image attachment or video thumbnail', diff --git a/package/src/components/AttachmentPicker/AttachmentPicker.tsx b/package/src/components/AttachmentPicker/AttachmentPicker.tsx index 46f5284a03..2aeb3d6bb9 100644 --- a/package/src/components/AttachmentPicker/AttachmentPicker.tsx +++ b/package/src/components/AttachmentPicker/AttachmentPicker.tsx @@ -18,7 +18,7 @@ import { import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useScreenDimensions } from '../../hooks/useScreenDimensions'; import { NativeHandlers } from '../../native'; -import type { Asset } from '../../types/types'; +import type { File } from '../../types/types'; import { BottomSheet } from '../BottomSheetCompatibility/BottomSheet'; import { BottomSheetFlatList } from '../BottomSheetCompatibility/BottomSheetFlatList'; @@ -108,7 +108,7 @@ export const AttachmentPicker = React.forwardRef( const [iOSLimited, setIosLimited] = useState(false); const hasNextPageRef = useRef(true); const [loadingPhotos, setLoadingPhotos] = useState(false); - const [photos, setPhotos] = useState([]); + const [photos, setPhotos] = useState([]); const attemptedToLoadPhotosOnOpenRef = useRef(false); const getMorePhotos = useCallback(async () => { @@ -245,14 +245,8 @@ export const AttachmentPicker = React.forwardRef( numberOfUploads: selectedFiles.length + selectedImages.length, // `id` is available for Expo MediaLibrary while Cameraroll doesn't share id therefore we use `uri` selected: - selectedImages.some((image) => - image.id - ? image.id === asset.id - : image.uri === asset.uri || image.originalUri === asset.uri, - ) || - selectedFiles.some((file) => - file.id ? file.id === asset.id : file.uri === asset.uri || file.originalUri === asset.uri, - ), + selectedImages.some((image) => image.uri === asset.uri) || + selectedFiles.some((file) => file.uri === asset.uri), selectedFiles, selectedImages, setSelectedFiles, diff --git a/package/src/components/AttachmentPicker/components/AttachmentPickerItem.tsx b/package/src/components/AttachmentPicker/components/AttachmentPickerItem.tsx index 1d86d8bb94..0eccbfd1a3 100644 --- a/package/src/components/AttachmentPicker/components/AttachmentPickerItem.tsx +++ b/package/src/components/AttachmentPicker/components/AttachmentPickerItem.tsx @@ -2,22 +2,19 @@ import React from 'react'; import { Alert, ImageBackground, StyleSheet, Text, View } from 'react-native'; -import { lookup } from 'mime-types'; - import { AttachmentPickerContextValue } from '../../../contexts/attachmentPickerContext/AttachmentPickerContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { useViewport } from '../../../hooks/useViewport'; import { Recorder } from '../../../icons'; -import type { Asset, File } from '../../../types/types'; +import type { File } from '../../../types/types'; import { getDurationLabelFromDuration } from '../../../utils/utils'; import { BottomSheetTouchableOpacity } from '../../BottomSheetCompatibility/BottomSheetTouchableOpacity'; - type AttachmentPickerItemType = Pick< AttachmentPickerContextValue, 'selectedFiles' | 'setSelectedFiles' | 'setSelectedImages' | 'selectedImages' | 'maxNumberOfFiles' > & { - asset: Asset; + asset: File; ImageOverlaySelectedComponent: React.ComponentType; numberOfUploads: number; selected: boolean; @@ -48,46 +45,25 @@ const AttachmentVideo = (props: AttachmentVideoProps) => { }, } = useTheme(); - const { duration: videoDuration, id: assetId, originalUri, uri } = asset; + const { duration: videoDuration, thumb_url, uri } = asset; - const durationLabel = getDurationLabelFromDuration(videoDuration); + const durationLabel = videoDuration ? getDurationLabelFromDuration(videoDuration) : '00:00'; const size = vw(100) / (numberOfAttachmentPickerImageColumns || 3) - 2; - /* Patches video files with uri and mimetype */ - const patchVideoFile = (files: File[]) => { - // We need a mime-type to upload a video file. - const mimeType = lookup(asset.name) || 'multipart/form-data'; - return [ - ...files, - { - duration: asset.duration, - id: asset.id, - mimeType, - name: asset.name, - originalUri, - size: asset.size, - uri, - }, - ]; - }; - const updateSelectedFiles = () => { if (numberOfUploads >= maxNumberOfFiles) { Alert.alert(t('Maximum number of files reached')); return; } - const files = patchVideoFile(selectedFiles); - setSelectedFiles(files); + setSelectedFiles([...selectedFiles, asset]); }; const onPressVideo = () => { if (selected) { setSelectedFiles((files) => // `id` is available for Expo MediaLibrary while Cameraroll doesn't share id therefore we use `uri` - files.filter((file) => - file.id ? file.id !== assetId : file.uri !== uri && file.originalUri !== uri, - ), + files.filter((file) => file.uri !== uri), ); } else { updateSelectedFiles(); @@ -97,7 +73,7 @@ const AttachmentVideo = (props: AttachmentVideoProps) => { return ( { const size = vw(100) / (numberOfAttachmentPickerImageColumns || 3) - 2; - const { id: assetId, originalUri, uri } = asset; + const { uri } = asset; const updateSelectedImages = () => { if (numberOfUploads >= maxNumberOfFiles) { @@ -160,11 +136,7 @@ const AttachmentImage = (props: AttachmentImageProps) => { const onPressImage = () => { if (selected) { // `id` is available for Expo MediaLibrary while Cameraroll doesn't share id therefore we use `uri` - setSelectedImages((images) => - images.filter((image) => - assetId ? image.id !== assetId : image.uri !== uri && originalUri !== uri, - ), - ); + setSelectedImages((images) => images.filter((image) => image.uri !== uri)); } else { updateSelectedImages(); } diff --git a/package/src/components/AutoCompleteInput/AutoCompleteInput.tsx b/package/src/components/AutoCompleteInput/AutoCompleteInput.tsx index f0652f0d75..66444b63e8 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteInput.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteInput.tsx @@ -23,7 +23,7 @@ import { useTranslationContext, } from '../../contexts/translationContext/TranslationContext'; import type { Emoji } from '../../emoji-data'; -import type { DefaultStreamChatGenerics } from '../../types/types'; + import { isCommandTrigger, isEmojiTrigger, @@ -47,10 +47,8 @@ const computeCaretPosition = (token: string, startOfTokenPosition: number) => const isCommand = (text: string) => text[0] === '/' && text.split(' ').length <= 1; -type AutoCompleteInputPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - MessageInputContextValue, +type AutoCompleteInputPropsWithContext = Pick< + MessageInputContextValue, | 'additionalTextInputProps' | 'autoCompleteSuggestionsLimit' | 'giphyActive' @@ -65,10 +63,7 @@ type AutoCompleteInputPropsWithContext< | 'text' | 'triggerSettings' > & - Pick< - SuggestionsContextValue, - 'closeSuggestions' | 'openSuggestions' | 'updateSuggestions' - > & + Pick & Pick & { /** * This is currently passed in from MessageInput to avoid rerenders @@ -77,15 +72,17 @@ type AutoCompleteInputPropsWithContext< cooldownActive?: boolean; }; -export type AutoCompleteInputProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; - -const AutoCompleteInputWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AutoCompleteInputPropsWithContext, -) => { +export type AutoCompleteInputProps = Partial< + Omit< + AutoCompleteInputPropsWithContext, + | 'triggerSettings' + | 'mentionAllAppUsersQuery' + | 'mentionAllAppUsersEnabled' + | 'autoCompleteSuggestionsLimit' + > +>; + +const AutoCompleteInputWithContext = (props: AutoCompleteInputPropsWithContext) => { const { additionalTextInputProps, autoCompleteSuggestionsLimit, @@ -93,9 +90,9 @@ const AutoCompleteInputWithContext = < cooldownActive = false, giphyActive, giphyEnabled, - maxMessageLength, mentionAllAppUsersEnabled, mentionAllAppUsersQuery, + maxMessageLength, numberOfLines, onChange, openSuggestions, @@ -156,7 +153,7 @@ const AutoCompleteInputWithContext = < const triggerSetting = triggerSettings[trigger]; if (triggerSetting) { await triggerSetting.dataProvider( - query as SuggestionUser['name'], + query as SuggestionUser['name'], text, (data, queryCallback) => { if (query === queryCallback) { @@ -178,7 +175,7 @@ const AutoCompleteInputWithContext = < const triggerSetting = triggerSettings[trigger]; if (triggerSetting) { await triggerSetting.dataProvider( - query as SuggestionCommand['name'], + query as SuggestionCommand['name'], text, (data, queryCallback) => { if (query !== queryCallback) { @@ -222,13 +219,7 @@ const AutoCompleteInputWithContext = < selectionEnd.current = end; }; - const onSelectSuggestion = ({ - item, - trigger, - }: { - item: Suggestion; - trigger: Trigger; - }) => { + const onSelectSuggestion = ({ item, trigger }: { item: Suggestion; trigger: Trigger }) => { if (!trigger || !triggerSettings[trigger]) { return; } @@ -430,9 +421,9 @@ const AutoCompleteInputWithContext = < ); }; -const areEqual = ( - prevProps: AutoCompleteInputPropsWithContext, - nextProps: AutoCompleteInputPropsWithContext, +const areEqual = ( + prevProps: AutoCompleteInputPropsWithContext, + nextProps: AutoCompleteInputPropsWithContext, ) => { const { cooldownActive: prevCooldownActive, @@ -475,11 +466,7 @@ const MemoizedAutoCompleteInput = React.memo( areEqual, ) as typeof AutoCompleteInputWithContext; -export const AutoCompleteInput = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AutoCompleteInputProps, -) => { +export const AutoCompleteInput = (props: AutoCompleteInputProps) => { const { giphyEnabled, additionalTextInputProps, @@ -494,9 +481,8 @@ export const AutoCompleteInput = < setInputBoxRef, text, triggerSettings, - } = useMessageInputContext(); - const { closeSuggestions, openSuggestions, updateSuggestions } = - useSuggestionsContext(); + } = useMessageInputContext(); + const { closeSuggestions, openSuggestions, updateSuggestions } = useSuggestionsContext(); const { t } = useTranslationContext(); return ( diff --git a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionCommandIcon.tsx b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionCommandIcon.tsx index fbd1dabf17..a1b155a9a6 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionCommandIcon.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionCommandIcon.tsx @@ -4,7 +4,6 @@ import { StyleSheet, View } from 'react-native'; import type { SuggestionCommand } from '../../contexts/suggestionsContext/SuggestionsContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { Flag, GiphyIcon, Imgur, Lightning, Mute, Sound, UserAdd, UserDelete } from '../../icons'; -import type { DefaultStreamChatGenerics } from '../../types/types'; const styles = StyleSheet.create({ iconContainer: { @@ -17,12 +16,10 @@ const styles = StyleSheet.create({ }, }); -export const AutoCompleteSuggestionCommandIcon = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const AutoCompleteSuggestionCommandIcon = ({ name, }: { - name: SuggestionCommand['name']; + name: SuggestionCommand['name']; }) => { const { theme: { diff --git a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionHeader.tsx b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionHeader.tsx index 9c3579d4cb..b5233d93fd 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionHeader.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionHeader.tsx @@ -7,11 +7,11 @@ import { useTranslationContext } from '../../contexts/translationContext/Transla import { Lightning } from '../../icons/Lightning'; import { Smile } from '../../icons/Smile'; -import type { DefaultStreamChatGenerics } from '../../types/types'; -export type AutoCompleteSuggestionHeaderPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'triggerType' | 'queryText'>; +export type AutoCompleteSuggestionHeaderPropsWithContext = Pick< + SuggestionsContextValue, + 'triggerType' | 'queryText' +>; const styles = StyleSheet.create({ container: { @@ -25,12 +25,10 @@ const styles = StyleSheet.create({ }, }); -const AutoCompleteSuggestionHeaderWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +const AutoCompleteSuggestionHeaderWithContext = ({ queryText, triggerType, -}: AutoCompleteSuggestionHeaderPropsWithContext) => { +}: AutoCompleteSuggestionHeaderPropsWithContext) => { const { t } = useTranslationContext(); const { theme: { @@ -68,9 +66,9 @@ const AutoCompleteSuggestionHeaderWithContext = < } }; -const areEqual = ( - prevProps: AutoCompleteSuggestionHeaderPropsWithContext, - nextProps: AutoCompleteSuggestionHeaderPropsWithContext, +const areEqual = ( + prevProps: AutoCompleteSuggestionHeaderPropsWithContext, + nextProps: AutoCompleteSuggestionHeaderPropsWithContext, ) => { const { queryText: prevQueryText, triggerType: prevType } = prevProps; const { queryText: nextQueryText, triggerType: nextType } = nextProps; @@ -92,15 +90,11 @@ const MemoizedAutoCompleteSuggestionHeader = React.memo( areEqual, ) as typeof AutoCompleteSuggestionHeaderWithContext; -export type AutoCompleteSuggestionHeaderProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = AutoCompleteSuggestionHeaderPropsWithContext; +export type AutoCompleteSuggestionHeaderProps = AutoCompleteSuggestionHeaderPropsWithContext; -export const AutoCompleteSuggestionHeader = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AutoCompleteSuggestionHeaderProps, -) => ; +export const AutoCompleteSuggestionHeader = (props: AutoCompleteSuggestionHeaderProps) => ( + +); AutoCompleteSuggestionHeader.displayName = 'AutoCompleteSuggestionHeader{messageInput{suggestions{Header}}}'; diff --git a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionItem.tsx b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionItem.tsx index 215a874933..0ae12208e0 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionItem.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionItem.tsx @@ -12,13 +12,14 @@ import type { import { useTheme } from '../../contexts/themeContext/ThemeContext'; import type { Emoji } from '../../emoji-data'; import { AtMentions } from '../../icons/AtMentions'; -import type { DefaultStreamChatGenerics } from '../../types/types'; + import { Avatar } from '../Avatar/Avatar'; -export type AutoCompleteSuggestionItemPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick & { - itemProps: Suggestion; +export type AutoCompleteSuggestionItemPropsWithContext = Pick< + SuggestionsContextValue, + 'triggerType' +> & { + itemProps: Suggestion; }; const styles = StyleSheet.create({ @@ -55,12 +56,10 @@ const styles = StyleSheet.create({ }, }); -const AutoCompleteSuggestionItemWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +const AutoCompleteSuggestionItemWithContext = ({ itemProps, triggerType, -}: AutoCompleteSuggestionItemPropsWithContext) => { +}: AutoCompleteSuggestionItemPropsWithContext) => { const { theme: { colors: { accent_blue, black, grey }, @@ -75,7 +74,7 @@ const AutoCompleteSuggestionItemWithContext = < } = useTheme(); if (triggerType === 'mention') { - const { id, image, name, online } = itemProps as SuggestionUser; + const { id, image, name, online } = itemProps as SuggestionUser; return ( @@ -100,7 +99,7 @@ const AutoCompleteSuggestionItemWithContext = < ); } else if (triggerType === 'command') { - const { args, name } = itemProps as SuggestionCommand; + const { args, name } = itemProps as SuggestionCommand; return ( @@ -117,9 +116,9 @@ const AutoCompleteSuggestionItemWithContext = < } }; -const areEqual = ( - prevProps: AutoCompleteSuggestionItemPropsWithContext, - nextProps: AutoCompleteSuggestionItemPropsWithContext, +const areEqual = ( + prevProps: AutoCompleteSuggestionItemPropsWithContext, + nextProps: AutoCompleteSuggestionItemPropsWithContext, ) => { const { itemProps: prevItemProps, triggerType: prevType } = prevProps; const { itemProps: nextItemProps, triggerType: nextType } = nextProps; @@ -139,15 +138,11 @@ const MemoizedAutoCompleteSuggestionItem = React.memo( areEqual, ) as typeof AutoCompleteSuggestionItemWithContext; -export type AutoCompleteSuggestionItemProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = AutoCompleteSuggestionItemPropsWithContext; +export type AutoCompleteSuggestionItemProps = AutoCompleteSuggestionItemPropsWithContext; -export const AutoCompleteSuggestionItem = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AutoCompleteSuggestionItemProps, -) => ; +export const AutoCompleteSuggestionItem = (props: AutoCompleteSuggestionItemProps) => ( + +); AutoCompleteSuggestionItem.displayName = 'AutoCompleteSuggestionItem{messageInput{suggestions{Item}}}'; diff --git a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx index 2ccce9ec41..d2ec40e117 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx @@ -20,25 +20,23 @@ import { useSuggestionsContext, } from '../../contexts/suggestionsContext/SuggestionsContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; const AUTO_COMPLETE_SUGGESTION_LIST_HEADER_HEIGHT = 50; -type AutoCompleteSuggestionListComponentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick & { +type AutoCompleteSuggestionListComponentProps = Pick< + SuggestionsContextValue, + 'queryText' | 'triggerType' +> & { active: boolean; - data: Suggestion[]; - onSelect: (item: Suggestion) => void; + data: Suggestion[]; + onSelect: (item: Suggestion) => void; }; -export type AutoCompleteSuggestionListPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - SuggestionsContextValue, +export type AutoCompleteSuggestionListPropsWithContext = Pick< + SuggestionsContextValue, 'AutoCompleteSuggestionHeader' | 'AutoCompleteSuggestionItem' > & - AutoCompleteSuggestionListComponentProps; + AutoCompleteSuggestionListComponentProps; const SuggestionsItem = (props: PressableProps) => { const { children, style: propsStyle, ...pressableProps } = props; @@ -57,10 +55,8 @@ const SuggestionsItem = (props: PressableProps) => { SuggestionsItem.displayName = 'SuggestionsHeader{messageInput{suggestions}}'; -export const AutoCompleteSuggestionListWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AutoCompleteSuggestionListPropsWithContext, +export const AutoCompleteSuggestionListWithContext = ( + props: AutoCompleteSuggestionListPropsWithContext, ) => { const [itemHeight, setItemHeight] = useState(0); const { @@ -98,7 +94,7 @@ export const AutoCompleteSuggestionListWithContext = < // eslint-disable-next-line react-hooks/exhaustive-deps }, [itemHeight, data.length]); - const renderItem = ({ item }: { item: Suggestion }) => { + const renderItem = ({ item }: { item: Suggestion }) => { switch (triggerType) { case 'command': case 'mention': @@ -145,9 +141,9 @@ export const AutoCompleteSuggestionListWithContext = < ); }; -const areEqual = ( - prevProps: AutoCompleteSuggestionListPropsWithContext, - nextProps: AutoCompleteSuggestionListPropsWithContext, +const areEqual = ( + prevProps: AutoCompleteSuggestionListPropsWithContext, + nextProps: AutoCompleteSuggestionListPropsWithContext, ) => { const { active: prevActive, @@ -190,22 +186,13 @@ const MemoizedAutoCompleteSuggestionList = React.memo( areEqual, ) as typeof AutoCompleteSuggestionListWithContext; -export type AutoCompleteSuggestionListProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = AutoCompleteSuggestionListComponentProps & { +export type AutoCompleteSuggestionListProps = AutoCompleteSuggestionListComponentProps & { AutoCompleteSuggestionHeader?: React.ComponentType; - AutoCompleteSuggestionItem?: React.ComponentType< - AutoCompleteSuggestionItemProps - >; + AutoCompleteSuggestionItem?: React.ComponentType; }; -export const AutoCompleteSuggestionList = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AutoCompleteSuggestionListProps, -) => { - const { AutoCompleteSuggestionHeader, AutoCompleteSuggestionItem } = - useSuggestionsContext(); +export const AutoCompleteSuggestionList = (props: AutoCompleteSuggestionListProps) => { + const { AutoCompleteSuggestionHeader, AutoCompleteSuggestionItem } = useSuggestionsContext(); return ( { const getComponent = (props = {}) => ( - - - + + + + ); diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index b4c6e9d945..d8200a5fcd 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -11,7 +11,8 @@ import { ChannelState, Channel as ChannelType, EventHandler, - FormatMessageResponse, + LocalMessage, + MessageLabel, MessageResponse, Reaction, SendMessageAPIResponse, @@ -91,7 +92,7 @@ import { NativeHandlers, } from '../../native'; import * as dbApi from '../../store/apis'; -import { ChannelUnreadState, DefaultStreamChatGenerics, FileTypes } from '../../types/types'; +import { ChannelUnreadState, FileTypes } from '../../types/types'; import { addReactionToLocalState } from '../../utils/addReactionToLocalState'; import { compressedImageURI } from '../../utils/compressImage'; import { DBSyncManager } from '../../utils/DBSyncManager'; @@ -170,7 +171,6 @@ import { ShowThreadMessageInChannelButton as ShowThreadMessageInChannelButtonDef import { StopMessageStreamingButton as DefaultStopMessageStreamingButton } from '../MessageInput/StopMessageStreamingButton'; import { UploadProgressIndicator as UploadProgressIndicatorDefault } from '../MessageInput/UploadProgressIndicator'; import { DateHeader as DateHeaderDefault } from '../MessageList/DateHeader'; -import type { MessageType } from '../MessageList/hooks/useMessageList'; import { InlineDateSeparator as InlineDateSeparatorDefault } from '../MessageList/InlineDateSeparator'; import { InlineUnreadIndicator as InlineUnreadIndicatorDefault } from '../MessageList/InlineUnreadIndicator'; import { MessageList as MessageListDefault } from '../MessageList/MessageList'; @@ -244,12 +244,10 @@ const debounceOptions = { trailing: true, }; -export type ChannelPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'channel'> & +export type ChannelPropsWithContext = Pick & Partial< Pick< - ChannelContextValue, + ChannelContextValue, | 'EmptyStateIndicator' | 'enableMessageGroupingByUser' | 'enforceUniqueReaction' @@ -262,30 +260,27 @@ export type ChannelPropsWithContext< | 'StickyHeader' > > & - Pick, 'client' | 'enableOfflineSupport'> & + Pick & Partial< Omit< - InputMessageInputContextValue, + InputMessageInputContextValue, 'quotedMessage' | 'editing' | 'clearEditingState' | 'clearQuotedMessageState' | 'sendMessage' > > & Partial< Pick< - SuggestionsContextValue, + SuggestionsContextValue, 'AutoCompleteSuggestionHeader' | 'AutoCompleteSuggestionItem' | 'AutoCompleteSuggestionList' > > & Pick & Partial< - Pick< - PaginatedMessageListContextValue, - 'messages' | 'loadingMore' | 'loadingMoreRecent' - > + Pick > & - Pick, 'threadMessages' | 'setThreadMessages'> & + Pick & Partial< Pick< - MessagesContextValue, + MessagesContextValue, | 'additionalPressableProps' | 'Attachment' | 'AttachmentActions' @@ -380,10 +375,10 @@ export type ChannelPropsWithContext< | 'StreamingMessageView' > > & - Partial, 'isMessageAIGenerated'>> & - Partial, 'allowThreadMessagesInChannel'>> & { + Partial> & + Partial> & { shouldSyncChannel: boolean; - thread: ThreadType; + thread: ThreadType; /** * Additional props passed to keyboard avoiding view */ @@ -402,7 +397,7 @@ export type ChannelPropsWithContext< * @param channel Channel object */ doMarkReadRequest?: ( - channel: ChannelType, + channel: ChannelType, setChannelUnreadUiState?: (state: ChannelUnreadState) => void, ) => void; /** @@ -412,8 +407,8 @@ export type ChannelPropsWithContext< */ doSendMessageRequest?: ( channelId: string, - messageData: StreamMessage, - ) => Promise>; + messageData: StreamMessage, + ) => Promise; /** * Overrides the Stream default update message request (Advanced usage only) * @param channelId @@ -421,8 +416,8 @@ export type ChannelPropsWithContext< */ doUpdateMessageRequest?: ( channelId: string, - updatedMessage: Parameters['updateMessage']>[0], - ) => ReturnType['updateMessage']>; + updatedMessage: Parameters[0], + ) => ReturnType; /** * When true, messageList will be scrolled at first unread message, when opened. */ @@ -480,11 +475,7 @@ export type ChannelPropsWithContext< > >; -const ChannelWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: PropsWithChildren>, -) => { +const ChannelWithContext = (props: PropsWithChildren) => { const { additionalKeyboardAvoidingViewProps, additionalPressableProps, @@ -696,14 +687,12 @@ const ChannelWithContext = < }, } = useTheme(); const [deleted, setDeleted] = useState(false); - const [editing, setEditing] = useState | undefined>(undefined); + const [editing, setEditing] = useState(undefined); const [error, setError] = useState(false); const [lastRead, setLastRead] = useState(); - const [quotedMessage, setQuotedMessage] = useState | undefined>( - undefined, - ); - const [thread, setThread] = useState | null>(threadProps || null); + const [quotedMessage, setQuotedMessage] = useState(undefined); + const [thread, setThread] = useState(threadProps || null); const [threadHasMore, setThreadHasMore] = useState(true); const [threadLoadingMore, setThreadLoadingMore] = useState(false); const [channelUnreadState, setChannelUnreadState] = useState( @@ -736,7 +725,7 @@ const ChannelWithContext = < setRead, setTyping, state: channelState, - } = useChannelDataState(channel); + } = useChannelDataState(channel); const { copyMessagesStateFromChannel, @@ -747,7 +736,7 @@ const ChannelWithContext = < loadMore, loadMoreRecent, state: channelMessagesState, - } = useMessageListPagination({ + } = useMessageListPagination({ channel, }); @@ -794,7 +783,7 @@ const ChannelWithContext = < [stateUpdateThrottleInterval, channel, copyStateFromChannel, copyMessagesStateFromChannel], ); - const handleEvent: EventHandler = useStableCallback((event) => { + const handleEvent: EventHandler = useStableCallback((event) => { if (shouldSyncChannel) { /** * Ignore user.watching.start and user.watching.stop as we should not copy the entire state when @@ -962,14 +951,12 @@ const ChannelWithContext = < }, [threadPropsExists, shouldSyncChannel]); const handleAppBackground = useCallback(() => { - const channelData = channel.data as - | Extract - | undefined; + const channelData = channel.data; if (channelData?.own_capabilities?.includes('send-typing-events')) { channel.sendEvent({ parent_id: thread?.id, type: 'typing.stop', - } as StreamEvent); + } as StreamEvent); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [thread?.id, channelId]); @@ -979,7 +966,7 @@ const ChannelWithContext = < /** * CHANNEL METHODS */ - const markReadInternal: ChannelContextValue['markRead'] = throttle( + const markReadInternal: ChannelContextValue['markRead'] = throttle( async (options?: MarkReadFunctionOptions) => { const { updateChannelUnreadState = true } = options ?? {}; if (!channel || channel?.disconnected || !clientChannelConfig?.read_events) { @@ -1060,13 +1047,13 @@ const ChannelWithContext = < }); } - const parseMessage = (message: FormatMessageResponse) => + const parseMessage = (message: LocalMessage) => ({ ...message, created_at: message.created_at.toString(), pinned_at: message.pinned_at?.toString(), updated_at: message.updated_at?.toString(), - }) as unknown as MessageResponse; + }) as unknown as MessageResponse; try { if (!thread) { @@ -1160,7 +1147,7 @@ const ChannelWithContext = < } }); - const loadChannelAroundMessage: ChannelContextValue['loadChannelAroundMessage'] = + const loadChannelAroundMessage: ChannelContextValue['loadChannelAroundMessage'] = useStableCallback(async ({ messageId: messageIdToLoadAround }): Promise => { if (!messageIdToLoadAround) { return; @@ -1197,8 +1184,8 @@ const ChannelWithContext = < /** * MESSAGE METHODS */ - const updateMessage: MessagesContextValue['updateMessage'] = - useStableCallback((updatedMessage, extraState = {}, throttled = false) => { + const updateMessage: MessagesContextValue['updateMessage'] = useStableCallback( + (updatedMessage, extraState = {}, throttled = false) => { if (!channel) { return; } @@ -1214,13 +1201,11 @@ const ChannelWithContext = < extraState.threadMessages = channel.state.threads[updatedMessage.parent_id] || []; setThreadMessages(extraState.threadMessages); } - }); + }, + ); const replaceMessage = useStableCallback( - ( - oldMessage: MessageResponse, - newMessage: MessageResponse, - ) => { + (oldMessage: MessageResponse, newMessage: MessageResponse) => { if (channel) { channel.state.removeMessage(oldMessage); channel.state.addMessageSorted(newMessage, true); @@ -1239,11 +1224,10 @@ const ChannelWithContext = < attachments, mentioned_users, parent_id, - poll, poll_id, text, ...extraFields - }: Partial>) => { + }: Partial) => { // Exclude following properties from message.user within message preview, // since they could be long arrays and have no meaning as sender of message. // Storing such large value within user's table may cause sqlite queries to crash. @@ -1262,7 +1246,6 @@ const ChannelWithContext = < id: userId, })) || [], parent_id, - poll, poll_id, reactions: [], status: MessageStatusTypes.SENDING, @@ -1273,7 +1256,7 @@ const ChannelWithContext = < id: client.userID, }, ...extraFields, - } as unknown as MessageResponse; + } as unknown as MessageResponse; /** * This is added to the message for local rendering prior to the message @@ -1285,87 +1268,84 @@ const ChannelWithContext = < (message) => message.id === preview.quoted_message_id, ); - preview.quoted_message = - quotedMessage as MessageResponse['quoted_message']; + preview.quoted_message = quotedMessage as MessageResponse['quoted_message']; } return preview; }, ); - const uploadPendingAttachments = useStableCallback( - async (message: MessageResponse) => { - const updatedMessage = { ...message }; - if (updatedMessage.attachments?.length) { - for (let i = 0; i < updatedMessage.attachments?.length; i++) { - const attachment = updatedMessage.attachments[i]; - const image = attachment.originalImage; - const file = attachment.originalFile; - // check if image_url is not a remote url - if ( - attachment.type === FileTypes.Image && - image?.uri && - attachment.image_url && - isLocalUrl(attachment.image_url) - ) { - const filename = image.name ?? getFileNameFromPath(image.uri); - // if any upload is in progress, cancel it - const controller = uploadAbortControllerRef.current.get(filename); - if (controller) { - controller.abort(); - uploadAbortControllerRef.current.delete(filename); - } - const compressedUri = await compressedImageURI(image, compressImageQuality); - const contentType = lookup(filename) || 'multipart/form-data'; - - const uploadResponse = doImageUploadRequest - ? await doImageUploadRequest(image, channel) - : await channel.sendImage(compressedUri, filename, contentType); + const uploadPendingAttachments = useStableCallback(async (message: MessageResponse) => { + const updatedMessage = { ...message }; + if (updatedMessage.attachments?.length) { + for (let i = 0; i < updatedMessage.attachments?.length; i++) { + const attachment = updatedMessage.attachments[i]; + const image = attachment.originalImage; + const file = attachment.originalFile; + // check if image_url is not a remote url + if ( + attachment.type === FileTypes.Image && + image?.uri && + attachment.image_url && + isLocalUrl(attachment.image_url) + ) { + const filename = image.name ?? getFileNameFromPath(image.uri); + // if any upload is in progress, cancel it + const controller = uploadAbortControllerRef.current.get(filename); + if (controller) { + controller.abort(); + uploadAbortControllerRef.current.delete(filename); + } + const compressedUri = await compressedImageURI(image, compressImageQuality); + const contentType = lookup(filename) || 'multipart/form-data'; - attachment.image_url = uploadResponse.file; - delete attachment.originalFile; + const uploadResponse = doImageUploadRequest + ? await doImageUploadRequest(image, channel) + : await channel.sendImage(compressedUri, filename, contentType); - await dbApi.updateMessage({ - message: { ...updatedMessage, cid: channel.cid }, - }); - } + attachment.image_url = uploadResponse.file; + delete attachment.originalFile; - if ( - (attachment.type === FileTypes.File || - attachment.type === FileTypes.Audio || - attachment.type === FileTypes.VoiceRecording || - attachment.type === FileTypes.Video) && - attachment.asset_url && - isLocalUrl(attachment.asset_url) && - file?.uri - ) { - // if any upload is in progress, cancel it - const controller = uploadAbortControllerRef.current.get(file.name); - if (controller) { - controller.abort(); - uploadAbortControllerRef.current.delete(file.name); - } - const response = doDocUploadRequest - ? await doDocUploadRequest(file, channel) - : await channel.sendFile(file.uri, file.name, file.mimeType); - attachment.asset_url = response.file; - if (response.thumb_url) { - attachment.thumb_url = response.thumb_url; - } + await dbApi.updateMessage({ + message: { ...updatedMessage, cid: channel.cid }, + }); + } - delete attachment.originalFile; - await dbApi.updateMessage({ - message: { ...updatedMessage, cid: channel.cid }, - }); + if ( + (attachment.type === FileTypes.File || + attachment.type === FileTypes.Audio || + attachment.type === FileTypes.VoiceRecording || + attachment.type === FileTypes.Video) && + attachment.asset_url && + isLocalUrl(attachment.asset_url) && + file?.uri + ) { + // if any upload is in progress, cancel it + const controller = uploadAbortControllerRef.current.get(file.name); + if (controller) { + controller.abort(); + uploadAbortControllerRef.current.delete(file.name); + } + const response = doDocUploadRequest + ? await doDocUploadRequest(file, channel) + : await channel.sendFile(file.uri, file.name, file.type); + attachment.asset_url = response.file; + if (response.thumb_url) { + attachment.thumb_url = response.thumb_url; } + + delete attachment.originalFile; + await dbApi.updateMessage({ + message: { ...updatedMessage, cid: channel.cid }, + }); } } + } - return updatedMessage; - }, - ); + return updatedMessage; + }); const sendMessageRequest = useStableCallback( - async (message: MessageResponse, retrying?: boolean) => { + async (message: MessageResponse, retrying?: boolean) => { try { const updatedMessage = await uploadPendingAttachments(message); const extraFields = omit(updatedMessage, [ @@ -1403,9 +1383,9 @@ const ChannelWithContext = < parent_id, text: patchMessageTextCommand(text ?? '', mentionedUserIds), ...extraFields, - } as StreamMessage; + } as StreamMessage; - let messageResponse = {} as SendMessageAPIResponse; + let messageResponse = {} as SendMessageAPIResponse; if (doSendMessageRequest) { messageResponse = await doSendMessageRequest(channel?.cid || '', messageData); } else if (channel) { @@ -1443,8 +1423,8 @@ const ChannelWithContext = < }, ); - const sendMessage: InputMessageInputContextValue['sendMessage'] = - useStableCallback(async (message) => { + const sendMessage: InputMessageInputContextValue['sendMessage'] = useStableCallback( + async (message) => { if (channel?.state?.filterErrorMessages) { channel.state.filterErrorMessages(); } @@ -1472,10 +1452,11 @@ const ChannelWithContext = < } await sendMessageRequest(messagePreview); - }); + }, + ); - const retrySendMessage: MessagesContextValue['retrySendMessage'] = - useStableCallback(async (message) => { + const retrySendMessage: MessagesContextValue['retrySendMessage'] = useStableCallback( + async (message) => { const statusPendingMessage = { ...message, status: MessageStatusTypes.SENDING, @@ -1485,44 +1466,43 @@ const ChannelWithContext = < // For bounced messages, we don't need to update the message, instead always send a new message. if (!isBouncedMessage(message)) { - updateMessage(messageWithoutReservedFields as MessageResponse); + updateMessage(messageWithoutReservedFields as MessageResponse); } - await sendMessageRequest( - messageWithoutReservedFields as MessageResponse, - true, - ); - }); + await sendMessageRequest(messageWithoutReservedFields as MessageResponse, true); + }, + ); - const editMessage: InputMessageInputContextValue['editMessage'] = - useStableCallback((updatedMessage) => + const editMessage: InputMessageInputContextValue['editMessage'] = useStableCallback( + (updatedMessage) => doUpdateMessageRequest ? doUpdateMessageRequest(channel?.cid || '', updatedMessage) : client.updateMessage(updatedMessage), - ); + ); - const setEditingState: MessagesContextValue['setEditingState'] = - useStableCallback((message) => { - clearQuotedMessageState(); - setEditing(message); - }); + const setEditingState: MessagesContextValue['setEditingState'] = useStableCallback((message) => { + clearQuotedMessageState(); + setEditing(message); + }); - const setQuotedMessageState: MessagesContextValue['setQuotedMessageState'] = - useStableCallback((messageOrBoolean) => { + const setQuotedMessageState: MessagesContextValue['setQuotedMessageState'] = useStableCallback( + (messageOrBoolean) => { setQuotedMessage(messageOrBoolean); - }); + }, + ); - const clearEditingState: InputMessageInputContextValue['clearEditingState'] = - useStableCallback(() => setEditing(undefined)); + const clearEditingState: InputMessageInputContextValue['clearEditingState'] = useStableCallback( + () => setEditing(undefined), + ); - const clearQuotedMessageState: InputMessageInputContextValue['clearQuotedMessageState'] = + const clearQuotedMessageState: InputMessageInputContextValue['clearQuotedMessageState'] = useStableCallback(() => setQuotedMessage(undefined)); /** * Removes the message from local state */ - const removeMessage: MessagesContextValue['removeMessage'] = - useStableCallback(async (message) => { + const removeMessage: MessagesContextValue['removeMessage'] = useStableCallback( + async (message) => { if (channel) { channel.state.removeMessage(message); copyMessagesStateFromChannel(channel); @@ -1537,18 +1517,19 @@ const ChannelWithContext = < id: message.id, }); } - }); + }, + ); const sendReaction = useStableCallback(async (type: string, messageId: string) => { if (!channel?.id || !client.user) { throw new Error('Channel has not been initialized'); } - const payload: Parameters['sendReaction']> = [ + const payload: Parameters = [ messageId, { type, - } as Reaction, + } as Reaction, { enforce_unique: enforceUniqueReaction }, ]; @@ -1557,7 +1538,7 @@ const ChannelWithContext = < return; } - addReactionToLocalState({ + addReactionToLocalState({ channel, enforceUniqueReaction, messageId, @@ -1567,7 +1548,7 @@ const ChannelWithContext = < copyMessagesStateFromChannel(channel); - const sendReactionResponse = await DBSyncManager.queueTask({ + const sendReactionResponse = await DBSyncManager.queueTask({ client, task: { channelId: channel.id, @@ -1582,8 +1563,8 @@ const ChannelWithContext = < } }); - const deleteMessage: MessagesContextValue['deleteMessage'] = - useStableCallback(async (message) => { + const deleteMessage: MessagesContextValue['deleteMessage'] = useStableCallback( + async (message) => { if (!channel.id) { throw new Error('Channel has not been initialized yet'); } @@ -1604,14 +1585,14 @@ const ChannelWithContext = < const updatedMessage = { ...message, cid: channel.cid, - deleted_at: new Date().toISOString(), - type: 'deleted', + deleted_at: new Date(), + type: 'deleted' as MessageLabel, }; updateMessage(updatedMessage); threadInstance?.upsertReplyLocally({ message: updatedMessage }); - const data = await DBSyncManager.queueTask({ + const data = await DBSyncManager.queueTask({ client, task: { channelId: channel.id, @@ -1626,10 +1607,11 @@ const ChannelWithContext = < updateMessage({ ...data.message }); } } - }); + }, + ); - const deleteReaction: MessagesContextValue['deleteReaction'] = - useStableCallback(async (type: string, messageId: string) => { + const deleteReaction: MessagesContextValue['deleteReaction'] = useStableCallback( + async (type: string, messageId: string) => { if (!channel?.id || !client.user) { throw new Error('Channel has not been initialized'); } @@ -1650,7 +1632,7 @@ const ChannelWithContext = < copyMessagesStateFromChannel(channel); - await DBSyncManager.queueTask({ + await DBSyncManager.queueTask({ client, task: { channelId: channel.id, @@ -1660,12 +1642,13 @@ const ChannelWithContext = < type: 'delete-reaction', }, }); - }); + }, + ); /** * THREAD METHODS */ - const openThread: ThreadContextValue['openThread'] = useCallback( + const openThread: ThreadContextValue['openThread'] = useCallback( (message) => { setThread(message); @@ -1681,7 +1664,7 @@ const ChannelWithContext = < [channel, setThread], ); - const closeThread: ThreadContextValue['closeThread'] = useCallback(() => { + const closeThread: ThreadContextValue['closeThread'] = useCallback(() => { setThread(null); setThreadMessages([]); }, [setThread, setThreadMessages]); @@ -1689,10 +1672,7 @@ const ChannelWithContext = < // hard limit to prevent you from scrolling faster than 1 page per 2 seconds const loadMoreThreadFinished = useRef( debounce( - ( - newThreadHasMore: boolean, - updatedThreadMessages: ChannelState['threads'][string], - ) => { + (newThreadHasMore: boolean, updatedThreadMessages: ChannelState['threads'][string]) => { setThreadHasMore(newThreadHasMore); setThreadLoadingMore(false); setThreadMessages(updatedThreadMessages); @@ -1702,54 +1682,53 @@ const ChannelWithContext = < ), ).current; - const loadMoreThread: ThreadContextValue['loadMoreThread'] = - useStableCallback(async () => { - if (threadLoadingMore || !thread?.id) { - return; - } - setThreadLoadingMore(true); + const loadMoreThread: ThreadContextValue['loadMoreThread'] = useStableCallback(async () => { + if (threadLoadingMore || !thread?.id) { + return; + } + setThreadLoadingMore(true); - try { - if (channel) { - const parentID = thread.id; - - /** - * In the channel is re-initializing, then threads may get wiped out during the process - * (check `addMessagesSorted` method on channel.state). In those cases, we still want to - * preserve the messages on active thread, so lets simply copy messages from UI state to - * `channel.state`. - */ - channel.state.threads[parentID] = threadMessages; - const oldestMessageID = threadMessages?.[0]?.id; - - const limit = 50; - const queryResponse = await channel.getReplies(parentID, { - id_lt: oldestMessageID, - limit, - }); + try { + if (channel) { + const parentID = thread.id; + + /** + * In the channel is re-initializing, then threads may get wiped out during the process + * (check `addMessagesSorted` method on channel.state). In those cases, we still want to + * preserve the messages on active thread, so lets simply copy messages from UI state to + * `channel.state`. + */ + channel.state.threads[parentID] = threadMessages; + const oldestMessageID = threadMessages?.[0]?.id; + + const limit = 50; + const queryResponse = await channel.getReplies(parentID, { + id_lt: oldestMessageID, + limit, + }); - const updatedHasMore = queryResponse.messages.length === limit; - const updatedThreadMessages = channel.state.threads[parentID] || []; - loadMoreThreadFinished(updatedHasMore, updatedThreadMessages); - } - } catch (err) { - console.warn('Message pagination request failed with error', err); - if (err instanceof Error) { - setError(err); - } else { - setError(true); - } - setThreadLoadingMore(false); - throw err; + const updatedHasMore = queryResponse.messages.length === limit; + const updatedThreadMessages = channel.state.threads[parentID] || []; + loadMoreThreadFinished(updatedHasMore, updatedThreadMessages); } - }); + } catch (err) { + console.warn('Message pagination request failed with error', err); + if (err instanceof Error) { + setError(err); + } else { + setError(true); + } + setThreadLoadingMore(false); + throw err; + } + }); const ownCapabilitiesContext = useCreateOwnCapabilitiesContext({ channel, overrideCapabilities: overrideOwnCapabilities, }); - const channelContext = useCreateChannelContext({ + const channelContext = useCreateChannelContext({ channel, channelUnreadState, disabled: !!channel?.data?.frozen, @@ -1800,7 +1779,7 @@ const ChannelWithContext = < // return sendMessageRef.current(...args); // }, []); - const inputMessageInputContext = useCreateInputMessageInputContext({ + const inputMessageInputContext = useCreateInputMessageInputContext({ additionalTextInputProps, asyncMessagesLockDistance, asyncMessagesMinimumPressDuration, @@ -2036,14 +2015,14 @@ const ChannelWithContext = < keyboardVerticalOffset={keyboardVerticalOffset} {...additionalKeyboardAvoidingViewProps} > - value={channelContext}> + - value={typingContext}> - value={messageListContext}> - value={messagesContext}> - value={threadContext}> - value={suggestionsContext}> - value={inputMessageInputContext}> + + + + + + {children} @@ -2057,11 +2036,9 @@ const ChannelWithContext = < ); }; -export type ChannelProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial, 'channel' | 'thread'>> & - Pick, 'channel'> & { - thread?: MessageType | ThreadType | null; +export type ChannelProps = Partial> & + Pick & { + thread?: LocalMessage | ThreadType | null; }; /** @@ -2072,35 +2049,30 @@ export type ChannelProps< * * @example ./Channel.md */ -export const Channel = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: PropsWithChildren>, -) => { - const { client, enableOfflineSupport, isMessageAIGenerated } = - useChatContext(); +export const Channel = (props: PropsWithChildren) => { + const { client, enableOfflineSupport, isMessageAIGenerated } = useChatContext(); const { t } = useTranslationContext(); const threadFromProps = props?.thread; + const threadInstance = (threadFromProps as ThreadType)?.threadInstance as Thread; const threadMessage = ( - threadFromProps?.threadInstance ? threadFromProps.thread : threadFromProps - ) as MessageType; - const threadInstance = threadFromProps?.threadInstance as Thread; + threadInstance ? (threadFromProps as ThreadType).thread : threadFromProps + ) as LocalMessage; - const thread: ThreadType = { + const thread: ThreadType = { thread: threadMessage, threadInstance, }; const shouldSyncChannel = threadMessage?.id ? !!props.threadList : true; - const { setThreadMessages, threadMessages } = useChannelState( + const { setThreadMessages, threadMessages } = useChannelState( props.channel, props.threadList ? threadMessage?.id : undefined, ); return ( - + = { +export type ChannelMessagesState = { hasMore?: boolean; hasMoreNewer?: boolean; loading?: boolean; loadingMore?: boolean; loadingMoreRecent?: boolean; - messages?: StreamChannelState['messages']; - pinnedMessages?: StreamChannelState['pinnedMessages']; + messages?: StreamChannelState['messages']; + pinnedMessages?: StreamChannelState['pinnedMessages']; targetedMessageId?: string; }; /** * The ChannelThreadState object */ -export type ChannelThreadState< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - thread: MessageType | null; +export type ChannelThreadState = { + thread: LocalMessage | null; threadHasMore?: boolean; threadLoadingMore?: boolean; - threadMessages?: StreamChannelState['messages']; + threadMessages?: StreamChannelState['messages']; }; /** * The ChannelState object */ -export type ChannelState< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = ChannelMessagesState & { - members?: StreamChannelState['members']; - read?: StreamChannelState['read']; - typing?: StreamChannelState['typing']; +export type ChannelState = ChannelMessagesState & { + members?: StreamChannelState['members']; + read?: StreamChannelState['read']; + typing?: StreamChannelState['typing']; watcherCount?: number; - watchers?: StreamChannelState['watchers']; + watchers?: StreamChannelState['watchers']; }; /** * The useChannelMessageDataState hook that handles the state for the channel messages. */ -export const useChannelMessageDataState = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, -) => { - const [state, setState] = useState>({ +export const useChannelMessageDataState = (channel: Channel) => { + const [state, setState] = useState({ hasMore: true, hasMoreNewer: false, loading: false, @@ -81,7 +68,7 @@ export const useChannelMessageDataState = < targetedMessageId: undefined, }); - const copyMessagesStateFromChannel = useCallback((channel: Channel) => { + const copyMessagesStateFromChannel = useCallback((channel: Channel) => { setState((prev) => ({ ...prev, messages: [...channel.state.messages], @@ -89,18 +76,15 @@ export const useChannelMessageDataState = < })); }, []); - const loadInitialMessagesStateFromChannel = useCallback( - (channel: Channel, hasMore: boolean) => { - setState((prev) => ({ - ...prev, - hasMore, - loading: false, - messages: [...channel.state.messages], - pinnedMessages: [...channel.state.pinnedMessages], - })); - }, - [], - ); + const loadInitialMessagesStateFromChannel = useCallback((channel: Channel, hasMore: boolean) => { + setState((prev) => ({ + ...prev, + hasMore, + loading: false, + messages: [...channel.state.messages], + pinnedMessages: [...channel.state.pinnedMessages], + })); + }, []); const jumpToLatestMessage = useCallback(() => { setState((prev) => ({ @@ -120,17 +104,14 @@ export const useChannelMessageDataState = < })); }, []); - const loadMoreFinished = useCallback( - (hasMore: boolean, messages: ChannelState['messages']) => { - setState((prev) => ({ - ...prev, - hasMore, - loadingMore: false, - messages, - })); - }, - [], - ); + const loadMoreFinished = useCallback((hasMore: boolean, messages: ChannelState['messages']) => { + setState((prev) => ({ + ...prev, + hasMore, + loadingMore: false, + messages, + })); + }, []); const setLoadingMore = useCallback((loadingMore: boolean) => { setState((prev) => ({ @@ -154,7 +135,7 @@ export const useChannelMessageDataState = < }, []); const loadMoreRecentFinished = useCallback( - (hasMoreNewer: boolean, messages: ChannelState['messages']) => { + (hasMoreNewer: boolean, messages: ChannelState['messages']) => { setState((prev) => ({ ...prev, hasMoreNewer, @@ -182,12 +163,8 @@ export const useChannelMessageDataState = < /** * The useChannelThreadState hook that handles the state for the channel member, read, typing, watchers, etc. */ -export const useChannelDataState = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, -) => { - const [state, setState] = useState>({ +export const useChannelDataState = (channel: Channel) => { + const [state, setState] = useState({ members: channel.state.members, read: channel.state.read, typing: {}, @@ -196,7 +173,7 @@ export const useChannelDataState = < }); const initStateFromChannel = useCallback( - (channel: Channel) => { + (channel: Channel) => { setState({ ...state, members: { ...channel.state.members }, @@ -209,7 +186,7 @@ export const useChannelDataState = < [state], ); - const copyStateFromChannel = useCallback((channel: Channel) => { + const copyStateFromChannel = useCallback((channel: Channel) => { setState((prev) => ({ ...prev, members: { ...channel.state.members }, @@ -219,14 +196,14 @@ export const useChannelDataState = < })); }, []); - const setRead = useCallback((channel: Channel) => { + const setRead = useCallback((channel: Channel) => { setState((prev) => ({ ...prev, read: { ...channel.state.read }, // Synchronize the read state from the channel })); }, []); - const setTyping = useCallback((channel: Channel) => { + const setTyping = useCallback((channel: Channel) => { setState((prev) => ({ ...prev, typing: { ...channel.state.typing }, // Synchronize the typing state from the channel diff --git a/package/src/components/Channel/hooks/useCreateChannelContext.ts b/package/src/components/Channel/hooks/useCreateChannelContext.ts index 8b42af61b8..28549db215 100644 --- a/package/src/components/Channel/hooks/useCreateChannelContext.ts +++ b/package/src/components/Channel/hooks/useCreateChannelContext.ts @@ -1,11 +1,8 @@ import { useMemo } from 'react'; import type { ChannelContextValue } from '../../../contexts/channelContext/ChannelContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -export const useCreateChannelContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useCreateChannelContext = ({ channel, channelUnreadState, disabled, @@ -39,7 +36,7 @@ export const useCreateChannelContext = < uploadAbortControllerRef, watcherCount, watchers, -}: ChannelContextValue) => { +}: ChannelContextValue) => { const channelId = channel?.id; const lastReadTime = lastRead?.getTime(); const membersLength = Object.keys(members).length; @@ -49,7 +46,7 @@ export const useCreateChannelContext = < const readUsersLastReads = readUsers.map(({ last_read }) => last_read.toISOString()).join(); const stringifiedChannelUnreadState = JSON.stringify(channelUnreadState); - const channelContext: ChannelContextValue = useMemo( + const channelContext: ChannelContextValue = useMemo( () => ({ channel, channelUnreadState, diff --git a/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts b/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts index 949e8cf852..80e9154f94 100644 --- a/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts +++ b/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts @@ -1,11 +1,8 @@ import { useMemo } from 'react'; import type { InputMessageInputContextValue } from '../../../contexts/messageInputContext/MessageInputContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -export const useCreateInputMessageInputContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useCreateInputMessageInputContext = ({ additionalTextInputProps, asyncMessagesLockDistance, asyncMessagesMinimumPressDuration, @@ -66,7 +63,7 @@ export const useCreateInputMessageInputContext = < StartAudioRecordingButton, StopMessageStreamingButton, UploadProgressIndicator, -}: InputMessageInputContextValue & { +}: InputMessageInputContextValue & { /** * To ensure we allow re-render, when channel is changed */ @@ -75,7 +72,7 @@ export const useCreateInputMessageInputContext = < const editingDep = editing ? editing.id : ''; const quotedMessageId = quotedMessage ? quotedMessage.id : ''; - const inputMessageInputContext: InputMessageInputContextValue = useMemo( + const inputMessageInputContext: InputMessageInputContextValue = useMemo( () => ({ additionalTextInputProps, asyncMessagesLockDistance, diff --git a/package/src/components/Channel/hooks/useCreateMessagesContext.ts b/package/src/components/Channel/hooks/useCreateMessagesContext.ts index 2681c93474..b4ff6d4d9e 100644 --- a/package/src/components/Channel/hooks/useCreateMessagesContext.ts +++ b/package/src/components/Channel/hooks/useCreateMessagesContext.ts @@ -1,11 +1,8 @@ import { useMemo } from 'react'; import type { MessagesContextValue } from '../../../contexts/messagesContext/MessagesContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -export const useCreateMessagesContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useCreateMessagesContext = ({ additionalPressableProps, Attachment, AttachmentActions, @@ -112,7 +109,7 @@ export const useCreateMessagesContext = < updateMessage, UrlPreview, VideoThumbnail, -}: MessagesContextValue & { +}: MessagesContextValue & { /** * To ensure we allow re-render, when channel is changed */ @@ -123,7 +120,7 @@ export const useCreateMessagesContext = < const messageContentOrderValue = messageContentOrder.join(); const supportedReactionsLength = supportedReactions?.length; - const messagesContext: MessagesContextValue = useMemo( + const messagesContext: MessagesContextValue = useMemo( () => ({ additionalPressableProps, Attachment, diff --git a/package/src/components/Channel/hooks/useCreateOwnCapabilitiesContext.ts b/package/src/components/Channel/hooks/useCreateOwnCapabilitiesContext.ts index 2446129f26..4d5236656a 100644 --- a/package/src/components/Channel/hooks/useCreateOwnCapabilitiesContext.ts +++ b/package/src/components/Channel/hooks/useCreateOwnCapabilitiesContext.ts @@ -7,15 +7,12 @@ import { OwnCapabilitiesContextValue, OwnCapability, } from '../../../contexts/ownCapabilitiesContext/OwnCapabilitiesContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -export const useCreateOwnCapabilitiesContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useCreateOwnCapabilitiesContext = ({ channel, overrideCapabilities, }: { - channel: Channel; + channel: Channel; overrideCapabilities?: Partial; }) => { const [own_capabilities, setOwnCapabilites] = useState( diff --git a/package/src/components/Channel/hooks/useCreatePaginatedMessageListContext.ts b/package/src/components/Channel/hooks/useCreatePaginatedMessageListContext.ts index 1e8e46a613..920175183a 100644 --- a/package/src/components/Channel/hooks/useCreatePaginatedMessageListContext.ts +++ b/package/src/components/Channel/hooks/useCreatePaginatedMessageListContext.ts @@ -1,12 +1,10 @@ import { useMemo } from 'react'; import type { PaginatedMessageListContextValue } from '../../../contexts/paginatedMessageListContext/PaginatedMessageListContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { reduceMessagesToString } from '../../../utils/utils'; -export const useCreatePaginatedMessageListContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useCreatePaginatedMessageListContext = ({ channelId, hasMore, loadingMore, @@ -17,12 +15,12 @@ export const useCreatePaginatedMessageListContext = < messages, setLoadingMore, setLoadingMoreRecent, -}: PaginatedMessageListContextValue & { +}: PaginatedMessageListContextValue & { channelId?: string; }) => { const messagesStr = reduceMessagesToString(messages); - const paginatedMessagesContext: PaginatedMessageListContextValue = useMemo( + const paginatedMessagesContext: PaginatedMessageListContextValue = useMemo( () => ({ hasMore, loadingMore, diff --git a/package/src/components/Channel/hooks/useCreateThreadContext.ts b/package/src/components/Channel/hooks/useCreateThreadContext.ts index 098a9a814a..43addaafe8 100644 --- a/package/src/components/Channel/hooks/useCreateThreadContext.ts +++ b/package/src/components/Channel/hooks/useCreateThreadContext.ts @@ -2,7 +2,6 @@ import { ThreadState } from 'stream-chat'; import type { ThreadContextValue } from '../../../contexts/threadContext/ThreadContext'; import { useStateStore } from '../../../hooks'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; const selector = (nextValue: ThreadState) => ({ @@ -11,9 +10,7 @@ const selector = (nextValue: ThreadState) => latestReplies: nextValue.replies, }) as const; -export const useCreateThreadContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useCreateThreadContext = ({ allowThreadMessagesInChannel, closeThread, loadMoreThread, @@ -25,7 +22,7 @@ export const useCreateThreadContext = < threadInstance, threadLoadingMore, threadMessages, -}: ThreadContextValue) => { +}: ThreadContextValue) => { const { isLoadingNext, isLoadingPrev, latestReplies } = useStateStore(threadInstance?.state, selector) ?? {}; diff --git a/package/src/components/Channel/hooks/useCreateTypingContext.ts b/package/src/components/Channel/hooks/useCreateTypingContext.ts index 1205bf9e4d..960177b8de 100644 --- a/package/src/components/Channel/hooks/useCreateTypingContext.ts +++ b/package/src/components/Channel/hooks/useCreateTypingContext.ts @@ -1,16 +1,11 @@ import { useMemo } from 'react'; import type { TypingContextValue } from '../../../contexts/typingContext/TypingContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -export const useCreateTypingContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - typing, -}: TypingContextValue) => { +export const useCreateTypingContext = ({ typing }: TypingContextValue) => { const typingValue = Object.keys(typing).join(); - const typingContext: TypingContextValue = useMemo( + const typingContext: TypingContextValue = useMemo( () => ({ typing, }), diff --git a/package/src/components/Channel/hooks/useMessageListPagination.tsx b/package/src/components/Channel/hooks/useMessageListPagination.tsx index 29e7d62162..4c6e454e20 100644 --- a/package/src/components/Channel/hooks/useMessageListPagination.tsx +++ b/package/src/components/Channel/hooks/useMessageListPagination.tsx @@ -7,7 +7,6 @@ import { useChannelMessageDataState } from './useChannelDataState'; import { ChannelContextValue } from '../../../contexts/channelContext/ChannelContext'; import { useStableCallback } from '../../../hooks'; -import { DefaultStreamChatGenerics } from '../../../types/types'; import { findInMessagesByDate, findInMessagesById } from '../../../utils/utils'; const defaultDebounceInterval = 500; @@ -22,13 +21,7 @@ const debounceOptions = { * * @param channel The channel object for which the message list pagination is being handled. */ -export const useMessageListPagination = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - channel, -}: { - channel: Channel; -}) => { +export const useMessageListPagination = ({ channel }: { channel: Channel }) => { const { copyMessagesStateFromChannel, jumpToLatestMessage, @@ -40,12 +33,12 @@ export const useMessageListPagination = < setLoadingMore, setLoadingMoreRecent, state, - } = useChannelMessageDataState(channel); + } = useChannelMessageDataState(channel); // hard limit to prevent you from scrolling faster than 1 page per 2 seconds const loadMoreFinished = useRef( debounce( - (hasMore: boolean, messages: ChannelState['messages']) => { + (hasMore: boolean, messages: ChannelState['messages']) => { loadMoreFinishedFn(hasMore, messages); }, defaultDebounceInterval, @@ -56,7 +49,7 @@ export const useMessageListPagination = < // hard limit to prevent you from scrolling faster than 1 page per 2 seconds const loadMoreRecentFinished = useRef( debounce( - (hasMore: boolean, newMessages: ChannelState['messages']) => { + (hasMore: boolean, newMessages: ChannelState['messages']) => { loadMoreRecentFinishedFn(hasMore, newMessages); }, defaultDebounceInterval, @@ -141,7 +134,7 @@ export const useMessageListPagination = < * * @param messageId If undefined, channel will be loaded at most recent message. */ - const loadChannelAroundMessage: ChannelContextValue['loadChannelAroundMessage'] = + const loadChannelAroundMessage: ChannelContextValue['loadChannelAroundMessage'] = useStableCallback( async ({ limit = 25, messageId: messageIdToLoadAround, setTargetedMessage }) => { if (!messageIdToLoadAround) { @@ -173,11 +166,7 @@ export const useMessageListPagination = < * Fetch messages around a specific timestamp. */ const fetchMessagesAround = useStableCallback( - async ( - channel: Channel, - timestamp: string, - limit: number, - ): Promise[]> => { + async (channel: Channel, timestamp: string, limit: number): Promise => { try { const { messages } = await channel.query( { messages: { created_at_around: timestamp, limit } }, @@ -194,7 +183,7 @@ export const useMessageListPagination = < /** * Loads channel at first unread message. */ - const loadChannelAtFirstUnreadMessage: ChannelContextValue['loadChannelAtFirstUnreadMessage'] = + const loadChannelAtFirstUnreadMessage: ChannelContextValue['loadChannelAtFirstUnreadMessage'] = useStableCallback( async ({ channelUnreadState, limit = 25, setChannelUnreadState, setTargetedMessage }) => { try { diff --git a/package/src/components/ChannelList/ChannelList.tsx b/package/src/components/ChannelList/ChannelList.tsx index 9b52e80a02..083af190b8 100644 --- a/package/src/components/ChannelList/ChannelList.tsx +++ b/package/src/components/ChannelList/ChannelList.tsx @@ -20,16 +20,14 @@ import { } from '../../contexts/channelsContext/ChannelsContext'; import { useChatContext } from '../../contexts/chatContext/ChatContext'; import { upsertCidsForQuery } from '../../store/apis/upsertCidsForQuery'; -import type { ChannelListEventListenerOptions, DefaultStreamChatGenerics } from '../../types/types'; +import type { ChannelListEventListenerOptions } from '../../types/types'; import { ChannelPreviewMessenger } from '../ChannelPreview/ChannelPreviewMessenger'; import { EmptyStateIndicator as EmptyStateIndicatorDefault } from '../Indicators/EmptyStateIndicator'; import { LoadingErrorIndicator as LoadingErrorIndicatorDefault } from '../Indicators/LoadingErrorIndicator'; -export type ChannelListProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial< +export type ChannelListProps = Partial< Pick< - ChannelsContextValue, + ChannelsContextValue, | 'additionalFlatListProps' | 'EmptyStateIndicator' | 'FooterLoadingIndicator' @@ -54,9 +52,7 @@ export type ChannelListProps< > > & { /** Optional function to filter channels prior to rendering the list. Do not use any complex logic that would delay the loading of the ChannelList. We recommend using a pure function with array methods like filter/sort/reduce. */ - channelRenderFilterFn?: ( - channels: Array>, - ) => Array>; + channelRenderFilterFn?: (channels: Array) => Array; /** * Object containing channel query filters * @@ -64,13 +60,13 @@ export type ChannelListProps< * * @overrideType object * */ - filters?: ChannelFilters; + filters?: ChannelFilters; /** * Custom UI component to display the list of channels * * Default: [ChannelListMessenger](https://getstream.io/chat/docs/sdk/reactnative/ui-components/channel-list-messenger/) */ - List?: React.ComponentType>; + List?: React.ComponentType; /** * If set to true, channels won't dynamically sort by most recent message, defaults to false */ @@ -86,9 +82,9 @@ export type ChannelListProps< * @overrideType Function * */ onAddedToChannel?: ( - setChannels: React.Dispatch[]>>, - event: Event, - options?: ChannelListEventListenerOptions, + setChannels: React.Dispatch>, + event: Event, + options?: ChannelListEventListenerOptions, ) => void; /** * Function that overrides default behavior when a channel gets deleted. In absence of this prop, the channel will be removed from the list. @@ -99,8 +95,8 @@ export type ChannelListProps< * @overrideType Function * */ onChannelDeleted?: ( - setChannels: React.Dispatch[]>>, - event: Event, + setChannels: React.Dispatch>, + event: Event, ) => void; /** * Function that overrides default behavior when a channel gets hidden. In absence of this prop, the channel will be removed from the list. @@ -111,8 +107,8 @@ export type ChannelListProps< * @overrideType Function * */ onChannelHidden?: ( - setChannels: React.Dispatch[]>>, - event: Event, + setChannels: React.Dispatch>, + event: Event, ) => void; /** * Function that overrides default behavior when a channel member.updated event is triggered @@ -125,9 +121,9 @@ export type ChannelListProps< */ onChannelMemberUpdated?: ( lockChannelOrder: boolean, - setChannels: React.Dispatch[]>>, - event: Event, - options?: ChannelListEventListenerOptions, + setChannels: React.Dispatch>, + event: Event, + options?: ChannelListEventListenerOptions, ) => void; /** * Function to customize behavior when a channel gets truncated @@ -138,8 +134,8 @@ export type ChannelListProps< * @overrideType Function * */ onChannelTruncated?: ( - setChannels: React.Dispatch[]>>, - event: Event, + setChannels: React.Dispatch>, + event: Event, ) => void; /** * Function that overrides default behavior when a channel gets updated @@ -150,8 +146,8 @@ export type ChannelListProps< * @overrideType Function * */ onChannelUpdated?: ( - setChannels: React.Dispatch[]>>, - event: Event, + setChannels: React.Dispatch>, + event: Event, ) => void; /** * Function that overrides default behavior when a channel gets visible. In absence of this prop, the channel will be added to the list. @@ -162,8 +158,8 @@ export type ChannelListProps< * @overrideType Function * */ onChannelVisible?: ( - setChannels: React.Dispatch[]>>, - event: Event, + setChannels: React.Dispatch>, + event: Event, ) => void; /** * Override the default listener/handler for event `message.new` @@ -179,9 +175,9 @@ export type ChannelListProps< * */ onNewMessage?: ( lockChannelOrder: boolean, - setChannels: React.Dispatch[]>>, - event: Event, - options?: ChannelListEventListenerOptions, + setChannels: React.Dispatch>, + event: Event, + options?: ChannelListEventListenerOptions, ) => void; /** * Override the default listener/handler for event `notification.message_new` @@ -193,9 +189,9 @@ export type ChannelListProps< * @overrideType Function * */ onNewMessageNotification?: ( - setChannels: React.Dispatch[]>>, - event: Event, - options?: ChannelListEventListenerOptions, + setChannels: React.Dispatch>, + event: Event, + options?: ChannelListEventListenerOptions, ) => void; /** @@ -207,8 +203,8 @@ export type ChannelListProps< * @overrideType Function * */ onRemovedFromChannel?: ( - setChannels: React.Dispatch[]>>, - event: Event, + setChannels: React.Dispatch>, + event: Event, ) => void; /** * Object containing channel query options @@ -219,7 +215,7 @@ export type ChannelListProps< * Object containing channel sort parameters * @see See [Channel query documentation](https://getstream.io/chat/docs/query_channels) for a list of available sorting fields * */ - sort?: ChannelSort; + sort?: ChannelSort; }; const DEFAULT_FILTERS = {}; @@ -233,11 +229,7 @@ const DEFAULT_SORT = {}; * * @example ./ChannelList.md */ -export const ChannelList = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ChannelListProps, -) => { +export const ChannelList = (props: ChannelListProps) => { const { additionalFlatListProps = {}, channelRenderFilterFn, @@ -280,7 +272,7 @@ export const ChannelList = < } = props; const [forceUpdate, setForceUpdate] = useState(0); - const { client, enableOfflineSupport } = useChatContext(); + const { client, enableOfflineSupport } = useChatContext(); const channelManager = useMemo(() => client.createChannelManager({}), [client]); /** @@ -352,7 +344,7 @@ export const ChannelList = < refreshList, reloadList, staticChannelsActive, - } = usePaginatedChannels({ + } = usePaginatedChannels({ channelManager, enableOfflineSupport, filters, @@ -417,7 +409,7 @@ export const ChannelList = < refreshing, refreshList, reloadList, - setFlatListRef: (ref: FlatList> | null) => { + setFlatListRef: (ref: FlatList | null) => { if (setFlatListRef) { setFlatListRef(ref); } @@ -427,7 +419,7 @@ export const ChannelList = < return ( - /> + ); }; diff --git a/package/src/components/ChannelList/ChannelListMessenger.tsx b/package/src/components/ChannelList/ChannelListMessenger.tsx index 97d92ceedf..87d514e732 100644 --- a/package/src/components/ChannelList/ChannelListMessenger.tsx +++ b/package/src/components/ChannelList/ChannelListMessenger.tsx @@ -13,7 +13,6 @@ import { useChatContext } from '../../contexts/chatContext/ChatContext'; import { useDebugContext } from '../../contexts/debugContext/DebugContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { ChannelPreview } from '../ChannelPreview/ChannelPreview'; const styles = StyleSheet.create({ @@ -22,10 +21,8 @@ const styles = StyleSheet.create({ statusIndicator: { left: 0, position: 'absolute', right: 0, top: 0 }, }); -export type ChannelListMessengerPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Omit< - ChannelsContextValue, +export type ChannelListMessengerPropsWithContext = Omit< + ChannelsContextValue, | 'HeaderErrorIndicator' | 'HeaderNetworkDownIndicator' | 'maxUnreadCount' @@ -39,12 +36,10 @@ export type ChannelListMessengerPropsWithContext< | 'Skeleton' >; -const StatusIndicator = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const { isOnline } = useChatContext(); +const StatusIndicator = () => { + const { isOnline } = useChatContext(); const { error, HeaderErrorIndicator, HeaderNetworkDownIndicator, loadingChannels, refreshList } = - useChannelsContext(); + useChannelsContext(); if (loadingChannels) { return null; @@ -66,25 +61,11 @@ const StatusIndicator = < return null; }; -const renderItem = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - item, -}: { - item: Channel; -}) => channel={item} />; +const renderItem = ({ item }: { item: Channel }) => ; -const keyExtractor = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - item: Channel, -) => item.cid; +const keyExtractor = (item: Channel) => item.cid; -const ChannelListMessengerWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ChannelListMessengerPropsWithContext, -) => { +const ChannelListMessengerWithContext = (props: ChannelListMessengerPropsWithContext) => { const onEndReachedCalledDuringCurrentScrollRef = useRef(false); const { additionalFlatListProps, @@ -195,14 +176,12 @@ const ChannelListMessengerWithContext = < testID='channel-list-messenger' {...additionalFlatListProps} /> - /> + ); }; -export type ChannelListMessengerProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; +export type ChannelListMessengerProps = Partial; /** * This UI component displays the preview list of channels and handles Channel navigation. It @@ -210,11 +189,7 @@ export type ChannelListMessengerProps< * * @example ./ChannelListMessenger.md */ -export const ChannelListMessenger = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ChannelListMessengerProps, -) => { +export const ChannelListMessenger = (props: ChannelListMessengerProps) => { const { additionalFlatListProps, channelListInitialized, @@ -235,7 +210,7 @@ export const ChannelListMessenger = < refreshList, reloadList, setFlatListRef, - } = useChannelsContext(); + } = useChannelsContext(); return ( = - { - setChannels: React.Dispatch[] | null>>; - onAddedToChannel?: ( - setChannels: React.Dispatch[] | null>>, - event: Event, - options?: ChannelListEventListenerOptions, - ) => void; - options?: ChannelListEventListenerOptions; - }; - -export const useAddedToChannelNotification = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - onAddedToChannel, - options, - setChannels, -}: Parameters) => { - const { client } = useChatContext(); - - useEffect(() => { - const handleEvent = async (event: Event) => { - if (typeof onAddedToChannel === 'function') { - onAddedToChannel(setChannels, event, options); - } else { - if (!options) { - return; - } - const { sort } = options; - if (event.channel?.id && event.channel?.type) { - const channel = await getChannel({ - client, - id: event.channel.id, - type: event.channel.type, - }); - - const pinnedAtSort = findPinnedAtSortOrder({ sort }); - - setChannels((channels) => { - if (!channels) { - return channels; - } - - // handle pinning - let lastPinnedChannelIndex: number | null = null; - - const newChannels = [...channels]; - if (pinnedAtSort === 1 || pinnedAtSort === -1) { - lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels: newChannels }); - const newTargetChannelIndex = - typeof lastPinnedChannelIndex === 'number' ? lastPinnedChannelIndex + 1 : 0; - - newChannels.splice(newTargetChannelIndex, 0, channel); - return newChannels; - } - - return uniqBy([channel, ...channels], 'cid'); - }); - } - } - }; - - const listener = client?.on('notification.added_to_channel', handleEvent); - return () => listener?.unsubscribe(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); -}; diff --git a/package/src/components/ChannelList/hooks/listeners/useChannelDeleted.ts b/package/src/components/ChannelList/hooks/listeners/useChannelDeleted.ts deleted file mode 100644 index 98cddfe56f..0000000000 --- a/package/src/components/ChannelList/hooks/listeners/useChannelDeleted.ts +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useEffect } from 'react'; - -import type { Channel, Event } from 'stream-chat'; - -import { useChatContext } from '../../../../contexts/chatContext/ChatContext'; - -import type { DefaultStreamChatGenerics } from '../../../../types/types'; - -type Parameters = - { - setChannels: React.Dispatch[] | null>>; - onChannelDeleted?: ( - setChannels: React.Dispatch[] | null>>, - event: Event, - ) => void; - }; - -export const useChannelDeleted = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - onChannelDeleted, - setChannels, -}: Parameters) => { - const { client } = useChatContext(); - - useEffect(() => { - const handleEvent = (event: Event) => { - if (typeof onChannelDeleted === 'function') { - onChannelDeleted(setChannels, event); - } else { - setChannels((channels) => { - if (!channels) { - return channels; - } - const index = channels.findIndex( - (channel) => channel.cid === (event.cid || event.channel?.cid), - ); - if (index >= 0) { - channels.splice(index, 1); - } - return [...channels]; - }); - } - }; - - const listener = client?.on('channel.deleted', handleEvent); - return () => listener?.unsubscribe(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); -}; diff --git a/package/src/components/ChannelList/hooks/listeners/useChannelHidden.ts b/package/src/components/ChannelList/hooks/listeners/useChannelHidden.ts deleted file mode 100644 index a132cf3c57..0000000000 --- a/package/src/components/ChannelList/hooks/listeners/useChannelHidden.ts +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useEffect } from 'react'; - -import type { Channel, Event } from 'stream-chat'; - -import { useChatContext } from '../../../../contexts/chatContext/ChatContext'; - -import type { DefaultStreamChatGenerics } from '../../../../types/types'; - -type Parameters = - { - setChannels: React.Dispatch[] | null>>; - onChannelHidden?: ( - setChannels: React.Dispatch[] | null>>, - event: Event, - ) => void; - }; - -export const useChannelHidden = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - onChannelHidden, - setChannels, -}: Parameters) => { - const { client } = useChatContext(); - - useEffect(() => { - const handleEvent = (event: Event) => { - if (typeof onChannelHidden === 'function') { - onChannelHidden(setChannels, event); - } else { - setChannels((channels) => { - if (!channels) { - return channels; - } - - const index = channels.findIndex( - (channel) => channel.cid === (event.cid || event.channel?.cid), - ); - if (index >= 0) { - channels.splice(index, 1); - } - return [...channels]; - }); - } - }; - - const listener = client?.on('channel.hidden', handleEvent); - return () => listener?.unsubscribe(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); -}; diff --git a/package/src/components/ChannelList/hooks/listeners/useChannelMemberUpdated.ts b/package/src/components/ChannelList/hooks/listeners/useChannelMemberUpdated.ts deleted file mode 100644 index 183b49e21b..0000000000 --- a/package/src/components/ChannelList/hooks/listeners/useChannelMemberUpdated.ts +++ /dev/null @@ -1,120 +0,0 @@ -import React, { useEffect } from 'react'; - -import type { Channel, Event } from 'stream-chat'; - -import { useChatContext } from '../../../../contexts/chatContext/ChatContext'; - -import type { - ChannelListEventListenerOptions, - DefaultStreamChatGenerics, -} from '../../../../types/types'; -import { - findLastPinnedChannelIndex, - findPinnedAtSortOrder, - isChannelArchived, - isChannelPinned, - shouldConsiderArchivedChannels, - shouldConsiderPinnedChannels, -} from '../utils'; - -type Parameters = - { - lockChannelOrder: boolean; - setChannels: React.Dispatch[]>>; - onChannelMemberUpdated?: ( - lockChannelOrder: boolean, - setChannels: React.Dispatch[]>>, - event: Event, - options?: ChannelListEventListenerOptions, - ) => void; - options?: ChannelListEventListenerOptions; - }; - -export const useChannelMemberUpdated = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - lockChannelOrder, - onChannelMemberUpdated, - options, - setChannels, -}: Parameters) => { - const { client } = useChatContext(); - - useEffect(() => { - const handleEvent = (event: Event) => { - if (typeof onChannelMemberUpdated === 'function') { - onChannelMemberUpdated(lockChannelOrder, setChannels, event, options); - } else { - if (!options) { - return; - } - const { filters, sort } = options; - if (!event.member?.user || event.member.user.id !== client.userID || !event.channel_type) { - return; - } - const channelType = event.channel_type; - const channelId = event.channel_id; - - const considerPinnedChannels = shouldConsiderPinnedChannels(sort); - const considerArchivedChannels = shouldConsiderArchivedChannels(filters); - const pinnedAtSort = findPinnedAtSortOrder({ sort }); - - setChannels((currentChannels) => { - if (!currentChannels) { - return currentChannels; - } - - const targetChannel = client.channel(channelType, channelId); - // assumes that channel instances are not changing - const targetChannelIndex = currentChannels.indexOf(targetChannel); - const targetChannelExistsWithinList = targetChannelIndex >= 0; - - const isTargetChannelPinned = isChannelPinned(targetChannel); - const isTargetChannelArchived = isChannelArchived(targetChannel); - - if (!considerPinnedChannels || lockChannelOrder) { - return currentChannels; - } - - const newChannels = [...currentChannels]; - - if (targetChannelExistsWithinList) { - newChannels.splice(targetChannelIndex, 1); - } - - // handle archiving (remove channel) - if ( - // When archived filter true, and channel is unarchived - (considerArchivedChannels && !isTargetChannelArchived && filters?.archived) || - // When archived filter false, and channel is archived - (considerArchivedChannels && isTargetChannelArchived && !filters?.archived) - ) { - return newChannels; - } - - // handle pinning - let lastPinnedChannelIndex: number | null = null; - - if (pinnedAtSort === 1 || (pinnedAtSort === -1 && !isTargetChannelPinned)) { - lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels: newChannels }); - } - const newTargetChannelIndex = - typeof lastPinnedChannelIndex === 'number' ? lastPinnedChannelIndex + 1 : 0; - - // skip re-render if the position of the channel does not change - if (currentChannels[newTargetChannelIndex] === targetChannel) { - return currentChannels; - } - - newChannels.splice(newTargetChannelIndex, 0, targetChannel); - - return newChannels; - }); - } - }; - - const listener = client?.on('member.updated', handleEvent); - return () => listener?.unsubscribe(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); -}; diff --git a/package/src/components/ChannelList/hooks/listeners/useChannelTruncated.ts b/package/src/components/ChannelList/hooks/listeners/useChannelTruncated.ts deleted file mode 100644 index cbaa8b5ddd..0000000000 --- a/package/src/components/ChannelList/hooks/listeners/useChannelTruncated.ts +++ /dev/null @@ -1,43 +0,0 @@ -import React, { useEffect } from 'react'; - -import type { Channel, Event } from 'stream-chat'; - -import { useChatContext } from '../../../../contexts/chatContext/ChatContext'; - -import type { DefaultStreamChatGenerics } from '../../../../types/types'; - -type Parameters = - { - refreshList: () => void; - setChannels: React.Dispatch[] | null>>; - setForceUpdate: React.Dispatch>; - onChannelTruncated?: ( - setChannels: React.Dispatch[] | null>>, - event: Event, - ) => void; - }; - -export const useChannelTruncated = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - onChannelTruncated, - refreshList, - setChannels, - setForceUpdate, -}: Parameters) => { - const { client } = useChatContext(); - - useEffect(() => { - const handleEvent = (event: Event) => { - if (typeof onChannelTruncated === 'function') { - onChannelTruncated(setChannels, event); - } - refreshList(); - setForceUpdate((count) => count + 1); - }; - - const listener = client?.on('channel.truncated', handleEvent); - return () => listener?.unsubscribe(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); -}; diff --git a/package/src/components/ChannelList/hooks/listeners/useChannelUpdated.ts b/package/src/components/ChannelList/hooks/listeners/useChannelUpdated.ts index a1c7ba8fa8..db078b626f 100644 --- a/package/src/components/ChannelList/hooks/listeners/useChannelUpdated.ts +++ b/package/src/components/ChannelList/hooks/listeners/useChannelUpdated.ts @@ -4,27 +4,19 @@ import type { Channel, Event } from 'stream-chat'; import { useChatContext } from '../../../../contexts/chatContext/ChatContext'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; - -type Parameters = - { - setChannels: React.Dispatch[]>>; - onChannelUpdated?: ( - setChannels: React.Dispatch[]>>, - event: Event, - ) => void; - }; - -export const useChannelUpdated = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - onChannelUpdated, - setChannels, -}: Parameters) => { - const { client } = useChatContext(); +type Parameters = { + setChannels: React.Dispatch>; + onChannelUpdated?: ( + setChannels: React.Dispatch>, + event: Event, + ) => void; +}; + +export const useChannelUpdated = ({ onChannelUpdated, setChannels }: Parameters) => { + const { client } = useChatContext(); useEffect(() => { - const handleEvent = (event: Event) => { + const handleEvent = (event: Event) => { if (typeof onChannelUpdated === 'function') { onChannelUpdated(setChannels, event); } else { diff --git a/package/src/components/ChannelList/hooks/listeners/useChannelVisible.ts b/package/src/components/ChannelList/hooks/listeners/useChannelVisible.ts deleted file mode 100644 index 4b0a03b7f2..0000000000 --- a/package/src/components/ChannelList/hooks/listeners/useChannelVisible.ts +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useEffect } from 'react'; - -import type { Channel, Event } from 'stream-chat'; - -import { useChatContext } from '../../../../contexts/chatContext/ChatContext'; - -import type { - ChannelListEventListenerOptions, - DefaultStreamChatGenerics, -} from '../../../../types/types'; -import { getChannel, moveChannelUp } from '../../utils'; - -type Parameters = - { - setChannels: React.Dispatch[] | null>>; - onChannelVisible?: ( - setChannels: React.Dispatch[] | null>>, - event: Event, - options?: ChannelListEventListenerOptions, - ) => void; - options?: ChannelListEventListenerOptions; - }; - -export const useChannelVisible = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - onChannelVisible, - options, - setChannels, -}: Parameters) => { - const { client } = useChatContext(); - - useEffect(() => { - const handleEvent = async (event: Event) => { - if (typeof onChannelVisible === 'function') { - onChannelVisible(setChannels, event); - } else { - if (!options) { - return; - } - const { sort } = options; - if (event.channel_id && event.channel_type) { - const channel = await getChannel({ - client, - id: event.channel_id, - type: event.channel_type, - }); - setChannels((channels) => - channels - ? moveChannelUp({ - channels, - channelToMove: channel, - sort, - }) - : channels, - ); - } - } - }; - - const listener = client?.on('channel.visible', handleEvent); - return () => listener?.unsubscribe(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); -}; diff --git a/package/src/components/ChannelList/hooks/listeners/useNewMessage.ts b/package/src/components/ChannelList/hooks/listeners/useNewMessage.ts deleted file mode 100644 index 0ee797f6df..0000000000 --- a/package/src/components/ChannelList/hooks/listeners/useNewMessage.ts +++ /dev/null @@ -1,97 +0,0 @@ -import React, { useEffect } from 'react'; - -import type { Channel, Event } from 'stream-chat'; - -import { useChatContext } from '../../../../contexts/chatContext/ChatContext'; - -import type { - ChannelListEventListenerOptions, - DefaultStreamChatGenerics, -} from '../../../../types/types'; -import { moveChannelUp } from '../../utils'; -import { - isChannelArchived, - isChannelPinned, - shouldConsiderArchivedChannels, - shouldConsiderPinnedChannels, -} from '../utils'; - -type Parameters = - { - lockChannelOrder: boolean; - setChannels: React.Dispatch[] | null>>; - onNewMessage?: ( - lockChannelOrder: boolean, - setChannels: React.Dispatch[] | null>>, - event: Event, - options?: ChannelListEventListenerOptions, - ) => void; - options?: ChannelListEventListenerOptions; - }; - -export const useNewMessage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - lockChannelOrder, - onNewMessage, - options, - setChannels, -}: Parameters) => { - const { client } = useChatContext(); - - useEffect(() => { - const handleEvent = (event: Event) => { - if (typeof onNewMessage === 'function') { - onNewMessage(lockChannelOrder, setChannels, event, options); - } else { - if (!options) { - return; - } - const { filters, sort } = options; - const considerPinnedChannels = shouldConsiderPinnedChannels(sort); - const considerArchivedChannels = shouldConsiderArchivedChannels(filters); - - const channelType = event.channel_type; - const channelId = event.channel_id; - - if (!channelType || !channelId) { - return; - } - - setChannels((channels) => { - if (!channels) { - return channels; - } - const targetChannel = client.channel(channelType, channelId); - const targetChannelIndex = channels.indexOf(targetChannel); - - const isTargetChannelArchived = isChannelArchived(targetChannel); - const isTargetChannelPinned = isChannelPinned(targetChannel); - - if ( - // When archived filter false, and channel is archived - (considerArchivedChannels && isTargetChannelArchived && !filters?.archived) || - // When archived filter true, and channel is unarchived - (considerArchivedChannels && !isTargetChannelArchived && filters?.archived) || - // If the channel is pinned and we are not considering pinned channels - (isTargetChannelPinned && considerPinnedChannels) || - lockChannelOrder - ) { - return [...channels]; - } - - return moveChannelUp({ - channels, - channelToMove: targetChannel, - channelToMoveIndexWithinChannels: targetChannelIndex, - sort, - }); - }); - } - }; - - const listener = client?.on('message.new', handleEvent); - return () => listener?.unsubscribe(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); -}; diff --git a/package/src/components/ChannelList/hooks/listeners/useNewMessageNotification.ts b/package/src/components/ChannelList/hooks/listeners/useNewMessageNotification.ts deleted file mode 100644 index b3e4bf2ddf..0000000000 --- a/package/src/components/ChannelList/hooks/listeners/useNewMessageNotification.ts +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useEffect } from 'react'; - -import type { Channel, Event } from 'stream-chat'; - -import { useChatContext } from '../../../../contexts/chatContext/ChatContext'; - -import type { - ChannelListEventListenerOptions, - DefaultStreamChatGenerics, -} from '../../../../types/types'; -import { getChannel, moveChannelUp } from '../../utils'; -import { isChannelArchived } from '../utils'; - -type Parameters = - { - setChannels: React.Dispatch[] | null>>; - onNewMessageNotification?: ( - setChannels: React.Dispatch[] | null>>, - event: Event, - options?: ChannelListEventListenerOptions, - ) => void; - options?: ChannelListEventListenerOptions; - }; - -export const useNewMessageNotification = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - onNewMessageNotification, - options, - setChannels, -}: Parameters) => { - const { client } = useChatContext(); - - useEffect(() => { - const handleEvent = async (event: Event) => { - if (typeof onNewMessageNotification === 'function') { - onNewMessageNotification(setChannels, event, options); - } else { - if (!options) { - return; - } - const { filters, sort } = options; - if (event.channel?.id && event.channel?.type) { - const channel = await getChannel({ - client, - id: event.channel.id, - type: event.channel.type, - }); - - // Handle archived channels - const considerArchivedChannels = filters && filters.archived === false; - if (isChannelArchived(channel) && considerArchivedChannels) { - return; - } - - setChannels((channels) => - channels - ? moveChannelUp({ - channels, - channelToMove: channel, - sort, - }) - : channels, - ); - } - } - }; - - const listener = client?.on('notification.message_new', handleEvent); - return () => listener?.unsubscribe(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); -}; diff --git a/package/src/components/ChannelList/hooks/listeners/useRemovedFromChannelNotification.ts b/package/src/components/ChannelList/hooks/listeners/useRemovedFromChannelNotification.ts deleted file mode 100644 index d7a8840d3b..0000000000 --- a/package/src/components/ChannelList/hooks/listeners/useRemovedFromChannelNotification.ts +++ /dev/null @@ -1,46 +0,0 @@ -import React, { useEffect } from 'react'; - -import type { Channel, Event } from 'stream-chat'; - -import { useChatContext } from '../../../../contexts/chatContext/ChatContext'; - -import type { DefaultStreamChatGenerics } from '../../../../types/types'; - -type Parameters = - { - setChannels: React.Dispatch[] | null>>; - onRemovedFromChannel?: ( - setChannels: React.Dispatch[] | null>>, - event: Event, - ) => void; - }; - -export const useRemovedFromChannelNotification = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - onRemovedFromChannel, - setChannels, -}: Parameters) => { - const { client } = useChatContext(); - - useEffect(() => { - const handleEvent = (event: Event) => { - if (typeof onRemovedFromChannel === 'function') { - onRemovedFromChannel(setChannels, event); - } else { - setChannels((channels) => { - if (!channels) { - return channels; - } - - const newChannels = channels.filter((channel) => channel.cid !== event.channel?.cid); - return [...newChannels]; - }); - } - }; - - const listener = client?.on('notification.removed_from_channel', handleEvent); - return () => listener?.unsubscribe(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); -}; diff --git a/package/src/components/ChannelList/hooks/listeners/useUserPresence.ts b/package/src/components/ChannelList/hooks/listeners/useUserPresence.ts deleted file mode 100644 index d44c6bf022..0000000000 --- a/package/src/components/ChannelList/hooks/listeners/useUserPresence.ts +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useEffect } from 'react'; - -import type { Channel, Event } from 'stream-chat'; - -import { useChatContext } from '../../../../contexts/chatContext/ChatContext'; - -import type { DefaultStreamChatGenerics } from '../../../../types/types'; - -type Parameters = - { - setChannels: React.Dispatch[]>>; - setForceUpdate: React.Dispatch>; - }; - -/** - * Hook to update the channel members when the user presence changes - * @deprecated this hook will be removed in favour of the useChannelPreviewDisplayPresence to improve performance - */ -export const useUserPresence = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - setChannels, - setForceUpdate, -}: Parameters) => { - const { client } = useChatContext(); - - useEffect(() => { - const handleEvent = (event: Event) => { - setChannels((channels) => { - if (!channels) { - return channels; - } - - const newChannels = channels.map((channel) => { - if (!event.user?.id || !channel.state.members[event.user.id]) { - return channel; - } - channel.state.members[event.user.id].user = event.user; - return channel; - }); - - return [...newChannels]; - }); - setForceUpdate((u) => u + 1); - }; - - const listeners = [ - client?.on('user.presence.changed', handleEvent), - client?.on('user.updated', handleEvent), - ]; - - return () => { - listeners?.forEach((l) => l?.unsubscribe()); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); -}; diff --git a/package/src/components/ChannelList/hooks/useChannelMembershipState.ts b/package/src/components/ChannelList/hooks/useChannelMembershipState.ts index b95b646a02..a483fd3749 100644 --- a/package/src/components/ChannelList/hooks/useChannelMembershipState.ts +++ b/package/src/components/ChannelList/hooks/useChannelMembershipState.ts @@ -2,21 +2,11 @@ import { Channel, ChannelMemberResponse, EventTypes } from 'stream-chat'; import { useSelectedChannelState } from '../../../hooks/useSelectedChannelState'; -import { DefaultStreamChatGenerics } from '../../../types/types'; - -const selector = ( - channel: Channel, -) => channel.state.membership; +const selector = (channel: Channel) => channel.state.membership; const keys: EventTypes[] = ['member.updated']; -export function useChannelMembershipState( - channel: Channel, -): ChannelMemberResponse; -export function useChannelMembershipState( - channel?: Channel, -): ChannelMemberResponse | undefined; -export function useChannelMembershipState( - channel?: Channel, -) { +export function useChannelMembershipState(channel: Channel): ChannelMemberResponse; +export function useChannelMembershipState(channel?: Channel): ChannelMemberResponse | undefined; +export function useChannelMembershipState(channel?: Channel) { return useSelectedChannelState({ channel, selector, stateChangeEventKeys: keys }); } diff --git a/package/src/components/ChannelList/hooks/useCreateChannelsContext.ts b/package/src/components/ChannelList/hooks/useCreateChannelsContext.ts index 0e652fed3a..dbe7e8c554 100644 --- a/package/src/components/ChannelList/hooks/useCreateChannelsContext.ts +++ b/package/src/components/ChannelList/hooks/useCreateChannelsContext.ts @@ -1,11 +1,8 @@ import { useMemo } from 'react'; import type { ChannelsContextValue } from '../../../contexts/channelsContext/ChannelsContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -export const useCreateChannelsContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useCreateChannelsContext = ({ additionalFlatListProps, channelListInitialized, channels, @@ -38,7 +35,7 @@ export const useCreateChannelsContext = < reloadList, setFlatListRef, Skeleton, -}: ChannelsContextValue) => { +}: ChannelsContextValue) => { const channelValueString = channels ?.map( (channel) => @@ -49,7 +46,7 @@ export const useCreateChannelsContext = < .join()}`, ) .join(); - const channelsContext: ChannelsContextValue = useMemo( + const channelsContext: ChannelsContextValue = useMemo( () => ({ additionalFlatListProps, channelListInitialized, diff --git a/package/src/components/ChannelList/hooks/usePaginatedChannels.ts b/package/src/components/ChannelList/hooks/usePaginatedChannels.ts index 30213cace5..d0ef342002 100644 --- a/package/src/components/ChannelList/hooks/usePaginatedChannels.ts +++ b/package/src/components/ChannelList/hooks/usePaginatedChannels.ts @@ -14,7 +14,7 @@ import { useStateStore } from '../../../hooks'; import { useIsMountedRef } from '../../../hooks/useIsMountedRef'; import { getChannelsForFilterSort } from '../../../store/apis/getChannelsForFilterSort'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { ONE_SECOND_IN_MS } from '../../../utils/date'; import { DBSyncManager } from '../../../utils/DBSyncManager'; import { MAX_QUERY_CHANNELS_LIMIT } from '../utils'; @@ -24,15 +24,14 @@ const waitSeconds = (seconds: number) => setTimeout(resolve, seconds * ONE_SECOND_IN_MS); }); -type Parameters = - { - channelManager: ChannelManager; - enableOfflineSupport: boolean; - filters: ChannelFilters; - options: ChannelOptions; - setForceUpdate: React.Dispatch>; - sort: ChannelSort; - }; +type Parameters = { + channelManager: ChannelManager; + enableOfflineSupport: boolean; + filters: ChannelFilters; + options: ChannelOptions; + setForceUpdate: React.Dispatch>; + sort: ChannelSort; +}; const DEFAULT_OPTIONS = { message_limit: 10, @@ -45,31 +44,27 @@ type QueryType = 'queryLocalDB' | 'reload' | 'refresh' | 'loadChannels'; export type QueryChannels = (queryType?: QueryType, retryCount?: number) => Promise; -const selector = ( - nextValue: ChannelManagerState, -) => +const selector = (nextValue: ChannelManagerState) => ({ channelListInitialized: nextValue.initialized, channels: nextValue.channels, pagination: nextValue.pagination, }) as const; -export const usePaginatedChannels = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const usePaginatedChannels = ({ channelManager, enableOfflineSupport, filters = {}, options = DEFAULT_OPTIONS, setForceUpdate, sort = {}, -}: Parameters) => { +}: Parameters) => { const [error, setError] = useState(undefined); const [staticChannelsActive, setStaticChannelsActive] = useState(false); const [activeQueryType, setActiveQueryType] = useState('queryLocalDB'); const activeChannels = useActiveChannelsRefContext(); const isMountedRef = useIsMountedRef(); - const { client } = useChatContext(); + const { client } = useChatContext(); const { channelListInitialized, channels, pagination } = useStateStore(channelManager?.state, selector) ?? {}; const hasNextPage = pagination?.hasNext; diff --git a/package/src/components/ChannelList/hooks/utils/index.ts b/package/src/components/ChannelList/hooks/utils/index.ts index 087b0f6fcd..8d6289b7f0 100644 --- a/package/src/components/ChannelList/hooks/utils/index.ts +++ b/package/src/components/ChannelList/hooks/utils/index.ts @@ -1,13 +1,8 @@ import { Channel, ChannelSortBase } from 'stream-chat'; -import { DefaultStreamChatGenerics } from '../../../../types/types'; import { ChannelListProps } from '../../ChannelList'; -export const isChannelPinned = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, -) => { +export const isChannelPinned = (channel: Channel) => { if (!channel) { return false; } @@ -17,11 +12,7 @@ export const isChannelPinned = < return !!member?.pinned_at; }; -export const isChannelArchived = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, -) => { +export const isChannelArchived = (channel: Channel) => { if (!channel) { return false; } @@ -31,11 +22,7 @@ export const isChannelArchived = < return !!member?.archived_at; }; -export const shouldConsiderArchivedChannels = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - filters: ChannelListProps['filters'], -) => { +export const shouldConsiderArchivedChannels = (filters: ChannelListProps['filters']) => { if (!filters) { return false; } @@ -43,21 +30,19 @@ export const shouldConsiderArchivedChannels = < return typeof filters.archived === 'boolean'; }; -export const extractSortValue = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const extractSortValue = ({ atIndex, sort, targetKey, }: { atIndex: number; - targetKey: keyof ChannelSortBase; - sort?: ChannelListProps['sort']; + targetKey: keyof ChannelSortBase; + sort?: ChannelListProps['sort']; }) => { if (!sort) { return null; } - let option: null | ChannelSortBase = null; + let option: null | ChannelSortBase = null; if (Array.isArray(sort)) { option = sort[atIndex] ?? null; @@ -85,11 +70,7 @@ export const extractSortValue = < /** * Returns true only if `{ pinned_at: -1 }` or `{ pinned_at: 1 }` option is first within the `sort` array. */ -export const shouldConsiderPinnedChannels = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - sort: ChannelListProps['sort'], -) => { +export const shouldConsiderPinnedChannels = (sort: ChannelListProps['sort']) => { const value = extractSortValue({ atIndex: 0, sort, @@ -103,9 +84,7 @@ export const shouldConsiderPinnedChannels = < return Math.abs(value) === 1; }; -export function findPinnedAtSortOrder< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ sort }: { sort: ChannelListProps['sort'] }) { +export function findPinnedAtSortOrder({ sort }: { sort: ChannelListProps['sort'] }) { if (!sort) { return null; } @@ -127,9 +106,7 @@ export function findPinnedAtSortOrder< } } -export function findLastPinnedChannelIndex< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ channels }: { channels: Channel[] }) { +export function findLastPinnedChannelIndex({ channels }: { channels: Channel[] }) { let lastPinnedChannelIndex: number | null = null; for (const channel of channels) { diff --git a/package/src/components/ChannelList/utils.ts b/package/src/components/ChannelList/utils.ts index 7abb355be5..706fd9d1a3 100644 --- a/package/src/components/ChannelList/utils.ts +++ b/package/src/components/ChannelList/utils.ts @@ -2,29 +2,23 @@ import type { Channel, ChannelSort, StreamChat } from 'stream-chat'; import { findLastPinnedChannelIndex, shouldConsiderPinnedChannels } from './hooks/utils'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - -type MoveParameters< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - channels: Array>; - channelToMove: Channel; +type MoveParameters = { + channels: Array; + channelToMove: Channel; /** * If the index of the channel within `channels` list which is being moved upwards * (`channelToMove`) is known, you can supply it to skip extra calculation. */ channelToMoveIndexWithinChannels?: number; - sort?: ChannelSort; + sort?: ChannelSort; }; -export const moveChannelUp = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const moveChannelUp = ({ channels, channelToMove, channelToMoveIndexWithinChannels, sort, -}: MoveParameters) => { +}: MoveParameters) => { // get index of channel to move up const targetChannelIndex = channelToMoveIndexWithinChannels ?? @@ -66,21 +60,13 @@ export const moveChannelUp = < return newChannels; }; -type GetParameters< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - client: StreamChat; +type GetParameters = { + client: StreamChat; id: string; type: string; }; -export const getChannel = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - client, - id, - type, -}: GetParameters) => { +export const getChannel = async ({ client, id, type }: GetParameters) => { const channel = client.channel(type, id); await channel.watch(); return channel; diff --git a/package/src/components/ChannelPreview/ChannelAvatar.tsx b/package/src/components/ChannelPreview/ChannelAvatar.tsx index 5216393e39..e38b3e428e 100644 --- a/package/src/components/ChannelPreview/ChannelAvatar.tsx +++ b/package/src/components/ChannelPreview/ChannelAvatar.tsx @@ -6,13 +6,11 @@ import { useChannelPreviewDisplayPresence } from './hooks/useChannelPreviewDispl import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; + import { Avatar } from '../Avatar/Avatar'; import { GroupAvatar } from '../Avatar/GroupAvatar'; -export type ChannelAvatarProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'channel'> & { +export type ChannelAvatarProps = Pick & { /** * The size of the avatar. */ @@ -22,10 +20,8 @@ export type ChannelAvatarProps< /** * This UI component displays an avatar for a particular channel. */ -export const ChannelAvatarWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ChannelAvatarProps & Pick, +export const ChannelAvatarWithContext = ( + props: ChannelAvatarProps & Pick, ) => { const { channel, ImageComponent, size: propSize } = props; const { @@ -63,12 +59,8 @@ export const ChannelAvatarWithContext = < ); }; -export const ChannelAvatar = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ChannelAvatarProps, -) => { - const { ImageComponent } = useChatContext(); +export const ChannelAvatar = (props: ChannelAvatarProps) => { + const { ImageComponent } = useChatContext(); return ; }; diff --git a/package/src/components/ChannelPreview/ChannelPreview.tsx b/package/src/components/ChannelPreview/ChannelPreview.tsx index fb8edbbed9..003c9c6875 100644 --- a/package/src/components/ChannelPreview/ChannelPreview.tsx +++ b/package/src/components/ChannelPreview/ChannelPreview.tsx @@ -10,27 +10,19 @@ import { } from '../../contexts/channelsContext/ChannelsContext'; import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - -export type ChannelPreviewProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial, 'client'>> & - Partial, 'Preview' | 'forceUpdate'>> & { +export type ChannelPreviewProps = Partial> & + Partial> & { /** * Instance of Channel from stream-chat package. */ - channel: Channel; + channel: Channel; }; -export const ChannelPreview = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ChannelPreviewProps, -) => { +export const ChannelPreview = (props: ChannelPreviewProps) => { const { channel, client: propClient, forceUpdate: propForceUpdate, Preview: propPreview } = props; - const { client: contextClient } = useChatContext(); - const { Preview: contextPreview } = useChannelsContext(); + const { client: contextClient } = useChatContext(); + const { Preview: contextPreview } = useChannelsContext(); const client = propClient || contextClient; const Preview = propPreview || contextPreview; diff --git a/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx b/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx index ed623715a7..5b0346640a 100644 --- a/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx +++ b/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx @@ -5,8 +5,6 @@ import type { LatestMessagePreview } from './hooks/useLatestMessagePreview'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - const styles = StyleSheet.create({ bold: { fontWeight: '600' }, message: { @@ -15,20 +13,14 @@ const styles = StyleSheet.create({ }, }); -export type ChannelPreviewMessageProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type ChannelPreviewMessageProps = { /** * Latest message on a channel, formatted for preview. */ - latestMessagePreview: LatestMessagePreview; + latestMessagePreview: LatestMessagePreview; }; -export const ChannelPreviewMessage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ChannelPreviewMessageProps, -) => { +export const ChannelPreviewMessage = (props: ChannelPreviewMessageProps) => { const { latestMessagePreview } = props; const { diff --git a/package/src/components/ChannelPreview/ChannelPreviewMessenger.tsx b/package/src/components/ChannelPreview/ChannelPreviewMessenger.tsx index 2805b61325..65f87b0b6b 100644 --- a/package/src/components/ChannelPreview/ChannelPreviewMessenger.tsx +++ b/package/src/components/ChannelPreview/ChannelPreviewMessenger.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { TouchableOpacity } from 'react-native-gesture-handler'; +import { StyleSheet, TouchableOpacity, View } from 'react-native'; import { ChannelAvatar } from './ChannelAvatar'; import type { ChannelPreviewProps } from './ChannelPreview'; @@ -19,7 +18,6 @@ import { } from '../../contexts/channelsContext/ChannelsContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useViewport } from '../../hooks/useViewport'; -import type { DefaultStreamChatGenerics } from '../../types/types'; const styles = StyleSheet.create({ container: { @@ -44,11 +42,9 @@ const styles = StyleSheet.create({ title: { fontSize: 14, fontWeight: '700' }, }); -export type ChannelPreviewMessengerPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'channel'> & +export type ChannelPreviewMessengerPropsWithContext = Pick & Pick< - ChannelsContextValue, + ChannelsContextValue, | 'maxUnreadCount' | 'onSelect' | 'PreviewAvatar' @@ -83,7 +79,7 @@ export type ChannelPreviewMessengerPropsWithContext< * * @overrideType object */ - latestMessagePreview: LatestMessagePreview; + latestMessagePreview: LatestMessagePreview; /** * Formatter function for date of latest message. * @param date Message date @@ -100,11 +96,7 @@ export type ChannelPreviewMessengerPropsWithContext< unread?: number; }; -const ChannelPreviewMessengerWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ChannelPreviewMessengerPropsWithContext, -) => { +const ChannelPreviewMessengerWithContext = (props: ChannelPreviewMessengerPropsWithContext) => { const { channel, formatLatestMessageDate, @@ -144,6 +136,7 @@ const ChannelPreviewMessengerWithContext = < } }} style={[ + // { opacity: pressed ? 0.5 : 1 }, styles.container, { backgroundColor: white_snow, borderBottomColor: border }, container, @@ -175,18 +168,10 @@ const ChannelPreviewMessengerWithContext = < ); }; -export type ChannelPreviewMessengerProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial< - Omit< - ChannelPreviewMessengerPropsWithContext, - 'channel' | 'latestMessagePreview' - > +export type ChannelPreviewMessengerProps = Partial< + Omit > & - Pick< - ChannelPreviewMessengerPropsWithContext, - 'channel' | 'latestMessagePreview' - >; + Pick; const MemoizedChannelPreviewMessengerWithContext = React.memo( ChannelPreviewMessengerWithContext, @@ -196,11 +181,7 @@ const MemoizedChannelPreviewMessengerWithContext = React.memo( * This UI component displays an individual preview item for each channel in a list. It also receives all props * from the ChannelPreview component. */ -export const ChannelPreviewMessenger = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ChannelPreviewMessengerProps, -) => { +export const ChannelPreviewMessenger = (props: ChannelPreviewMessengerProps) => { const { forceUpdate, maxUnreadCount, @@ -211,7 +192,7 @@ export const ChannelPreviewMessenger = < PreviewStatus, PreviewTitle, PreviewUnreadCount, - } = useChannelsContext(); + } = useChannelsContext(); return ( = Pick< - ChannelPreviewMessengerPropsWithContext, +export type ChannelPreviewStatusProps = Pick< + ChannelPreviewMessengerPropsWithContext, 'latestMessagePreview' | 'formatLatestMessageDate' > & - Pick, 'channel'>; + Pick; -export const ChannelPreviewStatus = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ChannelPreviewStatusProps, -) => { +export const ChannelPreviewStatus = (props: ChannelPreviewStatusProps) => { const { formatLatestMessageDate, latestMessagePreview } = props; const { t, tDateTimeParser } = useTranslationContext(); const { diff --git a/package/src/components/ChannelPreview/ChannelPreviewTitle.tsx b/package/src/components/ChannelPreview/ChannelPreviewTitle.tsx index 24ae659ff5..fd248f17c6 100644 --- a/package/src/components/ChannelPreview/ChannelPreviewTitle.tsx +++ b/package/src/components/ChannelPreview/ChannelPreviewTitle.tsx @@ -4,26 +4,19 @@ import { StyleSheet, Text } from 'react-native'; import type { ChannelPreviewProps } from './ChannelPreview'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; const styles = StyleSheet.create({ title: { fontSize: 14, fontWeight: '700' }, }); -export type ChannelPreviewTitleProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'channel'> & { +export type ChannelPreviewTitleProps = Pick & { /** * Formatted name for the previewed channel. */ displayName: string; }; -export const ChannelPreviewTitle = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ChannelPreviewTitleProps, -) => { +export const ChannelPreviewTitle = (props: ChannelPreviewTitleProps) => { const { displayName } = props; const { theme: { diff --git a/package/src/components/ChannelPreview/ChannelPreviewUnreadCount.tsx b/package/src/components/ChannelPreview/ChannelPreviewUnreadCount.tsx index 76ffd31e32..cd592ac958 100644 --- a/package/src/components/ChannelPreview/ChannelPreviewUnreadCount.tsx +++ b/package/src/components/ChannelPreview/ChannelPreviewUnreadCount.tsx @@ -6,23 +6,15 @@ import { ChannelPreviewProps } from './ChannelPreview'; import type { ChannelsContextValue } from '../../contexts/channelsContext/ChannelsContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - -export type ChannelPreviewUnreadCountProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'maxUnreadCount'> & - Pick, 'channel'> & { +export type ChannelPreviewUnreadCountProps = Pick & + Pick & { /** * Number of unread messages on the channel */ unread?: number; }; -export const ChannelPreviewUnreadCount = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ChannelPreviewUnreadCountProps, -) => { +export const ChannelPreviewUnreadCount = (props: ChannelPreviewUnreadCountProps) => { const { maxUnreadCount, unread } = props; const { theme: { diff --git a/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayName.test.tsx b/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayName.test.tsx index 7865355361..664f3e5f0d 100644 --- a/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayName.test.tsx +++ b/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayName.test.tsx @@ -3,7 +3,7 @@ import { Text } from 'react-native'; import { render, screen, waitFor } from '@testing-library/react-native'; -import type { Channel, ChannelMemberResponse, DefaultGenerics, StreamChat } from 'stream-chat'; +import type { Channel, ChannelMemberResponse, StreamChat } from 'stream-chat'; import { GROUP_CHANNEL_MEMBERS_MOCK, @@ -12,7 +12,7 @@ import { } from '../../../../mock-builders/api/queryMembers'; import { generateUser } from '../../../../mock-builders/generator/user'; import { getTestClientWithUser } from '../../../../mock-builders/mock'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; + import { getChannelPreviewDisplayName, useChannelPreviewDisplayName, @@ -20,7 +20,7 @@ import { describe('useChannelPreviewDisplayName', () => { const clientUser = generateUser(); - let chatClient: StreamChat | StreamChat; + let chatClient: StreamChat | StreamChat; const CHARACTER_LENGTH = 15; beforeEach(async () => { @@ -34,7 +34,7 @@ describe('useChannelPreviewDisplayName', () => { const channelDisplayName = useChannelPreviewDisplayName( { data: { name: channelName }, - } as unknown as Channel, + } as unknown as Channel, CHARACTER_LENGTH, ); return {channelDisplayName}; @@ -71,10 +71,7 @@ describe('useChannelPreviewDisplayName', () => { const displayName = getChannelPreviewDisplayName({ characterLimit: characterLength, currentUserId, - members: members as unknown as Record< - string, - ChannelMemberResponse - >, + members: members as unknown as Record, }); expect(displayName).toEqual(expected); diff --git a/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayPresence.test.tsx b/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayPresence.test.tsx index f09e1b09a6..2d18faf99b 100644 --- a/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayPresence.test.tsx +++ b/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewDisplayPresence.test.tsx @@ -4,16 +4,15 @@ import type { Channel, StreamChat, UserResponse } from 'stream-chat'; import type { ChatContextValue } from '../../../../contexts/chatContext/ChatContext'; import * as ChatContext from '../../../../contexts/chatContext/ChatContext'; import { getTestClientWithUser } from '../../../../mock-builders/mock'; -import { DefaultStreamChatGenerics } from '../../../../types/types'; import { useChannelPreviewDisplayPresence } from '../useChannelPreviewDisplayPresence'; describe('useChannelPreviewDisplayPresence', () => { // Mock user data const currentUserId = 'current-user'; const otherUserId = 'other-user'; - let chatClient: StreamChat; + let chatClient: StreamChat; - let mockChannel: Channel; + let mockChannel: Channel; beforeEach(async () => { jest.clearAllMocks(); @@ -27,14 +26,14 @@ describe('useChannelPreviewDisplayPresence', () => { state: { members: { [currentUserId]: { - user: { id: currentUserId, online: true } as UserResponse, + user: { id: currentUserId, online: true } as UserResponse, }, [otherUserId]: { - user: { id: otherUserId, online: false } as UserResponse, + user: { id: otherUserId, online: false } as UserResponse, }, }, }, - } as unknown as Channel; + } as unknown as Channel; // Mock the useChatContext hook jest @@ -53,17 +52,17 @@ describe('useChannelPreviewDisplayPresence', () => { state: { members: { [currentUserId]: { - user: { id: currentUserId } as UserResponse, + user: { id: currentUserId } as UserResponse, }, [otherUserId]: { - user: { id: otherUserId } as UserResponse, + user: { id: otherUserId } as UserResponse, }, [thirdUserId]: { - user: { id: thirdUserId } as UserResponse, + user: { id: thirdUserId } as UserResponse, }, }, }, - } as unknown as Channel; + } as unknown as Channel; const { result } = renderHook(() => useChannelPreviewDisplayPresence(channelWithThreeMembers)); expect(result.current).toBe(false); @@ -79,7 +78,7 @@ describe('useChannelPreviewDisplayPresence', () => { const onlineUser = { ...mockChannel.state.members[otherUserId].user, online: true, - } as UserResponse; + } as UserResponse; mockChannel.state.members[otherUserId].user = onlineUser; @@ -93,14 +92,14 @@ describe('useChannelPreviewDisplayPresence', () => { state: { members: { [currentUserId]: { - user: { id: currentUserId } as UserResponse, + user: { id: currentUserId } as UserResponse, }, 'null-user': { user: null, }, }, }, - } as unknown as Channel; + } as unknown as Channel; const { result } = renderHook(() => useChannelPreviewDisplayPresence(channelWithNullUser)); expect(result.current).toBe(false); diff --git a/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewMuted.test.tsx b/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewMuted.test.tsx index 06de03ae32..1808d5cba9 100644 --- a/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewMuted.test.tsx +++ b/package/src/components/ChannelPreview/hooks/__tests__/useChannelPreviewMuted.test.tsx @@ -1,16 +1,15 @@ import { act, renderHook, waitFor } from '@testing-library/react-native'; -import { Channel, DefaultGenerics, StreamChat } from 'stream-chat'; +import { Channel, StreamChat } from 'stream-chat'; import * as ChatContext from '../../../../contexts/chatContext/ChatContext'; import dispatchNotificationChannelMutesUpdated from '../../../../mock-builders/event/notificationChannelMutesUpdated'; import { generateUser } from '../../../../mock-builders/generator/user'; import { getTestClientWithUser } from '../../../../mock-builders/mock'; -import { DefaultStreamChatGenerics } from '../../../../types/types'; import { useIsChannelMuted } from '../useIsChannelMuted'; describe('useChannelPreviewMuted', () => { const clientUser = generateUser(); - let chatClient: StreamChat | StreamChat; + let chatClient: StreamChat; beforeEach(async () => { chatClient = await getTestClientWithUser(clientUser); @@ -35,7 +34,7 @@ describe('useChannelPreviewMuted', () => { expiresAt: Date.now() + 5000, muted: false, }), - } as unknown as Channel; + } as unknown as Channel; it('should return the correct mute status', () => { const { result } = renderHook(() => useIsChannelMuted(mockChannel)); diff --git a/package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx b/package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx index 9e51b11b00..b1a95c2a63 100644 --- a/package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx +++ b/package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx @@ -2,7 +2,7 @@ import React, { FC } from 'react'; import { renderHook, waitFor } from '@testing-library/react-native'; -import type { DefaultGenerics, MessageResponse, StreamChat } from 'stream-chat'; +import type { MessageResponse, StreamChat } from 'stream-chat'; import { ChatContext, ChatContextValue } from '../../../../contexts/chatContext/ChatContext'; import { @@ -18,14 +18,13 @@ import { import { generateUser } from '../../../../mock-builders/generator/user'; import { getTestClientWithUser } from '../../../../mock-builders/mock'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; import { useLatestMessagePreview } from '../useLatestMessagePreview'; describe('useLatestMessagePreview', () => { const FORCE_UPDATE = 15; const clientUser = generateUser(); - let chatClient: StreamChat | StreamChat; + let chatClient: StreamChat | StreamChat; beforeEach(async () => { chatClient = await getTestClientWithUser(clientUser); diff --git a/package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts b/package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts index 5c3db61c68..9df1c86c80 100644 --- a/package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts +++ b/package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts @@ -8,23 +8,19 @@ import { useIsChannelMuted } from './useIsChannelMuted'; import { useLatestMessagePreview } from './useLatestMessagePreview'; import { useChannelsContext } from '../../../contexts'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -export const useChannelPreviewData = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, - client: StreamChat, +export const useChannelPreviewData = ( + channel: Channel, + client: StreamChat, forceUpdateOverride?: number, ) => { const [forceUpdate, setForceUpdate] = useState(0); const [lastMessage, setLastMessage] = useState< - | ReturnType['formatMessage']> - | MessageResponse + ReturnType | MessageResponse >(channel.state.messages[channel.state.messages.length - 1]); const [unread, setUnread] = useState(channel.countUnread()); const { muted } = useIsChannelMuted(channel); - const { forceUpdate: contextForceUpdate } = useChannelsContext(); + const { forceUpdate: contextForceUpdate } = useChannelsContext(); const channelListForceUpdate = forceUpdateOverride ?? contextForceUpdate; const channelLastMessage = channel.lastMessage(); @@ -113,7 +109,7 @@ export const useChannelPreviewData = < refreshUnreadCount(); }; - const handleNewMessageEvent = (event: Event) => { + const handleNewMessageEvent = (event: Event) => { const message = event.message; if (message && (!message.parent_id || message.show_in_channel)) { setLastMessage(message); @@ -121,7 +117,7 @@ export const useChannelPreviewData = < } }; - const handleUpdatedOrDeletedMessage = (event: Event) => { + const handleUpdatedOrDeletedMessage = (event: Event) => { setLastMessage((prevLastMessage) => { if (prevLastMessage?.id === event.message?.id) { return event.message; diff --git a/package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayAvatar.ts b/package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayAvatar.ts index 5ce86a726b..390112b91b 100644 --- a/package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayAvatar.ts +++ b/package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayAvatar.ts @@ -4,14 +4,7 @@ import type { Channel, StreamChat } from 'stream-chat'; import { useChatContext } from '../../../contexts/chatContext/ChatContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; - -export const getChannelPreviewDisplayAvatar = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, - client: StreamChat, -) => { +export const getChannelPreviewDisplayAvatar = (channel: Channel, client: StreamChat) => { const currentUserId = client?.user?.id; const channelId = channel?.id; const channelData = channel?.data; @@ -54,12 +47,8 @@ export const getChannelPreviewDisplayAvatar = < * * @returns {object} e.g., { image: 'http://dummyurl.com/test.png', name: 'Uhtred Bebbanburg' } */ -export const useChannelPreviewDisplayAvatar = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, -) => { - const { client } = useChatContext(); +export const useChannelPreviewDisplayAvatar = (channel: Channel) => { + const { client } = useChatContext(); const displayAvatar = useMemo( () => getChannelPreviewDisplayAvatar(channel, client), diff --git a/package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayName.ts b/package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayName.ts index 93ef4cf267..c9bd19b2ff 100644 --- a/package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayName.ts +++ b/package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayName.ts @@ -5,16 +5,12 @@ import type { Channel, ChannelMemberResponse } from 'stream-chat'; import { useChatContext } from '../../../contexts/chatContext/ChatContext'; import { useViewport } from '../../../hooks/useViewport'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; - const ELLIPSIS = '...'; const getMemberName = (member: ChannelMemberResponse) => member.user?.name || member.user?.id || 'Unknown User'; -export const getChannelPreviewDisplayName = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const getChannelPreviewDisplayName = ({ channelName, characterLimit, currentUserId, @@ -23,7 +19,7 @@ export const getChannelPreviewDisplayName = < characterLimit: number; channelName?: string; currentUserId?: string; - members?: Channel['state']['members']; + members?: Channel['state']['members']; }): string => { if (channelName) { return channelName.length > characterLimit @@ -76,13 +72,8 @@ export const getChannelPreviewDisplayName = < return name; }; -export const useChannelPreviewDisplayName = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel?: Channel, - characterLength?: number, -) => { - const { client } = useChatContext(); +export const useChannelPreviewDisplayName = (channel?: Channel, characterLength?: number) => { + const { client } = useChatContext(); const { vw } = useViewport(); const DEFAULT_MAX_CHARACTER_LENGTH = Math.floor((vw(100) - 16) / 6); diff --git a/package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayPresence.ts b/package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayPresence.ts index 2356d6eac6..2118e6e142 100644 --- a/package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayPresence.ts +++ b/package/src/components/ChannelPreview/hooks/useChannelPreviewDisplayPresence.ts @@ -3,7 +3,6 @@ import type { Channel, EventTypes, StreamChat } from 'stream-chat'; import { useChatContext } from '../../../contexts/chatContext/ChatContext'; import { useSyncClientEventsToChannel } from '../../../hooks/useSyncClientEvents'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; /** * Selector to get the display avatar presence for channel preview @@ -13,10 +12,7 @@ import type { DefaultStreamChatGenerics } from '../../../types/types'; * * NOTE: If you want to listen to the value changes where you call the hook, the selector should return primitive values instead of object. */ -const selector = ( - channel: Channel, - client: StreamChat, -) => { +const selector = (channel: Channel, client: StreamChat) => { const members = channel.state.members; const membersCount = Object.keys(members).length; const otherMember = Object.values(members).find((member) => member.user?.id !== client.userID); @@ -33,9 +29,7 @@ const keys: EventTypes[] = ['user.presence.changed', 'user.updated']; * * @returns {boolean} e.g., true */ -export function useChannelPreviewDisplayPresence< - StreamChatGenerics extends DefaultStreamChatGenerics, ->(channel: Channel) { - const { client } = useChatContext(); +export function useChannelPreviewDisplayPresence(channel: Channel) { + const { client } = useChatContext(); return useSyncClientEventsToChannel({ channel, client, selector, stateChangeEventKeys: keys }); } diff --git a/package/src/components/ChannelPreview/hooks/useIsChannelMuted.ts b/package/src/components/ChannelPreview/hooks/useIsChannelMuted.ts index 04ddbf6644..366271361b 100644 --- a/package/src/components/ChannelPreview/hooks/useIsChannelMuted.ts +++ b/package/src/components/ChannelPreview/hooks/useIsChannelMuted.ts @@ -4,20 +4,14 @@ import type { Channel } from 'stream-chat'; import { useChatContext } from '../../../contexts/chatContext/ChatContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; - const defaultMuteStatus = { createdAt: null, expiresAt: null, muted: false, }; -export const useIsChannelMuted = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, -) => { - const { client } = useChatContext(); +export const useIsChannelMuted = (channel: Channel) => { + const { client } = useChatContext(); const [muted, setMuted] = useState(() => channel.muteStatus()); diff --git a/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts b/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts index ac2efc7329..dba79b5c39 100644 --- a/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts +++ b/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts @@ -16,19 +16,13 @@ import { useTranslationContext } from '../../../contexts/translationContext/Tran import { useStateStore } from '../../../hooks'; import { useTranslatedMessage } from '../../../hooks/useTranslatedMessage'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { stringifyMessage } from '../../../utils/utils'; -type LatestMessage< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = - | ReturnType['formatMessage']> - | MessageResponse; +type LatestMessage = ReturnType | MessageResponse; -export type LatestMessagePreview< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - messageObject: LatestMessage | undefined; +export type LatestMessagePreview = { + messageObject: LatestMessage | undefined; previews: { bold: boolean; text: string; @@ -43,18 +37,14 @@ export type LatestMessagePreviewSelectorReturnType = { name?: string; }; -const selector = ( - nextValue: PollState, -): LatestMessagePreviewSelectorReturnType => ({ +const selector = (nextValue: PollState): LatestMessagePreviewSelectorReturnType => ({ createdBy: nextValue.created_by, latestVotesByOption: nextValue.latest_votes_by_option, name: nextValue.name, }); -const getMessageSenderName = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - message: LatestMessage | undefined, +const getMessageSenderName = ( + message: LatestMessage | undefined, currentUserId: string | undefined, t: (key: string) => string, membersLength: number, @@ -70,11 +60,7 @@ const getMessageSenderName = < return ''; }; -const getMentionUsers = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - mentionedUser: UserResponse[] | undefined, -) => { +const getMentionUsers = (mentionedUser: UserResponse[] | undefined) => { if (Array.isArray(mentionedUser)) { const mentionUserString = mentionedUser.reduce((acc, cur) => { const userName = cur.name || cur.id || ''; @@ -93,12 +79,10 @@ const getMentionUsers = < return ''; }; -const getLatestMessageDisplayText = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, - client: StreamChat, - message: LatestMessage | undefined, +const getLatestMessageDisplayText = ( + channel: Channel, + client: StreamChat, + message: LatestMessage | undefined, t: (key: string) => string, pollState: LatestMessagePreviewSelectorReturnType | undefined, ) => { @@ -185,12 +169,10 @@ export enum MessageReadStatus { READ = 2, } -const getLatestMessageReadStatus = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, - client: StreamChat, - message: LatestMessage | undefined, +const getLatestMessageReadStatus = ( + channel: Channel, + client: StreamChat, + message: LatestMessage | undefined, readEvents: boolean, ): MessageReadStatus => { const currentUserId = client.userID; @@ -216,17 +198,13 @@ const getLatestMessageReadStatus = < : MessageReadStatus.UNREAD; }; -const getLatestMessagePreview = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->(params: { - channel: Channel; - client: StreamChat; +const getLatestMessagePreview = (params: { + channel: Channel; + client: StreamChat; pollState: LatestMessagePreviewSelectorReturnType | undefined; readEvents: boolean; t: TFunction; - lastMessage?: - | ReturnType['formatMessage']> - | MessageResponse; + lastMessage?: ReturnType | MessageResponse; }) => { const { channel, client, lastMessage, pollState, readEvents, t } = params; @@ -265,21 +243,17 @@ const getLatestMessagePreview = < * * @returns {object} latest message preview e.g.. { text: 'this was last message ...', created_at: '11/12/2020', messageObject: { originalMessageObject } } */ -export const useLatestMessagePreview = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, +export const useLatestMessagePreview = ( + channel: Channel, forceUpdate: number, - lastMessage?: - | ReturnType['formatMessage']> - | MessageResponse, + lastMessage?: ReturnType | MessageResponse, ) => { - const { client } = useChatContext(); + const { client } = useChatContext(); const { t } = useTranslationContext(); const channelConfigExists = typeof channel?.getConfig === 'function'; - const translatedLastMessage = useTranslatedMessage(lastMessage); + const translatedLastMessage = useTranslatedMessage(lastMessage); const channelLastMessageString = translatedLastMessage ? stringifyMessage(translatedLastMessage) diff --git a/package/src/components/Chat/Chat.tsx b/package/src/components/Chat/Chat.tsx index 5d6aaa1f7f..d8fe2e261b 100644 --- a/package/src/components/Chat/Chat.tsx +++ b/package/src/components/Chat/Chat.tsx @@ -25,17 +25,15 @@ import init from '../../init'; import { NativeHandlers } from '../../native'; import { SqliteClient } from '../../store/SqliteClient'; -import type { DefaultStreamChatGenerics } from '../../types/types'; + import { DBSyncManager } from '../../utils/DBSyncManager'; import type { Streami18n } from '../../utils/i18n/Streami18n'; import { version } from '../../version.json'; init(); -export type ChatProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'client'> & - Partial, 'ImageComponent' | 'isMessageAIGenerated'>> & { +export type ChatProps = Pick & + Partial> & { /** * When false, ws connection won't be disconnection upon backgrounding the app. * To receive push notifications, its necessary that user doesn't have active @@ -136,11 +134,7 @@ export type ChatProps< style?: DeepPartial; }; -const ChatWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: PropsWithChildren>, -) => { +const ChatWithContext = (props: PropsWithChildren) => { const { children, client, @@ -153,7 +147,7 @@ const ChatWithContext = < style, } = props; - const [channel, setChannel] = useState>(); + const [channel, setChannel] = useState(); // Setup translators const translators = useStreami18n(i18nInstance); @@ -161,10 +155,7 @@ const ChatWithContext = < /** * Setup connection event listeners */ - const { connectionRecovering, isOnline } = useIsOnline( - client, - closeConnectionOnBackground, - ); + const { connectionRecovering, isOnline } = useIsOnline(client, closeConnectionOnBackground); const [initialisedDatabaseConfig, setInitialisedDatabaseConfig] = useState<{ initialised: boolean; @@ -178,7 +169,7 @@ const ChatWithContext = < * Setup muted user listener * TODO: reimplement */ - const mutedUsers = useMutedUsers(client); + const mutedUsers = useMutedUsers(client); const debugRef = useDebugContext(); const isDebugModeEnabled = __DEV__ && debugRef && debugRef.current; @@ -214,7 +205,7 @@ const ChatWithContext = < // eslint-disable-next-line react-hooks/exhaustive-deps }, [client, enableOfflineSupport]); - const setActiveChannel = (newChannel?: Channel) => setChannel(newChannel); + const setActiveChannel = (newChannel?: Channel) => setChannel(newChannel); useEffect(() => { if (!(userID && enableOfflineSupport)) { @@ -294,12 +285,12 @@ const ChatWithContext = < } return ( - value={chatContext}> + - >{children} + {children} @@ -317,21 +308,8 @@ const ChatWithContext = < * - connectionRecovering - whether or not websocket is reconnecting * - isOnline - whether or not set user is active * - setActiveChannel - function to set the currently active channel - * - * The Chat Component takes the following generics in order: - * - At (AttachmentType) - custom Attachment object extension - * - Ct (ChannelType) - custom Channel object extension - * - Co (CommandType) - custom Command string union extension - * - Ev (EventType) - custom Event object extension - * - Me (MessageType) - custom Message object extension - * - Re (ReactionType) - custom Reaction object extension - * - Us (UserType) - custom User object extension */ -export const Chat = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: PropsWithChildren>, -) => { +export const Chat = (props: PropsWithChildren) => { const { style } = useOverlayContext(); return ; diff --git a/package/src/components/Chat/hooks/handleEventToSyncDB.ts b/package/src/components/Chat/hooks/handleEventToSyncDB.ts index e3ea1d7c2b..8f90918a46 100644 --- a/package/src/components/Chat/hooks/handleEventToSyncDB.ts +++ b/package/src/components/Chat/hooks/handleEventToSyncDB.ts @@ -14,15 +14,8 @@ import { upsertReads } from '../../../store/apis/upsertReads'; import { createSelectQuery } from '../../../store/sqlite-utils/createSelectQuery'; import { SqliteClient } from '../../../store/SqliteClient'; import { PreparedQueries } from '../../../store/types'; -import { DefaultStreamChatGenerics } from '../../../types/types'; -export const handleEventToSyncDB = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - event: Event, - client: StreamChat, - flush?: boolean, -) => { +export const handleEventToSyncDB = async (event: Event, client: StreamChat, flush?: boolean) => { const { type } = event; // This function is used to guard the queries that require channel to be present in the db first diff --git a/package/src/components/Chat/hooks/useAppSettings.ts b/package/src/components/Chat/hooks/useAppSettings.ts index 6ea63488ea..f51a34931e 100644 --- a/package/src/components/Chat/hooks/useAppSettings.ts +++ b/package/src/components/Chat/hooks/useAppSettings.ts @@ -4,12 +4,9 @@ import type { AppSettingsAPIResponse, StreamChat } from 'stream-chat'; import { useIsMountedRef } from '../../../hooks/useIsMountedRef'; import * as dbApi from '../../../store/apis'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -export const useAppSettings = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - client: StreamChat, +export const useAppSettings = ( + client: StreamChat, isOnline: boolean | null, enableOfflineSupport: boolean, initialisedDatabase: boolean, diff --git a/package/src/components/Chat/hooks/useCreateChatClient.ts b/package/src/components/Chat/hooks/useCreateChatClient.ts index eb5625001a..eab9a51f0f 100644 --- a/package/src/components/Chat/hooks/useCreateChatClient.ts +++ b/package/src/components/Chat/hooks/useCreateChatClient.ts @@ -3,8 +3,6 @@ import { useEffect, useState } from 'react'; import { StreamChat } from 'stream-chat'; import type { - DefaultGenerics, - ExtendableGenerics, OwnUserResponse, StreamChatOptions, TokenOrProvider, @@ -14,7 +12,7 @@ import type { /** * React hook to create, connect and return `StreamChat` client. */ -export const useCreateChatClient = ({ +export const useCreateChatClient = ({ apiKey, options, tokenOrProvider, @@ -22,10 +20,10 @@ export const useCreateChatClient = | UserResponse; + userData: OwnUserResponse | UserResponse; options?: StreamChatOptions; }) => { - const [chatClient, setChatClient] = useState | null>(null); + const [chatClient, setChatClient] = useState(null); const [cachedUserData, setCachedUserData] = useState(userData); if (userData.id !== cachedUserData.id) { @@ -35,7 +33,7 @@ export const useCreateChatClient = { - const client = new StreamChat(apiKey, undefined, cachedOptions); + const client = new StreamChat(apiKey, undefined, cachedOptions); let didUserConnectInterrupt = false; const connectionPromise = client.connectUser(cachedUserData, tokenOrProvider).then(() => { diff --git a/package/src/components/Chat/hooks/useCreateChatContext.ts b/package/src/components/Chat/hooks/useCreateChatContext.ts index e39728c142..2992dbc961 100644 --- a/package/src/components/Chat/hooks/useCreateChatContext.ts +++ b/package/src/components/Chat/hooks/useCreateChatContext.ts @@ -1,11 +1,8 @@ import { useMemo } from 'react'; import type { ChatContextValue } from '../../../contexts/chatContext/ChatContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -export const useCreateChatContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useCreateChatContext = ({ appSettings, channel, client, @@ -16,7 +13,7 @@ export const useCreateChatContext = < isOnline, mutedUsers, setActiveChannel, -}: ChatContextValue) => { +}: ChatContextValue) => { const channelId = channel?.id; const clientValues = client ? `${client.clientID}${Object.keys(client.activeChannels).length}${ @@ -25,7 +22,7 @@ export const useCreateChatContext = < : 'Offline'; const mutedUsersLength = mutedUsers.length; - const chatContext: ChatContextValue = useMemo( + const chatContext: ChatContextValue = useMemo( () => ({ appSettings, channel, diff --git a/package/src/components/Chat/hooks/useIsOnline.ts b/package/src/components/Chat/hooks/useIsOnline.ts index d9fd3c0fa2..15b12fd913 100644 --- a/package/src/components/Chat/hooks/useIsOnline.ts +++ b/package/src/components/Chat/hooks/useIsOnline.ts @@ -7,20 +7,13 @@ import type { StreamChat, Event as StreamEvent } from 'stream-chat'; import { useAppStateListener } from '../../../hooks/useAppStateListener'; import { useIsMountedRef } from '../../../hooks/useIsMountedRef'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; - /** * Disconnect the websocket connection when app goes to background, * and reconnect when app comes to foreground. * We do this to make sure the user receives push notifications when app is in the background. * You can't receive push notification until you have active websocket connection. */ -export const useIsOnline = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - client: StreamChat, - closeConnectionOnBackground = true, -) => { +export const useIsOnline = (client: StreamChat, closeConnectionOnBackground = true) => { const [isOnline, setIsOnline] = useState(null); const [connectionRecovering, setConnectionRecovering] = useState(false); const isMounted = useIsMountedRef(); @@ -47,7 +40,7 @@ export const useIsOnline = < useAppStateListener(onForeground, onBackground); useEffect(() => { - const handleChangedEvent = (event: StreamEvent) => { + const handleChangedEvent = (event: StreamEvent) => { setConnectionRecovering(!event.online); setIsOnline(event.online || false); }; diff --git a/package/src/components/Chat/hooks/useMutedUsers.ts b/package/src/components/Chat/hooks/useMutedUsers.ts index 8aa77163a0..07f88ce028 100644 --- a/package/src/components/Chat/hooks/useMutedUsers.ts +++ b/package/src/components/Chat/hooks/useMutedUsers.ts @@ -2,19 +2,11 @@ import { useEffect, useState } from 'react'; import type { Event, Mute, StreamChat } from 'stream-chat'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; - -export const useMutedUsers = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - client: StreamChat, -) => { - const [mutedUsers, setMutedUsers] = useState[]>( - client?.mutedUsers || [], - ); +export const useMutedUsers = (client: StreamChat) => { + const [mutedUsers, setMutedUsers] = useState(client?.mutedUsers || []); useEffect(() => { - const handleEvent = (event: Event) => { + const handleEvent = (event: Event) => { setMutedUsers((mutes) => event.me?.mutes || mutes || []); }; diff --git a/package/src/components/Chat/hooks/useSyncDatabase.ts b/package/src/components/Chat/hooks/useSyncDatabase.ts index 2aa4e8b01d..10401a2f07 100644 --- a/package/src/components/Chat/hooks/useSyncDatabase.ts +++ b/package/src/components/Chat/hooks/useSyncDatabase.ts @@ -4,20 +4,12 @@ import type { StreamChat } from 'stream-chat'; import { handleEventToSyncDB } from './handleEventToSyncDB'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; - -type Params = { - client: StreamChat; +type Params = { + client: StreamChat; enableOfflineSupport: boolean; initialisedDatabase: boolean; }; -export const useSyncDatabase = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - client, - enableOfflineSupport, - initialisedDatabase, -}: Params) => { +export const useSyncDatabase = ({ client, enableOfflineSupport, initialisedDatabase }: Params) => { useEffect(() => { let listener: ReturnType | undefined; diff --git a/package/src/components/ImageGallery/ImageGallery.tsx b/package/src/components/ImageGallery/ImageGallery.tsx index 40776564bc..f9e5945203 100644 --- a/package/src/components/ImageGallery/ImageGallery.tsx +++ b/package/src/components/ImageGallery/ImageGallery.tsx @@ -42,7 +42,7 @@ import { OverlayProviderProps } from '../../contexts/overlayContext/OverlayConte import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useViewport } from '../../hooks/useViewport'; import { isVideoPlayerAvailable, VideoType } from '../../native'; -import { DefaultStreamChatGenerics, FileTypes } from '../../types/types'; +import { FileTypes } from '../../types/types'; import { getResizedImageUrl } from '../../utils/getResizedImageUrl'; import { getUrlOfImageAttachment } from '../../utils/getUrlOfImageAttachment'; import { getGiphyMimeType } from '../Attachment/utils/getGiphyMimeType'; @@ -64,9 +64,7 @@ export enum IsSwiping { FALSE, } -export type ImageGalleryCustomComponents< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type ImageGalleryCustomComponents = { /** * Override props for following UI components, which are part of [image gallery](https://github.com/GetStream/stream-chat-react-native/wiki/Cookbook-v3.0#gallery-components). * @@ -99,30 +97,25 @@ export type ImageGalleryCustomComponents< * @overrideType object */ imageGalleryCustomComponents?: { - footer?: ImageGalleryFooterCustomComponentProps; - grid?: ImageGalleryGridImageComponents; + footer?: ImageGalleryFooterCustomComponentProps; + grid?: ImageGalleryGridImageComponents; gridHandle?: ImageGalleryGridHandleCustomComponentProps; - header?: ImageGalleryHeaderCustomComponentProps; + header?: ImageGalleryHeaderCustomComponentProps; }; }; -type Props = - ImageGalleryCustomComponents & { - overlayOpacity: SharedValue; - } & Pick< - OverlayProviderProps, - | 'giphyVersion' - | 'imageGalleryGridSnapPoints' - | 'imageGalleryGridHandleHeight' - | 'numberOfImageGalleryGridColumns' - | 'autoPlayVideo' - >; - -export const ImageGallery = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: Props, -) => { +type Props = ImageGalleryCustomComponents & { + overlayOpacity: SharedValue; +} & Pick< + OverlayProviderProps, + | 'giphyVersion' + | 'imageGalleryGridSnapPoints' + | 'imageGalleryGridHandleHeight' + | 'numberOfImageGalleryGridColumns' + | 'autoPlayVideo' + >; + +export const ImageGallery = (props: Props) => { const { autoPlayVideo = false, giphyVersion = 'fixed_height', @@ -132,9 +125,7 @@ export const ImageGallery = < numberOfImageGalleryGridColumns, overlayOpacity, } = props; - const [imageGalleryAttachments, setImageGalleryAttachments] = useState< - Photo[] - >([]); + const [imageGalleryAttachments, setImageGalleryAttachments] = useState([]); const { resizableCDNHosts } = useChatConfigContext(); const { theme: { @@ -142,9 +133,8 @@ export const ImageGallery = < imageGallery: { backgroundColor, pager, slide }, }, } = useTheme(); - const [gridPhotos, setGridPhotos] = useState[]>([]); - const { messages, selectedMessage, setSelectedMessage } = - useImageGalleryContext(); + const [gridPhotos, setGridPhotos] = useState([]); + const { messages, selectedMessage, setSelectedMessage } = useImageGalleryContext(); const { vh, vw } = useViewport(); @@ -224,7 +214,7 @@ export const ImageGallery = < * photo attachments */ - const photos = messages.reduce((acc: Photo[], cur) => { + const photos = messages.reduce((acc: Photo[], cur) => { const attachmentImages = cur.attachments ?.filter( @@ -273,7 +263,7 @@ export const ImageGallery = < }; }); - return [...attachmentPhotos, ...acc] as Photo[]; + return [...attachmentPhotos, ...acc] as Photo[]; }, []); /** @@ -482,6 +472,10 @@ export const ImageGallery = < paused: imageGalleryAttachment.id === index ? false : true, })), ); + + if (videoRef.current?.play) { + videoRef.current.play(); + } } else { // If the status is true we simply set all the audio's paused state as true. setImageGalleryAttachments((prevImageGalleryAttachment) => @@ -490,6 +484,10 @@ export const ImageGallery = < paused: true, })), ); + + if (videoRef.current?.pause) { + videoRef.current.pause(); + } } }; @@ -583,7 +581,7 @@ export const ImageGallery = < - + {imageGalleryAttachments[selectedIndex] && ( - + = { +export type Photo = { id: string; uri: string; channelId?: string; @@ -669,7 +665,7 @@ export type Photo< progress?: number; thumb_url?: string; type?: string; - user?: UserResponse | null; + user?: UserResponse | null; user_id?: string; }; diff --git a/package/src/components/ImageGallery/__tests__/AnimatedVideoGallery.test.tsx b/package/src/components/ImageGallery/__tests__/AnimatedVideoGallery.test.tsx index f3673201ce..8a055d8fdd 100644 --- a/package/src/components/ImageGallery/__tests__/AnimatedVideoGallery.test.tsx +++ b/package/src/components/ImageGallery/__tests__/AnimatedVideoGallery.test.tsx @@ -155,76 +155,4 @@ describe('ImageGallery', () => { expect(spinnerComponent?.props.style[1].opacity).toBe(1); }); - - it('trigger onPlaybackStatusUpdate event handler of Video component', () => { - jest.spyOn(console, 'error'); - const handleLoadMock = jest.fn(); - const handleProgressMock = jest.fn(); - const handleEndMock = jest.fn(); - - render( - getComponent({ - handleEnd: handleEndMock, - handleLoad: handleLoadMock, - handleProgress: handleProgressMock, - offsetScale: { value: 1 } as SharedValue, - scale: { value: 1 } as SharedValue, - shouldRender: true, - source: { - uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', - }, - translateX: { value: 1 } as SharedValue, - }), - ); - - const videoComponent = screen.getByTestId('video-player'); - const spinnerComponent = screen.queryByLabelText('Spinner'); - - act(() => { - fireEvent(videoComponent, 'onPlaybackStatusUpdate', { - error: true, - isLoaded: false, - }); - }); - - expect(spinnerComponent?.props.style[1].opacity).toBe(1); - expect(console.error).toHaveBeenCalled(); - - act(() => { - fireEvent(videoComponent, 'onPlaybackStatusUpdate', { - isLoaded: true, - }); - }); - - expect(spinnerComponent?.props.style[1].opacity).toBe(0); - expect(handleLoadMock).toHaveBeenCalled(); - - act(() => { - fireEvent(videoComponent, 'onPlaybackStatusUpdate', { - isLoaded: true, - isPlaying: true, - }); - }); - - expect(handleProgressMock).toHaveBeenCalled(); - - act(() => { - fireEvent(videoComponent, 'onPlaybackStatusUpdate', { - isBuffering: true, - isLoaded: true, - }); - }); - - expect(spinnerComponent?.props.style[1].opacity).toBe(1); - - act(() => { - fireEvent(videoComponent, 'onPlaybackStatusUpdate', { - didJustFinish: true, - isLoaded: true, - isLooping: false, - }); - }); - - expect(handleEndMock).toHaveBeenCalled(); - }); }); diff --git a/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx index 67a51d9a3f..75867d2a57 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx @@ -4,6 +4,8 @@ import type { SharedValue } from 'react-native-reanimated'; import { act, fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import { LocalMessage } from 'stream-chat'; + import { ImageGalleryContext, ImageGalleryContextValue, @@ -17,8 +19,7 @@ import { generateVideoAttachment, } from '../../../mock-builders/generator/attachment'; import { generateMessage } from '../../../mock-builders/generator/message'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -import type { MessageType } from '../../MessageList/hooks/useMessageList'; + import { ImageGallery } from '../ImageGallery'; jest.mock('../../../native.ts', () => { @@ -56,7 +57,7 @@ describe('ImageGallery', () => { generateVideoAttachment({ type: 'video' }), ], }), - ] as unknown as MessageType[], + ] as unknown as LocalMessage[], }), ); @@ -73,7 +74,7 @@ describe('ImageGallery', () => { }); render( getComponent({ - messages: [message] as unknown as MessageType[], + messages: [message] as unknown as LocalMessage[], }), ); @@ -102,7 +103,7 @@ describe('ImageGallery', () => { generateMessage({ attachments: [generateVideoAttachment({ type: 'video' })], }), - ] as unknown as MessageType[], + ] as unknown as LocalMessage[], }), ); @@ -128,7 +129,7 @@ describe('ImageGallery', () => { render( getComponent({ - messages: [message] as unknown as MessageType[], + messages: [message] as unknown as LocalMessage[], }), ); @@ -163,7 +164,7 @@ describe('ImageGallery', () => { generateMessage({ attachments: [generateVideoAttachment({ type: 'video' })], }), - ] as unknown as MessageType[], + ] as unknown as LocalMessage[], }), ); @@ -193,7 +194,7 @@ describe('ImageGallery', () => { }); render( getComponent({ - messages: [message] as unknown as MessageType[], + messages: [message] as unknown as LocalMessage[], }), ); diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx index ca2d1e16ad..f244a20d36 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryFooter.test.tsx @@ -7,6 +7,8 @@ import { ReactTestInstance } from 'react-test-renderer'; import { render, screen, userEvent, waitFor } from '@testing-library/react-native'; +import { LocalMessage } from 'stream-chat'; + import { Chat } from '../../../components/Chat/Chat'; import { ImageGalleryContext, @@ -20,8 +22,6 @@ import { import { generateMessage } from '../../../mock-builders/generator/message'; import { getTestClientWithUser } from '../../../mock-builders/mock'; import { NativeHandlers } from '../../../native'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -import type { MessageType } from '../../MessageList/hooks/useMessageList'; import { ImageGallery, ImageGalleryCustomComponents } from '../ImageGallery'; jest.mock('../../../native.ts', () => { @@ -77,7 +77,7 @@ describe('ImageGalleryFooter', () => { generateMessage({ attachments: [generateVideoAttachment({ type: 'video' })], }), - ] as unknown as MessageType[], + ] as unknown as LocalMessage[], } as unknown as ImageGalleryContextValue } > @@ -132,7 +132,7 @@ describe('ImageGalleryFooter', () => { generateMessage({ attachments: [generateVideoAttachment({ type: 'video' })], }), - ] as unknown as MessageType[], + ] as unknown as LocalMessage[], } as unknown as ImageGalleryContextValue } > @@ -175,7 +175,7 @@ describe('ImageGalleryFooter', () => { generateMessage({ attachments: [generateImageAttachment()], }), - ] as unknown as MessageType[], + ] as unknown as LocalMessage[], } as unknown as ImageGalleryContextValue } > diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx index 2e1d1e08a8..7da00363b1 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryHeader.test.tsx @@ -7,6 +7,8 @@ import { act } from 'react-test-renderer'; import { render, screen, userEvent, waitFor } from '@testing-library/react-native'; +import { LocalMessage } from 'stream-chat'; + import { Chat } from '../../../components/Chat/Chat'; import { ImageGalleryContext, @@ -23,8 +25,7 @@ import { } from '../../../mock-builders/generator/attachment'; import { generateMessage } from '../../../mock-builders/generator/message'; import { getTestClientWithUser } from '../../../mock-builders/mock'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -import type { MessageType } from '../../MessageList/hooks/useMessageList'; + import { ImageGallery, ImageGalleryCustomComponents } from '../ImageGallery'; jest.mock('../../../native.ts', () => { @@ -71,7 +72,7 @@ describe('ImageGalleryHeader', () => { generateMessage({ attachments: [generateImageAttachment()], }), - ] as unknown as MessageType[], + ] as unknown as LocalMessage[], } as unknown as ImageGalleryContextValue } > @@ -118,7 +119,7 @@ describe('ImageGalleryHeader', () => { generateMessage({ attachments: [generateVideoAttachment({ type: 'video' })], }), - ] as unknown as MessageType[], + ] as unknown as LocalMessage[], } as unknown as ImageGalleryContextValue } > @@ -158,7 +159,7 @@ describe('ImageGalleryHeader', () => { generateMessage({ attachments: [generateImageAttachment()], }), - ] as unknown as MessageType[], + ] as unknown as LocalMessage[], } as unknown as ImageGalleryContextValue } > diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryOverlay.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryOverlay.test.tsx index b008904e67..53da8b2eab 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryOverlay.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryOverlay.test.tsx @@ -7,6 +7,8 @@ import { act } from 'react-test-renderer'; import { fireEvent, render, waitFor } from '@testing-library/react-native'; +import { LocalMessage } from 'stream-chat'; + import { Chat } from '../../../components/Chat/Chat'; import { ImageGalleryContext, @@ -19,8 +21,7 @@ import { import { generateImageAttachment } from '../../../mock-builders/generator/attachment'; import { generateMessage } from '../../../mock-builders/generator/message'; import { getTestClientWithUser } from '../../../mock-builders/mock'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -import type { MessageType } from '../../MessageList/hooks/useMessageList'; + import { ImageGalleryOverlay } from '../components/ImageGalleryOverlay'; describe('ImageGalleryOverlay', () => { @@ -39,7 +40,7 @@ describe('ImageGalleryOverlay', () => { generateMessage({ attachments: [generateImageAttachment()], }), - ] as unknown as MessageType[], + ] as unknown as LocalMessage[], } as unknown as ImageGalleryContextValue } > diff --git a/package/src/components/ImageGallery/components/AnimatedGalleryVideo.tsx b/package/src/components/ImageGallery/components/AnimatedGalleryVideo.tsx index 0fe17ca843..5711e84ce8 100644 --- a/package/src/components/ImageGallery/components/AnimatedGalleryVideo.tsx +++ b/package/src/components/ImageGallery/components/AnimatedGalleryVideo.tsx @@ -6,7 +6,6 @@ import Animated, { SharedValue } from 'react-native-reanimated'; import { isVideoPlayerAvailable, NativeHandlers, - PlaybackStatus, VideoPayloadData, VideoProgressData, VideoType, @@ -98,37 +97,6 @@ export const AnimatedGalleryVideo = React.memo( } }; - const onPlayBackStatusUpdate = (playbackStatus: PlaybackStatus) => { - if (!playbackStatus.isLoaded) { - // Update your UI for the unloaded state - setOpacity(1); - if (playbackStatus.error) { - console.error(`Encountered a fatal error during playback: ${playbackStatus.error}`); - } - } else { - // Update your UI for the loaded state - setOpacity(0); - handleLoad(attachmentId, playbackStatus.durationMillis); - if (playbackStatus.isPlaying) { - // Update your UI for the playing state - handleProgress( - attachmentId, - playbackStatus.positionMillis / playbackStatus.durationMillis, - ); - } - - if (playbackStatus.isBuffering) { - // Update your UI for the buffering state - setOpacity(1); - } - - if (playbackStatus.didJustFinish && !playbackStatus.isLooping) { - // The player has just finished playing and will stop. Maybe you want to play something else? - handleEnd(); - } - } - }; - const animatedStyles = useAnimatedGalleryStyle({ index, offsetScale, @@ -162,7 +130,6 @@ export const AnimatedGalleryVideo = React.memo( onEnd={onEnd} onLoad={onLoad} onLoadStart={onLoadStart} - onPlaybackStatusUpdate={onPlayBackStatusUpdate} onProgress={onProgress} paused={paused} repeat={repeat} diff --git a/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx b/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx index 9b321e5a72..978735ba3e 100644 --- a/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx +++ b/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx @@ -23,12 +23,10 @@ const ReanimatedSafeAreaView = Animated.createAnimatedComponent ? Animated.createAnimatedComponent(SafeAreaView) : SafeAreaView; -import { DefaultStreamChatGenerics, FileTypes } from '../../../types/types'; +import { FileTypes } from '../../../types/types'; import type { Photo } from '../ImageGallery'; -export type ImageGalleryFooterCustomComponent< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = ({ +export type ImageGalleryFooterCustomComponent = ({ openGridView, photo, share, @@ -37,7 +35,7 @@ export type ImageGalleryFooterCustomComponent< openGridView: () => void; share: () => Promise; shareMenuOpen: boolean; - photo?: Photo; + photo?: Photo; }) => React.ReactElement | null; export type ImageGalleryFooterVideoControlProps = { @@ -55,27 +53,23 @@ export type ImageGalleryFooterVideoControlComponent = ({ progress, }: ImageGalleryFooterVideoControlProps) => React.ReactElement | null; -export type ImageGalleryFooterCustomComponentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - centerElement?: ImageGalleryFooterCustomComponent; +export type ImageGalleryFooterCustomComponentProps = { + centerElement?: ImageGalleryFooterCustomComponent; GridIcon?: React.ReactElement; - leftElement?: ImageGalleryFooterCustomComponent; - rightElement?: ImageGalleryFooterCustomComponent; + leftElement?: ImageGalleryFooterCustomComponent; + rightElement?: ImageGalleryFooterCustomComponent; ShareIcon?: React.ReactElement; videoControlElement?: ImageGalleryFooterVideoControlComponent; }; -type ImageGalleryFooterPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = ImageGalleryFooterCustomComponentProps & { +type ImageGalleryFooterPropsWithContext = ImageGalleryFooterCustomComponentProps & { accessibilityLabel: string; duration: number; onPlayPause: () => void; opacity: SharedValue; openGridView: () => void; paused: boolean; - photo: Photo; + photo: Photo; photoLength: number; progress: number; selectedIndex: number; @@ -83,11 +77,7 @@ type ImageGalleryFooterPropsWithContext< visible: SharedValue; }; -export const ImageGalleryFooterWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ImageGalleryFooterPropsWithContext, -) => { +export const ImageGalleryFooterWithContext = (props: ImageGalleryFooterPropsWithContext) => { const { accessibilityLabel, centerElement, @@ -239,9 +229,9 @@ const ShareButton = ({ share, ShareIcon, shareMenuOpen }: ShareButtonProps) => { ); }; -const areEqual = ( - prevProps: ImageGalleryFooterPropsWithContext, - nextProps: ImageGalleryFooterPropsWithContext, +const areEqual = ( + prevProps: ImageGalleryFooterPropsWithContext, + nextProps: ImageGalleryFooterPropsWithContext, ) => { const { duration: prevDuration, @@ -284,15 +274,11 @@ const MemoizedImageGalleryFooter = React.memo( areEqual, ) as typeof ImageGalleryFooterWithContext; -export type ImageGalleryFooterProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = ImageGalleryFooterPropsWithContext; +export type ImageGalleryFooterProps = ImageGalleryFooterPropsWithContext; -export const ImageGalleryFooter = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ImageGalleryFooterProps, -) => ; +export const ImageGalleryFooter = (props: ImageGalleryFooterProps) => ( + +); ImageGalleryFooter.displayName = 'ImageGalleryFooter{imageGallery{footer}}'; diff --git a/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx b/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx index a2afec24d4..1c831681ff 100644 --- a/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx +++ b/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx @@ -12,7 +12,6 @@ import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { Close } from '../../../icons'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; import { getDateString } from '../../../utils/i18n/getDateString'; import type { Photo } from '../ImageGallery'; @@ -20,38 +19,29 @@ const ReanimatedSafeAreaView = Animated.createAnimatedComponent ? Animated.createAnimatedComponent(SafeAreaView) : SafeAreaView; -export type ImageGalleryHeaderCustomComponent< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = ({ +export type ImageGalleryHeaderCustomComponent = ({ hideOverlay, photo, }: { hideOverlay: () => void; - photo?: Photo; + photo?: Photo; }) => React.ReactElement | null; -export type ImageGalleryHeaderCustomComponentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - centerElement?: ImageGalleryHeaderCustomComponent; +export type ImageGalleryHeaderCustomComponentProps = { + centerElement?: ImageGalleryHeaderCustomComponent; CloseIcon?: React.ReactElement; - leftElement?: ImageGalleryHeaderCustomComponent; - rightElement?: ImageGalleryHeaderCustomComponent; + leftElement?: ImageGalleryHeaderCustomComponent; + rightElement?: ImageGalleryHeaderCustomComponent; }; -type Props = - ImageGalleryHeaderCustomComponentProps & { - opacity: SharedValue; - visible: SharedValue; - photo?: Photo; - /* Lookup key in the language corresponding translations sheet to perform date formatting */ - }; +type Props = ImageGalleryHeaderCustomComponentProps & { + opacity: SharedValue; + visible: SharedValue; + photo?: Photo; + /* Lookup key in the language corresponding translations sheet to perform date formatting */ +}; -export const ImageGalleryHeader = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: Props, -) => { +export const ImageGalleryHeader = (props: Props) => { const { centerElement, CloseIcon, leftElement, opacity, photo, rightElement, visible } = props; const [height, setHeight] = useState(200); const { diff --git a/package/src/components/ImageGallery/components/ImageGalleryVideoControl.tsx b/package/src/components/ImageGallery/components/ImageGalleryVideoControl.tsx index 84efe99210..95c55fd937 100644 --- a/package/src/components/ImageGallery/components/ImageGalleryVideoControl.tsx +++ b/package/src/components/ImageGallery/components/ImageGalleryVideoControl.tsx @@ -60,10 +60,12 @@ export const ImageGalleryVideoControl = React.memo( } = useTheme(); const handlePlayPause = async () => { + // Note: Not particularly sure why this was ever added, but + // will keep it for now for backwards compatibility. if (progress === 1) { // For expo CLI - if (videoRef.current?.setPositionAsync) { - await videoRef.current.setPositionAsync(0); + if (videoRef.current?.replay) { + await videoRef.current.replay(); } } onPlayPause(); diff --git a/package/src/components/ImageGallery/components/ImageGrid.tsx b/package/src/components/ImageGallery/components/ImageGrid.tsx index ca03d49302..0cf5a70172 100644 --- a/package/src/components/ImageGallery/components/ImageGrid.tsx +++ b/package/src/components/ImageGallery/components/ImageGrid.tsx @@ -4,7 +4,7 @@ import { Image, StyleSheet, View } from 'react-native'; import { VideoThumbnail } from '../../../components/Attachment/VideoThumbnail'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useViewport } from '../../../hooks/useViewport'; -import { DefaultStreamChatGenerics, FileTypes } from '../../../types/types'; +import { FileTypes } from '../../../types/types'; import { BottomSheetFlatList } from '../../BottomSheetCompatibility/BottomSheetFlatList'; import { BottomSheetTouchableOpacity } from '../../BottomSheetCompatibility/BottomSheetTouchableOpacity'; @@ -31,39 +31,27 @@ const styles = StyleSheet.create({ }, }); -export type ImageGalleryGridImageComponent< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = ({ +export type ImageGalleryGridImageComponent = ({ item, }: { - item: Photo & { + item: Photo & { selectAndClose: () => void; numberOfImageGalleryGridColumns?: number; }; }) => React.ReactElement | null; -export type ImageGalleryGridImageComponents< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - avatarComponent?: ImageGalleryGridImageComponent; - imageComponent?: ImageGalleryGridImageComponent; +export type ImageGalleryGridImageComponents = { + avatarComponent?: ImageGalleryGridImageComponent; + imageComponent?: ImageGalleryGridImageComponent; }; -export type GridImageItem< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Photo & - ImageGalleryGridImageComponents & { +export type GridImageItem = Photo & + ImageGalleryGridImageComponents & { selectAndClose: () => void; numberOfImageGalleryGridColumns?: number; }; -const GridImage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - item, -}: { - item: GridImageItem; -}) => { +const GridImage = ({ item }: { item: GridImageItem }) => { const { theme: { imageGallery: { @@ -95,19 +83,11 @@ const GridImage = < ); }; -const renderItem = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - item, -}: { - item: GridImageItem; -}) => ; +const renderItem = ({ item }: { item: GridImageItem }) => ; -export type ImageGridType< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = ImageGalleryGridImageComponents & { +export type ImageGridType = ImageGalleryGridImageComponents & { closeGridView: () => void; - photos: Photo[]; + photos: Photo[]; setSelectedMessage: React.Dispatch< React.SetStateAction< | { @@ -120,11 +100,7 @@ export type ImageGridType< numberOfImageGalleryGridColumns?: number; }; -export const ImageGrid = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ImageGridType, -) => { +export const ImageGrid = (props: ImageGridType) => { const { avatarComponent, closeGridView, @@ -155,14 +131,14 @@ export const ImageGrid = < })); return ( - > + accessibilityLabel='Image Grid' contentContainerStyle={[ styles.contentContainer, { backgroundColor: white }, contentContainer, ]} - data={imageGridItems as GridImageItem[]} + data={imageGridItems as GridImageItem[]} keyExtractor={(item, index) => `${item.uri}-${index}`} numColumns={numberOfImageGalleryGridColumns || 3} renderItem={renderItem} diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx index c9ca11701c..bdb8f7062b 100644 --- a/package/src/components/Message/Message.tsx +++ b/package/src/components/Message/Message.tsx @@ -1,7 +1,7 @@ import React, { useMemo, useState } from 'react'; import { GestureResponderEvent, Keyboard, StyleProp, View, ViewStyle } from 'react-native'; -import type { Attachment, UserResponse } from 'stream-chat'; +import type { Attachment, LocalMessage, UserResponse } from 'stream-chat'; import { useCreateMessageContext } from './hooks/useCreateMessageContext'; import { useMessageActionHandlers } from './hooks/useMessageActionHandlers'; @@ -32,7 +32,7 @@ import { } from '../../contexts/translationContext/TranslationContext'; import { isVideoPlayerAvailable, NativeHandlers } from '../../native'; -import { DefaultStreamChatGenerics, FileTypes } from '../../types/types'; +import { FileTypes } from '../../types/types'; import { hasOnlyEmojis, isBlockedMessage, @@ -41,11 +41,7 @@ import { MessageStatusTypes, } from '../../utils/utils'; import type { Thumbnail } from '../Attachment/utils/buildGallery/types'; - -import { - isMessageWithStylesReadByAndDateSeparator, - MessageType, -} from '../MessageList/hooks/useMessageList'; +import { getReadState } from '../MessageList/utils/getReadState'; export type TouchableEmitter = | 'fileAttachment' @@ -56,15 +52,11 @@ export type TouchableEmitter = | 'messageReplies' | 'reactionList'; -export type TextMentionTouchableHandlerAdditionalInfo< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { user?: UserResponse }; +export type TextMentionTouchableHandlerAdditionalInfo = { user?: UserResponse }; -export type TextMentionTouchableHandlerPayload< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type TextMentionTouchableHandlerPayload = { emitter: 'textMention'; - additionalInfo?: TextMentionTouchableHandlerAdditionalInfo; + additionalInfo?: TextMentionTouchableHandlerAdditionalInfo; }; export type UrlTouchableHandlerAdditionalInfo = { url?: string }; @@ -74,15 +66,13 @@ export type UrlTouchableHandlerPayload = { additionalInfo?: UrlTouchableHandlerAdditionalInfo; }; -export type FileAttachmentTouchableHandlerAdditionalInfo< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { attachment?: Attachment }; +export type FileAttachmentTouchableHandlerAdditionalInfo = { + attachment?: Attachment; +}; -export type FileAttachmentTouchableHandlerPayload< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type FileAttachmentTouchableHandlerPayload = { emitter: 'fileAttachment'; - additionalInfo?: FileAttachmentTouchableHandlerAdditionalInfo; + additionalInfo?: FileAttachmentTouchableHandlerAdditionalInfo; }; export type GalleryThumbnailTouchableHandlerAdditionalInfo = { thumbnail?: Thumbnail }; @@ -108,13 +98,11 @@ export type PressableHandlerPayload = { | GalleryThumbnailTouchableHandlerPayload ); -export type MessagePressableHandlerPayload< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = PressableHandlerPayload & { +export type MessagePressableHandlerPayload = PressableHandlerPayload & { /** * Set of action handler functions for various message actions. You can use these functions to perform any action when give interaction occurs. */ - actionHandlers?: MessageActionHandlers; + actionHandlers?: MessageActionHandlers; /** * Additional message touchable handler info. */ @@ -122,12 +110,10 @@ export type MessagePressableHandlerPayload< /** * Message object, on which interaction occurred. */ - message?: MessageType; + message?: LocalMessage; }; -export type MessageActionHandlers< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type MessageActionHandlers = { copyMessage: () => void; deleteMessage: () => void; editMessage: () => void; @@ -140,25 +126,20 @@ export type MessageActionHandlers< toggleMuteUser: () => Promise; toggleReaction: (reactionType: string) => Promise; unpinMessage: () => Promise; - threadReply?: (message: MessageType) => Promise; + threadReply?: (message: LocalMessage) => Promise; }; -export type MessagePropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'channel' | 'enforceUniqueReaction' | 'members'> & +export type MessagePropsWithContext = Pick< + ChannelContextValue, + 'channel' | 'enforceUniqueReaction' | 'members' +> & Pick & Partial< - Omit< - MessageContextValue, - 'groupStyles' | 'handleReaction' | 'message' | 'isMessageAIGenerated' - > - > & - Pick< - MessageContextValue, - 'groupStyles' | 'message' | 'isMessageAIGenerated' + Omit > & + Pick & Pick< - MessagesContextValue, + MessagesContextValue, | 'sendReaction' | 'deleteMessage' | 'dismissKeyboardOnMessageTouch' @@ -194,10 +175,10 @@ export type MessagePropsWithContext< | 'updateMessage' | 'PollContent' > & - Pick, 'openThread'> & + Pick & Pick & { - chatContext: ChatContextValue; - messagesContext: MessagesContextValue; + chatContext: ChatContextValue; + messagesContext: MessagesContextValue; /** * Whether or not users are able to long press messages. */ @@ -209,7 +190,7 @@ export type MessagePropsWithContext< * * @param message A message object to open the thread upon. */ - onThreadSelect?: (message: MessageType) => void; + onThreadSelect?: (message: LocalMessage) => void; showUnreadUnderlay?: boolean; style?: StyleProp; }; @@ -219,11 +200,7 @@ export type MessagePropsWithContext< * we memoized and broke it up to prevent new messages from re-rendering * each individual Message component. */ -const MessageWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessagePropsWithContext, -) => { +const MessageWithContext = (props: MessagePropsWithContext) => { const [messageOverlayVisible, setMessageOverlayVisible] = useState(false); const [isErrorInMessage, setIsErrorInMessage] = useState(false); const [showMessageReactions, setShowMessageReactions] = useState(true); @@ -286,6 +263,7 @@ const MessageWithContext = < threadList = false, updateMessage, } = props; + const { read } = useChannelContext(); const isMessageAIGenerated = messagesContext.isMessageAIGenerated; const isAIGenerated = useMemo( () => isMessageAIGenerated(message), @@ -300,6 +278,7 @@ const MessageWithContext = < screenPadding, }, } = useTheme(); + const readBy = useMemo(() => getReadState(message, read), [message, read]); const showMessageOverlay = async (showMessageReactions = false, selectedReaction?: string) => { await dismissKeyboard(); @@ -331,7 +310,7 @@ const MessageWithContext = < } }; - const onPressQuotedMessage = (quotedMessage: MessageType) => { + const onPressQuotedMessage = (quotedMessage: LocalMessage) => { if (!goToMessage) { return; } @@ -348,7 +327,7 @@ const MessageWithContext = < if (isEditedMessage(message)) { setIsEditedMessageOpen((prevState) => !prevState); } - const quotedMessage = message.quoted_message as MessageType; + const quotedMessage = message.quoted_message; if (error) { setIsErrorInMessage(true); /** @@ -424,17 +403,17 @@ const MessageWithContext = < return acc; }, { - files: [] as Attachment[], - images: [] as Attachment[], - other: [] as Attachment[], - videos: [] as Attachment[], + files: [] as Attachment[], + images: [] as Attachment[], + other: [] as Attachment[], + videos: [] as Attachment[], }, ) : { - files: [] as Attachment[], - images: [] as Attachment[], - other: [] as Attachment[], - videos: [] as Attachment[], + files: [] as Attachment[], + images: [] as Attachment[], + other: [] as Attachment[], + videos: [] as Attachment[], }; /** * Check if any actions to prevent long press @@ -598,7 +577,7 @@ const MessageWithContext = < unpinMessage, }); - const actionHandlers: MessageActionHandlers = { + const actionHandlers: MessageActionHandlers = { copyMessage: handleCopyMessage, deleteMessage: handleDeleteMessage, editMessage: handleEditMessage, @@ -712,6 +691,7 @@ const MessageWithContext = < otherAttachments: attachments.other, preventPress, reactions, + readBy, setIsEditedMessageOpen, showAvatar, showMessageOverlay, @@ -767,10 +747,7 @@ const MessageWithContext = < ); }; -const areEqual = ( - prevProps: MessagePropsWithContext, - nextProps: MessagePropsWithContext, -) => { +const areEqual = (prevProps: MessagePropsWithContext, nextProps: MessagePropsWithContext) => { const { chatContext: { mutedUsers: prevMutedUsers }, goToMessage: prevGoToMessage, @@ -836,8 +813,6 @@ const areEqual = = Partial< - Omit, 'groupStyles' | 'handleReaction' | 'message'> +export type MessageProps = Partial< + Omit > & - Pick, 'groupStyles' | 'message'>; + Pick; /** * Message - A high level component which implements all the logic required for a message. @@ -951,20 +924,16 @@ export type MessageProps< * * @example ./Message.md */ -export const Message = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageProps, -) => { - const { channel, enforceUniqueReaction, members } = useChannelContext(); - const chatContext = useChatContext(); +export const Message = (props: MessageProps) => { + const { channel, enforceUniqueReaction, members } = useChannelContext(); + const chatContext = useChatContext(); const { dismissKeyboard } = useKeyboardContext(); - const messagesContext = useMessagesContext(); - const { openThread } = useThreadContext(); + const messagesContext = useMessagesContext(); + const { openThread } = useThreadContext(); const { t } = useTranslationContext(); return ( - + = Pick< - MessageContextValue, +export type MessageAvatarPropsWithContext = Pick< + MessageContextValue, 'alignment' | 'lastGroupMessage' | 'message' | 'showAvatar' > & - Pick, 'ImageComponent'> & + Pick & Partial>; -const MessageAvatarWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageAvatarPropsWithContext, -) => { +const MessageAvatarWithContext = (props: MessageAvatarPropsWithContext) => { const { alignment, ImageComponent, lastGroupMessage, message, showAvatar, size } = props; const { theme: { @@ -56,9 +49,9 @@ const MessageAvatarWithContext = < ); }; -const areEqual = ( - prevProps: MessageAvatarPropsWithContext, - nextProps: MessageAvatarPropsWithContext, +const areEqual = ( + prevProps: MessageAvatarPropsWithContext, + nextProps: MessageAvatarPropsWithContext, ) => { const { lastGroupMessage: prevLastGroupMessage, message: prevMessage } = prevProps; const { lastGroupMessage: nextLastGroupMessage, message: nextMessage } = nextProps; @@ -84,18 +77,11 @@ const MemoizedMessageAvatar = React.memo( areEqual, ) as typeof MessageAvatarWithContext; -export type MessageAvatarProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; +export type MessageAvatarProps = Partial; -export const MessageAvatar = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageAvatarProps, -) => { - const { alignment, lastGroupMessage, message, showAvatar } = - useMessageContext(); - const { ImageComponent } = useChatContext(); +export const MessageAvatar = (props: MessageAvatarProps) => { + const { alignment, lastGroupMessage, message, showAvatar } = useMessageContext(); + const { ImageComponent } = useChatContext(); return ( = Pick< - MessagesContextValue, +export type MessageBouncePropsWithContext = Pick< + MessagesContextValue, 'setEditingState' | 'removeMessage' | 'retrySendMessage' > & - Pick, 'message'> & { + Pick & { setIsBounceDialogOpen: React.Dispatch>; }; -export const MessageBounceWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageBouncePropsWithContext, -) => { +export const MessageBounceWithContext = (props: MessageBouncePropsWithContext) => { const { t } = useTranslationContext(); const { message, removeMessage, retrySendMessage, setEditingState, setIsBounceDialogOpen } = props; @@ -71,9 +63,9 @@ export const MessageBounceWithContext = < ); }; -const areEqual = ( - prevProps: MessageBouncePropsWithContext, - nextProps: MessageBouncePropsWithContext, +const areEqual = ( + prevProps: MessageBouncePropsWithContext, + nextProps: MessageBouncePropsWithContext, ) => { const { message: prevMessage } = prevProps; const { message: nextMessage } = nextProps; @@ -93,22 +85,15 @@ const MemoizedMessageBounce = React.memo( areEqual, ) as typeof MessageBounceWithContext; -export type MessageBounceProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial> & { +export type MessageBounceProps = Partial & { setIsBounceDialogOpen: React.Dispatch>; }; -export const MessageBounce = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageBounceProps, -) => { - const { message } = useMessageContext(); - const { removeMessage, retrySendMessage, setEditingState } = - useMessagesContext(); +export const MessageBounce = (props: MessageBounceProps) => { + const { message } = useMessageContext(); + const { removeMessage, retrySendMessage, setEditingState } = useMessagesContext(); return ( - + = Pick< - MessageContextValue, +export type MessageContentPropsWithContext = Pick< + MessageContextValue, | 'alignment' | 'goToMessage' | 'groupStyles' @@ -85,7 +83,7 @@ export type MessageContentPropsWithContext< | 'isMessageAIGenerated' > & Pick< - MessagesContextValue, + MessagesContextValue, | 'additionalPressableProps' | 'Attachment' | 'enableMessageGroupingByUser' @@ -120,11 +118,7 @@ export type MessageContentPropsWithContext< /** * Child of MessageSimple that displays a message's content */ -const MessageContentWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageContentPropsWithContext, -) => { +const MessageContentWithContext = (props: MessageContentPropsWithContext) => { const { additionalPressableProps, alignment, @@ -354,7 +348,7 @@ const MessageContentWithContext = < default: return (otherAttachments.length && otherAttachments[0].actions) || isAIGenerated ? null : ( - + ); @@ -367,9 +361,9 @@ const MessageContentWithContext = < ); }; -const areEqual = ( - prevProps: MessageContentPropsWithContext, - nextProps: MessageContentPropsWithContext, +const areEqual = ( + prevProps: MessageContentPropsWithContext, + nextProps: MessageContentPropsWithContext, ) => { const { goToMessage: prevGoToMessage, @@ -507,19 +501,15 @@ const MemoizedMessageContent = React.memo( areEqual, ) as typeof MessageContentWithContext; -export type MessageContentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial, 'setMessageContentWidth'>> & - Pick, 'setMessageContentWidth'>; +export type MessageContentProps = Partial< + Omit +> & + Pick; /** * Child of MessageSimple that displays a message's content */ -export const MessageContent = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageContentProps, -) => { +export const MessageContent = (props: MessageContentProps) => { const { alignment, goToMessage, @@ -536,7 +526,7 @@ export const MessageContent = < otherAttachments, preventPress, threadList, - } = useMessageContext(); + } = useMessageContext(); const { additionalPressableProps, Attachment, @@ -548,11 +538,11 @@ export const MessageContent = < myMessageTheme, Reply, StreamingMessageView, - } = useMessagesContext(); + } = useMessagesContext(); const { t } = useTranslationContext(); return ( - + = Pick, 'alignment' | 'message'> & - Pick, 'MessageFooter'> & +type MessageDeletedPropsWithContext = Pick & + Pick & MessageDeletedComponentProps; -const MessageDeletedWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageDeletedPropsWithContext, -) => { +const MessageDeletedWithContext = (props: MessageDeletedPropsWithContext) => { const { alignment, date, groupStyle, message, MessageFooter, noBorder, onLayout } = props; const { @@ -96,7 +88,7 @@ const MessageDeletedWithContext = < deletedContainerInner, ]} > - + @@ -106,9 +98,9 @@ const MessageDeletedWithContext = < ); }; -const areEqual = ( - prevProps: MessageDeletedPropsWithContext, - nextProps: MessageDeletedPropsWithContext, +const areEqual = ( + prevProps: MessageDeletedPropsWithContext, + nextProps: MessageDeletedPropsWithContext, ) => { const { alignment: prevAlignment, date: prevDate, message: prevMessage } = prevProps; const { alignment: nextAlignment, date: nextDate, message: nextMessage } = nextProps; @@ -144,22 +136,16 @@ const MemoizedMessageDeleted = React.memo( areEqual, ) as typeof MessageDeletedWithContext; -export type MessageDeletedProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial> & { +export type MessageDeletedProps = Partial & { groupStyle: string; noBorder: boolean; onLayout: (event: LayoutChangeEvent) => void; }; -export const MessageDeleted = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageDeletedProps, -) => { - const { alignment, message } = useMessageContext(); +export const MessageDeleted = (props: MessageDeletedProps) => { + const { alignment, message } = useMessageContext(); - const { MessageFooter } = useMessagesContext(); + const { MessageFooter } = useMessagesContext(); return ( = Partial, 'message'>> & - Partial, 'MessageTimestamp'>>; +export type MessageEditedTimestampProps = Partial> & + Partial>; -export const MessageEditedTimestamp = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageEditedTimestampProps, -) => { +export const MessageEditedTimestamp = (props: MessageEditedTimestampProps) => { const { message: propMessage, MessageTimestamp } = props; const { theme: { @@ -31,7 +24,7 @@ export const MessageEditedTimestamp = < }, } = useTheme(); const { t } = useTranslationContext(); - const { message: contextMessage } = useMessageContext(); + const { message: contextMessage } = useMessageContext(); const message = propMessage || contextMessage; if (!isEditedMessage(message)) { diff --git a/package/src/components/Message/MessageSimple/MessageFooter.tsx b/package/src/components/Message/MessageSimple/MessageFooter.tsx index b2a03f622f..2f1af67c84 100644 --- a/package/src/components/Message/MessageSimple/MessageFooter.tsx +++ b/package/src/components/Message/MessageSimple/MessageFooter.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { StyleSheet, Text, View } from 'react-native'; -import type { Attachment } from 'stream-chat'; +import type { Attachment, LocalMessage } from 'stream-chat'; import type { MessageStatusProps } from './MessageStatus'; @@ -20,9 +20,7 @@ import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { Eye } from '../../../icons'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; import { isEditedMessage, MessageStatusTypes } from '../../../utils/utils'; -import type { MessageType } from '../../MessageList/hooks/useMessageList'; type MessageFooterComponentProps = { date?: string | Date; @@ -30,10 +28,8 @@ type MessageFooterComponentProps = { isDeleted?: boolean; }; -type MessageFooterPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - MessageContextValue, +type MessageFooterPropsWithContext = Pick< + MessageContextValue, | 'alignment' | 'isEditedMessageOpen' | 'members' @@ -44,7 +40,7 @@ type MessageFooterPropsWithContext< | 'isMessageAIGenerated' > & Pick< - MessagesContextValue, + MessagesContextValue, | 'deletedMessagesVisibilityType' | 'MessageEditedTimestamp' | 'MessageStatus' @@ -83,11 +79,7 @@ const OnlyVisibleToYouComponent = ({ alignment }: { alignment: Alignment }) => { ); }; -const MessageFooterWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageFooterPropsWithContext, -) => { +const MessageFooterWithContext = (props: MessageFooterPropsWithContext) => { const { alignment, date, @@ -177,9 +169,9 @@ const MessageFooterWithContext = < ); }; -const areEqual = ( - prevProps: MessageFooterPropsWithContext, - nextProps: MessageFooterPropsWithContext, +const areEqual = ( + prevProps: MessageFooterPropsWithContext, + nextProps: MessageFooterPropsWithContext, ) => { const { alignment: prevAlignment, @@ -274,23 +266,17 @@ const MemoizedMessageFooter = React.memo( areEqual, ) as typeof MessageFooterWithContext; -export type MessageFooterProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial, 'members'>> & +export type MessageFooterProps = Partial> & MessageFooterComponentProps & { alignment?: Alignment; lastGroupMessage?: boolean; - message?: MessageType; - MessageStatus?: React.ComponentType>; - otherAttachments?: Attachment[]; + message?: LocalMessage; + MessageStatus?: React.ComponentType; + otherAttachments?: Attachment[]; showMessageStatus?: boolean; }; -export const MessageFooter = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageFooterProps, -) => { +export const MessageFooter = (props: MessageFooterProps) => { const { alignment, isEditedMessageOpen, @@ -300,10 +286,10 @@ export const MessageFooter = < message, otherAttachments, showMessageStatus, - } = useMessageContext(); + } = useMessageContext(); const { deletedMessagesVisibilityType, MessageEditedTimestamp, MessageStatus, MessageTimestamp } = - useMessagesContext(); + useMessagesContext(); return ( >; -export type MessagePinnedHeaderProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial, 'message'>>; - -export const MessagePinnedHeader = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessagePinnedHeaderProps, -) => { +export const MessagePinnedHeader = (props: MessagePinnedHeaderProps) => { const { message: propMessage } = props; - const { message: contextMessage } = useMessageContext(); + const { message: contextMessage } = useMessageContext(); const message = propMessage || contextMessage; const { theme: { diff --git a/package/src/components/Message/MessageSimple/MessageReplies.tsx b/package/src/components/Message/MessageSimple/MessageReplies.tsx index 5b0f7bccf9..d4e15bff5f 100644 --- a/package/src/components/Message/MessageSimple/MessageReplies.tsx +++ b/package/src/components/Message/MessageSimple/MessageReplies.tsx @@ -15,8 +15,6 @@ import { useTranslationContext, } from '../../../contexts/translationContext/TranslationContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; - const styles = StyleSheet.create({ container: { alignItems: 'center', @@ -48,10 +46,8 @@ const styles = StyleSheet.create({ }, }); -export type MessageRepliesPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - MessageContextValue, +export type MessageRepliesPropsWithContext = Pick< + MessageContextValue, | 'alignment' | 'message' | 'onLongPress' @@ -61,17 +57,13 @@ export type MessageRepliesPropsWithContext< | 'preventPress' | 'threadList' > & - Pick, 'MessageRepliesAvatars'> & + Pick & Pick & { noBorder?: boolean; repliesCurveColor?: ColorValue; }; -const MessageRepliesWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageRepliesPropsWithContext, -) => { +const MessageRepliesWithContext = (props: MessageRepliesPropsWithContext) => { const { alignment, message, @@ -175,9 +167,9 @@ const MessageRepliesWithContext = < ); }; -const areEqual = ( - prevProps: MessageRepliesPropsWithContext, - nextProps: MessageRepliesPropsWithContext, +const areEqual = ( + prevProps: MessageRepliesPropsWithContext, + nextProps: MessageRepliesPropsWithContext, ) => { const { message: prevMessage, @@ -234,15 +226,9 @@ const MemoizedMessageReplies = React.memo( areEqual, ) as typeof MessageRepliesWithContext; -export type MessageRepliesProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; +export type MessageRepliesProps = Partial; -export const MessageReplies = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageRepliesProps, -) => { +export const MessageReplies = (props: MessageRepliesProps) => { const { alignment, message, @@ -252,8 +238,8 @@ export const MessageReplies = < onPressIn, preventPress, threadList, - } = useMessageContext(); - const { MessageRepliesAvatars } = useMessagesContext(); + } = useMessageContext(); + const { MessageRepliesAvatars } = useMessagesContext(); const { t } = useTranslationContext(); return ( diff --git a/package/src/components/Message/MessageSimple/MessageRepliesAvatars.tsx b/package/src/components/Message/MessageSimple/MessageRepliesAvatars.tsx index 5485d9a428..715ad80b72 100644 --- a/package/src/components/Message/MessageSimple/MessageRepliesAvatars.tsx +++ b/package/src/components/Message/MessageSimple/MessageRepliesAvatars.tsx @@ -5,7 +5,6 @@ import { ChatContextValue, useChatContext } from '../../../contexts/chatContext/ import type { MessageContextValue } from '../../../contexts/messageContext/MessageContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; import { Avatar } from '../../Avatar/Avatar'; const styles = StyleSheet.create({ @@ -20,14 +19,10 @@ const styles = StyleSheet.create({ }, }); -export type MessageRepliesAvatarsProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'alignment' | 'message'>; +export type MessageRepliesAvatarsProps = Pick; -export const MessageRepliesAvatarsWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageRepliesAvatarsProps & Pick, +export const MessageRepliesAvatarsWithContext = ( + props: MessageRepliesAvatarsProps & Pick, ) => { const { alignment, ImageComponent, message } = props; @@ -88,11 +83,7 @@ export const MessageRepliesAvatarsWithContext = < ); }; -export const MessageRepliesAvatars = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageRepliesAvatarsProps, -) => { +export const MessageRepliesAvatars = (props: MessageRepliesAvatarsProps) => { const { ImageComponent } = useChatContext(); return ; diff --git a/package/src/components/Message/MessageSimple/MessageSimple.tsx b/package/src/components/Message/MessageSimple/MessageSimple.tsx index 693e18a762..ca2e7f0b74 100644 --- a/package/src/components/Message/MessageSimple/MessageSimple.tsx +++ b/package/src/components/Message/MessageSimple/MessageSimple.tsx @@ -25,7 +25,7 @@ import { import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { NativeHandlers } from '../../../native'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { useMessageData } from '../hooks/useMessageData'; const styles = StyleSheet.create({ @@ -56,10 +56,8 @@ const styles = StyleSheet.create({ }, }); -export type MessageSimplePropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - MessageContextValue, +export type MessageSimplePropsWithContext = Pick< + MessageContextValue, | 'alignment' | 'channel' | 'groupStyles' @@ -73,7 +71,7 @@ export type MessageSimplePropsWithContext< | 'showMessageStatus' > & Pick< - MessagesContextValue, + MessagesContextValue, | 'clearQuotedMessageState' | 'enableMessageGroupingByUser' | 'enableSwipeToReply' @@ -94,11 +92,7 @@ export type MessageSimplePropsWithContext< | 'setQuotedMessageState' >; -const MessageSimpleWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageSimplePropsWithContext, -) => { +const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => { const [messageContentWidth, setMessageContentWidth] = useState(0); const { width } = Dimensions.get('screen'); const { @@ -440,9 +434,9 @@ const MessageSimpleWithContext = < ); }; -const areEqual = ( - prevProps: MessageSimplePropsWithContext, - nextProps: MessageSimplePropsWithContext, +const areEqual = ( + prevProps: MessageSimplePropsWithContext, + nextProps: MessageSimplePropsWithContext, ) => { const { channel: prevChannel, @@ -579,19 +573,13 @@ const MemoizedMessageSimple = React.memo( areEqual, ) as typeof MessageSimpleWithContext; -export type MessageSimpleProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; +export type MessageSimpleProps = Partial; /** * * Message UI component */ -export const MessageSimple = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageSimpleProps, -) => { +export const MessageSimple = (props: MessageSimpleProps) => { const { alignment, channel, @@ -604,7 +592,7 @@ export const MessageSimple = < onlyEmojis, otherAttachments, showMessageStatus, - } = useMessageContext(); + } = useMessageContext(); const { clearQuotedMessageState, enableMessageGroupingByUser, @@ -624,10 +612,10 @@ export const MessageSimple = < reactionListPosition, ReactionListTop, setQuotedMessageState, - } = useMessagesContext(); + } = useMessagesContext(); return ( - + = Pick, 'message' | 'threadList'>; +export type MessageStatusPropsWithContext = Pick< + MessageContextValue, + 'message' | 'readBy' | 'threadList' +>; -const MessageStatusWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageStatusPropsWithContext, -) => { - const { channel } = useChannelContext(); - const { message, threadList } = props; +const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => { + const { channel } = useChannelContext(); + const { message, readBy, threadList } = props; const { theme: { @@ -48,17 +42,17 @@ const MessageStatusWithContext = < return null; } - if (isMessageWithStylesReadByAndDateSeparator(message)) { + if (readBy) { const members = channel?.state.members; const otherMembers = Object.values(members).filter( (member) => member.user_id !== message.user?.id, ); const hasOtherMembersGreaterThanOne = otherMembers.length > 1; - const hasReadByGreaterThanOne = typeof message.readBy === 'number' && message.readBy > 1; + const hasReadByGreaterThanOne = typeof readBy === 'number' && readBy > 1; const shouldDisplayReadByCount = hasOtherMembersGreaterThanOne && hasReadByGreaterThanOne; const countOfReadBy = - typeof message.readBy === 'number' && hasOtherMembersGreaterThanOne ? message.readBy - 1 : 0; - const showDoubleCheck = hasReadByGreaterThanOne || message.readBy === true; + typeof readBy === 'number' && hasOtherMembersGreaterThanOne ? readBy - 1 : 0; + const showDoubleCheck = hasReadByGreaterThanOne || readBy === true; return ( @@ -92,23 +86,25 @@ const MessageStatusWithContext = < return null; }; -const areEqual = ( - prevProps: MessageStatusPropsWithContext, - nextProps: MessageStatusPropsWithContext, +const areEqual = ( + prevProps: MessageStatusPropsWithContext, + nextProps: MessageStatusPropsWithContext, ) => { - const { message: prevMessage, threadList: prevThreadList } = prevProps; - const { message: nextMessage, threadList: nextThreadList } = nextProps; + const { message: prevMessage, readBy: prevReadBy, threadList: prevThreadList } = prevProps; + const { message: nextMessage, readBy: nextReadBy, threadList: nextThreadList } = nextProps; const threadListEqual = prevThreadList === nextThreadList; if (!threadListEqual) { return false; } + const readByEqual = prevReadBy === nextReadBy; + if (!readByEqual) { + return false; + } + const messageEqual = - prevMessage.status === nextMessage.status && - prevMessage.type === nextMessage.type && - (isMessageWithStylesReadByAndDateSeparator(prevMessage) && prevMessage.readBy) === - (isMessageWithStylesReadByAndDateSeparator(nextMessage) && nextMessage.readBy); + prevMessage.status === nextMessage.status && prevMessage.type === nextMessage.type; if (!messageEqual) { return false; } @@ -121,18 +117,12 @@ const MemoizedMessageStatus = React.memo( areEqual, ) as typeof MessageStatusWithContext; -export type MessageStatusProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; +export type MessageStatusProps = Partial; -export const MessageStatus = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageStatusProps, -) => { - const { message, threadList } = useMessageContext(); +export const MessageStatus = (props: MessageStatusProps) => { + const { message, readBy, threadList } = useMessageContext(); - return ; + return ; }; MessageStatus.displayName = 'MessageStatus{messageSimple{status}}'; diff --git a/package/src/components/Message/MessageSimple/MessageTextContainer.tsx b/package/src/components/Message/MessageSimple/MessageTextContainer.tsx index e5a2f55988..28b0ac70ca 100644 --- a/package/src/components/Message/MessageSimple/MessageTextContainer.tsx +++ b/package/src/components/Message/MessageSimple/MessageTextContainer.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; +import { LocalMessage } from 'stream-chat'; + import { renderText, RenderTextParams } from './utils/renderText'; import { @@ -15,28 +17,22 @@ import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import type { MarkdownStyle, Theme } from '../../../contexts/themeContext/utils/theme'; import { useTranslatedMessage } from '../../../hooks/useTranslatedMessage'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -import type { MessageType } from '../../MessageList/hooks/useMessageList'; const styles = StyleSheet.create({ textContainer: { maxWidth: 250, paddingHorizontal: 16 }, }); -export type MessageTextProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = MessageTextContainerProps & { - renderText: (params: RenderTextParams) => React.ReactNode | null; +export type MessageTextProps = MessageTextContainerProps & { + renderText: (params: RenderTextParams) => React.ReactNode | null; theme: { theme: Theme }; }; -export type MessageTextContainerPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - MessageContextValue, +export type MessageTextContainerPropsWithContext = Pick< + MessageContextValue, 'message' | 'onLongPress' | 'onlyEmojis' | 'onPress' | 'preventPress' > & Pick< - MessagesContextValue, + MessagesContextValue, 'markdownRules' | 'MessageText' | 'myMessageTheme' | 'messageTextNumberOfLines' > & { markdownStyles?: MarkdownStyle; @@ -46,11 +42,7 @@ export type MessageTextContainerPropsWithContext< }>; }; -const MessageTextContainerWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageTextContainerPropsWithContext, -) => { +const MessageTextContainerWithContext = (props: MessageTextContainerPropsWithContext) => { const theme = useTheme(); const { @@ -79,9 +71,7 @@ const MessageTextContainerWithContext = < }, } = theme; - const translatedMessage = useTranslatedMessage( - message, - ) as MessageType; + const translatedMessage = useTranslatedMessage(message) as LocalMessage; if (!message.text) { return null; @@ -97,7 +87,7 @@ const MessageTextContainerWithContext = < {MessageText ? ( ) : ( - renderText({ + renderText({ colors, markdownRules, markdownStyles: { @@ -117,9 +107,9 @@ const MessageTextContainerWithContext = < ); }; -const areEqual = ( - prevProps: MessageTextContainerPropsWithContext, - nextProps: MessageTextContainerPropsWithContext, +const areEqual = ( + prevProps: MessageTextContainerPropsWithContext, + nextProps: MessageTextContainerPropsWithContext, ) => { const { markdownStyles: prevMarkdownStyles, @@ -188,19 +178,12 @@ const MemoizedMessageTextContainer = React.memo( areEqual, ) as typeof MessageTextContainerWithContext; -export type MessageTextContainerProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; +export type MessageTextContainerProps = Partial; -export const MessageTextContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageTextContainerProps, -) => { - const { message, onLongPress, onlyEmojis, onPress, preventPress } = - useMessageContext(); +export const MessageTextContainer = (props: MessageTextContainerProps) => { + const { message, onLongPress, onlyEmojis, onPress, preventPress } = useMessageContext(); const { markdownRules, MessageText, messageTextNumberOfLines, myMessageTheme } = - useMessagesContext(); + useMessagesContext(); return ( { return ; }; -export type ReactionListBottomItemProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial< +export type ReactionListBottomItemProps = Partial< Pick< - MessageContextValue, + MessageContextValue, | 'handleReaction' | 'onLongPress' | 'onPress' @@ -44,15 +42,11 @@ export type ReactionListBottomItemProps< | 'showMessageOverlay' > > & - Partial, 'supportedReactions'>> & { + Partial> & { reaction: ReactionSummary; }; -export const ReactionListBottomItem = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ReactionListBottomItemProps, -) => { +export const ReactionListBottomItem = (props: ReactionListBottomItemProps) => { const { handleReaction, onLongPress, @@ -174,11 +168,9 @@ const renderItem = ({ index, item }: { index: number; item: ReactionListBottomIt /> ); -export type ReactionListBottomProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial< +export type ReactionListBottomProps = Partial< Pick< - MessageContextValue, + MessageContextValue, | 'handleReaction' | 'hasReactions' | 'onLongPress' @@ -189,13 +181,9 @@ export type ReactionListBottomProps< | 'showMessageOverlay' > > & - Partial, 'supportedReactions'>>; + Partial>; -export const ReactionListBottom = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ReactionListBottomProps, -) => { +export const ReactionListBottom = (props: ReactionListBottomProps) => { const { handleReaction: propHandlerReaction, hasReactions: propHasReactions, @@ -217,10 +205,9 @@ export const ReactionListBottom = < preventPress: contextPreventPress, reactions: contextReactions, showMessageOverlay: contextShowMessageOverlay, - } = useMessageContext(); + } = useMessageContext(); - const { supportedReactions: contextSupportedReactions } = - useMessagesContext(); + const { supportedReactions: contextSupportedReactions } = useMessagesContext(); const handleReaction = propHandlerReaction || contextHandleReaction; const hasReactions = propHasReactions || contextHasReactions; diff --git a/package/src/components/Message/MessageSimple/ReactionList/ReactionListTop.tsx b/package/src/components/Message/MessageSimple/ReactionList/ReactionListTop.tsx index 9344c726cf..51b3b6b795 100644 --- a/package/src/components/Message/MessageSimple/ReactionList/ReactionListTop.tsx +++ b/package/src/components/Message/MessageSimple/ReactionList/ReactionListTop.tsx @@ -14,7 +14,7 @@ import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; import { Unknown } from '../../../../icons/Unknown'; import type { IconProps } from '../../../../icons/utils/base'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; + import type { ReactionData } from '../../../../utils/utils'; import { ReactionSummary } from '../../hooks/useProcessReactions'; @@ -35,19 +35,13 @@ const Icon = ({ pathFill, size, style, supportedReactions, type }: Props) => { ); }; -export type ReactionListTopItemProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial, 'reactions'>> & - Partial, 'supportedReactions'>> & { +export type ReactionListTopItemProps = Partial> & + Partial> & { index: number; reaction: ReactionSummary; }; -export const ReactionListTopItem = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ReactionListTopItemProps, -) => { +export const ReactionListTopItem = (props: ReactionListTopItemProps) => { const { index, reaction, reactions, supportedReactions } = props; const { theme: { @@ -84,11 +78,9 @@ export const ReactionListTopItem = < ); }; -export type ReactionListTopProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial< +export type ReactionListTopProps = Partial< Pick< - MessageContextValue, + MessageContextValue, | 'alignment' | 'hasReactions' | 'onLongPress' @@ -99,7 +91,7 @@ export type ReactionListTopProps< | 'showMessageOverlay' > > & - Pick, 'supportedReactions'> & { + Pick & { messageContentWidth: number; fill?: string; reactionSize?: number; @@ -108,11 +100,7 @@ export type ReactionListTopProps< /** * ReactionListTop - A high level component which implements all the logic required for a message reaction list */ -export const ReactionListTop = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ReactionListTopProps, -) => { +export const ReactionListTop = (props: ReactionListTopProps) => { const { alignment: propAlignment, fill: propFill, @@ -137,10 +125,9 @@ export const ReactionListTop = < preventPress: contextPreventPress, reactions: contextReactions, showMessageOverlay: contextShowMessageOverlay, - } = useMessageContext(); + } = useMessageContext(); - const { supportedReactions: contextSupportedReactions } = - useMessagesContext(); + const { supportedReactions: contextSupportedReactions } = useMessagesContext(); const alignment = propAlignment || contextAlignment; const hasReactions = propHasReactions || contextHasReactions; diff --git a/package/src/components/Message/MessageSimple/StreamingMessageView.tsx b/package/src/components/Message/MessageSimple/StreamingMessageView.tsx index 88dd0d1b64..c9566c21b6 100644 --- a/package/src/components/Message/MessageSimple/StreamingMessageView.tsx +++ b/package/src/components/Message/MessageSimple/StreamingMessageView.tsx @@ -3,23 +3,17 @@ import React from 'react'; import { MessageTextContainer, MessageTextContainerProps } from './MessageTextContainer'; import { useMessageContext } from '../../../contexts'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { useStreamingMessage } from '../hooks/useStreamingMessage'; -export type StreamingMessageViewProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'message'> & { +export type StreamingMessageViewProps = Pick & { letterInterval?: number; renderingLetterCount?: number; }; -export const StreamingMessageView = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: StreamingMessageViewProps, -) => { +export const StreamingMessageView = (props: StreamingMessageViewProps) => { const { letterInterval, message: messageFromProps, renderingLetterCount } = props; - const { message: messageFromContext } = useMessageContext(); + const { message: messageFromContext } = useMessageContext(); const message = messageFromProps || messageFromContext; const { text = '' } = message; const { streamedMessageText } = useStreamingMessage({ diff --git a/package/src/components/Message/MessageSimple/__tests__/MessageStatus.test.js b/package/src/components/Message/MessageSimple/__tests__/MessageStatus.test.js index 400ed93719..85c4ab790f 100644 --- a/package/src/components/Message/MessageSimple/__tests__/MessageStatus.test.js +++ b/package/src/components/Message/MessageSimple/__tests__/MessageStatus.test.js @@ -72,26 +72,32 @@ describe('MessageStatus', () => { it('should render message status with read by container', async () => { const user = generateUser(); - const message = generateMessage({ readBy: 2, user }); + const message = generateMessage({ user }); + const readBy = 2; const { getByTestId, getByText, rerender, toJSON } = renderMessageStatus({ lastReceivedId: message.id, message, + readBy, }); await waitFor(() => { expect(getByTestId('read-by-container')).toBeTruthy(); - expect(getByText((message.readBy - 1).toString())).toBeTruthy(); + expect(getByText((readBy - 1).toString())).toBeTruthy(); }); const staticUser = generateStaticUser(0); - const staticMessage = generateMessage({ readBy: 2, staticUser }); + const staticMessage = generateMessage({ readBy, user: staticUser }); rerender( - + , @@ -100,7 +106,7 @@ describe('MessageStatus', () => { await waitFor(() => { expect(toJSON()).toMatchSnapshot(); expect(getByTestId('read-by-container')).toBeTruthy(); - expect(getByText((staticMessage.readBy - 1).toString())).toBeTruthy(); + expect(getByText((readBy - 1).toString())).toBeTruthy(); }); }); diff --git a/package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx b/package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx index c4abae945b..dd063bca25 100644 --- a/package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx +++ b/package/src/components/Message/MessageSimple/__tests__/MessageTextContainer.test.tsx @@ -3,6 +3,8 @@ import { Text } from 'react-native'; import { cleanup, render, waitFor } from '@testing-library/react-native'; +import { LocalMessage } from 'stream-chat'; + import { OverlayProvider } from '../../../../contexts/overlayContext/OverlayProvider'; import { ThemeProvider } from '../../../../contexts/themeContext/ThemeContext'; import { defaultTheme } from '../../../../contexts/themeContext/utils/theme'; @@ -17,7 +19,6 @@ import { generateStaticUser } from '../../../../mock-builders/generator/user'; import { getTestClientWithUser } from '../../../../mock-builders/mock'; import { Channel } from '../../../Channel/Channel'; import { Chat } from '../../../Chat/Chat'; -import type { MessageType } from '../../../MessageList/hooks/useMessageList'; import { MessageList } from '../../../MessageList/MessageList'; import { MessageTextContainer } from '../MessageTextContainer'; @@ -31,7 +32,7 @@ describe('MessageTextContainer', () => { }); const { getByTestId, getByText, rerender, toJSON } = render( - + , ); @@ -43,7 +44,7 @@ describe('MessageTextContainer', () => { rerender( {message?.text}} /> , @@ -61,7 +62,7 @@ describe('MessageTextContainer', () => { rerender( - + , ); diff --git a/package/src/components/Message/MessageSimple/utils/renderText.tsx b/package/src/components/Message/MessageSimple/utils/renderText.tsx index 0df0ff4802..cf5e2828cc 100644 --- a/package/src/components/Message/MessageSimple/utils/renderText.tsx +++ b/package/src/components/Message/MessageSimple/utils/renderText.tsx @@ -26,15 +26,14 @@ import { State, } from 'simple-markdown'; -import type { UserResponse } from 'stream-chat'; +import type { LocalMessage, UserResponse } from 'stream-chat'; import { generateMarkdownText } from './generateMarkdownText'; import type { MessageContextValue } from '../../../../contexts/messageContext/MessageContext'; import type { Colors, MarkdownStyle } from '../../../../contexts/themeContext/utils/theme'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; + import { escapeRegExp } from '../../../../utils/utils'; -import type { MessageType } from '../../../MessageList/hooks/useMessageList'; type ReactNodeOutput = NodeOutput; type ReactOutput = Output; @@ -158,13 +157,11 @@ const mentionsParseFunction: ParseFunction = (capture, parse, state) => ({ export type MarkdownRules = Partial; -export type RenderTextParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial< - Pick, 'onLongPress' | 'onPress' | 'preventPress'> +export type RenderTextParams = Partial< + Pick > & { colors: typeof Colors; - message: MessageType; + message: LocalMessage; markdownRules?: MarkdownRules; markdownStyles?: MarkdownStyle; messageOverlay?: boolean; @@ -173,11 +170,7 @@ export type RenderTextParams< onlyEmojis?: boolean; }; -export const renderText = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - params: RenderTextParams, -) => { +export const renderText = (params: RenderTextParams) => { const { colors, markdownRules, @@ -368,9 +361,7 @@ export const renderText = < if (!preventPress && onPressParam) { onPressParam({ additionalInfo: { - user: mentioned_users?.find( - (user: UserResponse) => userName === user.name, - ), + user: mentioned_users?.find((user: UserResponse) => userName === user.name), }, emitter: 'textMention', event, diff --git a/package/src/components/Message/hooks/useCreateMessageContext.ts b/package/src/components/Message/hooks/useCreateMessageContext.ts index 5984eb8faa..37a594c6f9 100644 --- a/package/src/components/Message/hooks/useCreateMessageContext.ts +++ b/package/src/components/Message/hooks/useCreateMessageContext.ts @@ -1,12 +1,10 @@ import { useMemo } from 'react'; import type { MessageContextValue } from '../../../contexts/messageContext/MessageContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { stringifyMessage } from '../../../utils/utils'; -export const useCreateMessageContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useCreateMessageContext = ({ actionsEnabled, alignment, channel, @@ -36,13 +34,14 @@ export const useCreateMessageContext = < otherAttachments, preventPress, reactions, + readBy, setIsEditedMessageOpen, showAvatar, showMessageOverlay, showMessageStatus, threadList, videos, -}: MessageContextValue) => { +}: MessageContextValue) => { const groupStylesLength = groupStyles.length; const reactionsValue = reactions.map(({ count, own, type }) => `${own}${type}${count}`).join(); const stringifiedMessage = stringifyMessage(message); @@ -52,7 +51,7 @@ export const useCreateMessageContext = < const quotedMessageDeletedValue = message.quoted_message?.deleted_at; - const messageContext: MessageContextValue = useMemo( + const messageContext: MessageContextValue = useMemo( () => ({ actionsEnabled, alignment, @@ -83,6 +82,7 @@ export const useCreateMessageContext = < otherAttachments, preventPress, reactions, + readBy, setIsEditedMessageOpen, showAvatar, showMessageOverlay, @@ -105,6 +105,7 @@ export const useCreateMessageContext = < stringifiedMessage, myMessageThemeString, reactionsValue, + readBy, showAvatar, showMessageStatus, threadList, diff --git a/package/src/components/Message/hooks/useMessageActionHandlers.ts b/package/src/components/Message/hooks/useMessageActionHandlers.ts index 7107ffdc8f..7432e37597 100644 --- a/package/src/components/Message/hooks/useMessageActionHandlers.ts +++ b/package/src/components/Message/hooks/useMessageActionHandlers.ts @@ -1,7 +1,5 @@ import { Alert } from 'react-native'; -import type { MessageResponse } from 'stream-chat'; - import type { ChannelContextValue } from '../../../contexts/channelContext/ChannelContext'; import type { ChatContextValue } from '../../../contexts/chatContext/ChatContext'; import type { MessageContextValue } from '../../../contexts/messageContext/MessageContext'; @@ -9,11 +7,8 @@ import type { MessagesContextValue } from '../../../contexts/messagesContext/Mes import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { NativeHandlers } from '../../../native'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -export const useMessageActionHandlers = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useMessageActionHandlers = ({ channel, client, deleteMessage, @@ -24,7 +19,7 @@ export const useMessageActionHandlers = < setEditingState, setQuotedMessageState, }: Pick< - MessagesContextValue, + MessagesContextValue, | 'sendReaction' | 'deleteMessage' | 'deleteReaction' @@ -33,9 +28,9 @@ export const useMessageActionHandlers = < | 'setQuotedMessageState' | 'supportedReactions' > & - Pick, 'channel' | 'enforceUniqueReaction'> & - Pick, 'client'> & - Pick, 'message'>) => { + Pick & + Pick & + Pick) => { const { t } = useTranslationContext(); const handleResendMessage = () => retrySendMessage(message); @@ -65,7 +60,7 @@ export const useMessageActionHandlers = < { style: 'cancel', text: t('Cancel') }, { onPress: async () => { - await deleteMessage(message as MessageResponse); + await deleteMessage(message); }, style: 'destructive', text: t('Delete'), diff --git a/package/src/components/Message/hooks/useMessageActions.tsx b/package/src/components/Message/hooks/useMessageActions.tsx index a70b0c6030..5b859148f9 100644 --- a/package/src/components/Message/hooks/useMessageActions.tsx +++ b/package/src/components/Message/hooks/useMessageActions.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { LocalMessage } from 'stream-chat'; + import { useMessageActionHandlers } from './useMessageActionHandlers'; import type { ChannelContextValue } from '../../../contexts/channelContext/ChannelContext'; @@ -23,17 +25,14 @@ import { UnreadIndicator, UserDelete, } from '../../../icons'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { removeReservedFields } from '../../../utils/removeReservedFields'; import { MessageStatusTypes } from '../../../utils/utils'; -import type { MessageType } from '../../MessageList/hooks/useMessageList'; import type { MessageActionType } from '../../MessageMenu/MessageActionListItem'; -export type MessageActionsHookProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - MessagesContextValue, +export type MessageActionsHookProps = Pick< + MessagesContextValue, | 'deleteMessage' | 'sendReaction' | 'handleBan' @@ -57,17 +56,15 @@ export type MessageActionsHookProps< | 'supportedReactions' | 'updateMessage' > & - Pick, 'channel' | 'enforceUniqueReaction'> & - Pick, 'client'> & - Pick, 'openThread'> & - Pick, 'dismissOverlay' | 'message'> & + Pick & + Pick & + Pick & + Pick & Pick & { - onThreadSelect?: (message: MessageType) => void; + onThreadSelect?: (message: LocalMessage) => void; }; -export const useMessageActions = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useMessageActions = ({ channel, client, deleteMessage: deleteMessageFromContext, @@ -96,7 +93,7 @@ export const useMessageActions = < setQuotedMessageState, supportedReactions, t, -}: MessageActionsHookProps) => { +}: MessageActionsHookProps) => { const { theme: { colors: { accent_red, grey }, @@ -297,7 +294,7 @@ export const useMessageActions = < dismissOverlay(); const messageWithoutReservedFields = removeReservedFields(message); if (handleRetry) { - handleRetry(messageWithoutReservedFields as MessageType); + handleRetry(messageWithoutReservedFields as LocalMessage); } await handleResendMessage(); diff --git a/package/src/components/Message/hooks/useMessageData.ts b/package/src/components/Message/hooks/useMessageData.ts index 20cd17d923..7853d51198 100644 --- a/package/src/components/Message/hooks/useMessageData.ts +++ b/package/src/components/Message/hooks/useMessageData.ts @@ -3,26 +3,18 @@ import { useMessageContext, } from '../../../contexts/messageContext/MessageContext'; -import { DefaultStreamChatGenerics } from '../../../types/types'; import { MessageStatusTypes } from '../../../utils/utils'; -export type UseMessageDataProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial< - Pick< - MessageContextValue, - 'channel' | 'groupStyles' | 'isMyMessage' | 'message' - > +export type UseMessageDataProps = Partial< + Pick >; -export const useMessageData = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useMessageData = ({ channel: propChannel, groupStyles: propGroupStyles, isMyMessage: propIsMyMessage, message: propMessage, -}: UseMessageDataProps) => { +}: UseMessageDataProps) => { const { channel: contextChannel, groupStyles: contextGroupStyles, diff --git a/package/src/components/Message/hooks/useProcessReactions.ts b/package/src/components/Message/hooks/useProcessReactions.ts index 22e0e11f9b..7ea35c4bdd 100644 --- a/package/src/components/Message/hooks/useProcessReactions.ts +++ b/package/src/components/Message/hooks/useProcessReactions.ts @@ -7,7 +7,6 @@ import { MessagesContextValue, useMessagesContext, } from '../../../contexts/messagesContext/MessagesContext'; -import { DefaultStreamChatGenerics } from '../../../types/types'; import { ReactionData } from '../../../utils/utils'; export type ReactionSummary = { @@ -23,21 +22,17 @@ export type ReactionSummary = { export type ReactionsComparator = (a: ReactionSummary, b: ReactionSummary) => number; -export type MessageReactionsData< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type MessageReactionsData = { /** An array of the reaction objects to display in the list */ - latest_reactions?: ReactionResponse[]; + latest_reactions?: ReactionResponse[]; /** An array of the own reaction objects to distinguish own reactions visually */ - own_reactions?: ReactionResponse[] | null; + own_reactions?: ReactionResponse[] | null; /** An object containing summary for each reaction type on a message */ reaction_groups?: Record | null; }; -type UseProcessReactionsParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = MessageReactionsData & - Partial, 'supportedReactions'>> & { +type UseProcessReactionsParams = MessageReactionsData & + Partial> & { sortReactions?: ReactionsComparator; }; @@ -49,12 +44,10 @@ export const defaultReactionsSort: ReactionsComparator = (a, b) => { return a.type.localeCompare(b.type, 'en'); }; -const isOwnReaction = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( +const isOwnReaction = ( reactionType: string, - ownReactions?: MessageReactionsData['own_reactions'], - latestReactions?: ReactionResponse[] | null, + ownReactions?: MessageReactionsData['own_reactions'], + latestReactions?: ReactionResponse[] | null, userID?: string, ) => (ownReactions ? ownReactions.some((reaction) => reaction.type === reactionType) : false) || @@ -86,13 +79,9 @@ const getLatestReactedUserNames = (reactionType: string, latestReactions?: React /** * Custom hook to process reactions data from message and return a list of reactions with additional info. */ -export const useProcessReactions = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: UseProcessReactionsParams, -) => { +export const useProcessReactions = (props: UseProcessReactionsParams) => { const { supportedReactions: contextSupportedReactions } = useMessagesContext(); - const { client } = useChatContext(); + const { client } = useChatContext(); const { latest_reactions, @@ -120,12 +109,7 @@ export const useProcessReactions = < Icon: getEmojiByReactionType(reactionType, supportedReactions), lastReactionAt: last_reaction_at ? new Date(last_reaction_at) : null, latestReactedUserNames, - own: isOwnReaction( - reactionType, - own_reactions, - latest_reactions, - client.userID, - ), + own: isOwnReaction(reactionType, own_reactions, latest_reactions, client.userID), type: reactionType, unlistedReactedUserCount: count - latestReactedUserNames.length, }; diff --git a/package/src/components/Message/hooks/useStreamingMessage.ts b/package/src/components/Message/hooks/useStreamingMessage.ts index 793c632c6f..506ba99fe0 100644 --- a/package/src/components/Message/hooks/useStreamingMessage.ts +++ b/package/src/components/Message/hooks/useStreamingMessage.ts @@ -1,12 +1,9 @@ import { useEffect, useRef, useState } from 'react'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; import { StreamingMessageViewProps } from '../MessageSimple/StreamingMessageView'; -export type UseStreamingMessageProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - StreamingMessageViewProps, +export type UseStreamingMessageProps = Pick< + StreamingMessageViewProps, 'letterInterval' | 'renderingLetterCount' > & { text: string }; @@ -21,13 +18,11 @@ const DEFAULT_RENDERING_LETTER_COUNT = 2; * @param {string} text - The text that we want to render in a typewriter fashion. * @returns {{ streamedMessageText: string }} - A substring of the text property, up until we've finished rendering the typewriter animation. */ -export const useStreamingMessage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useStreamingMessage = ({ letterInterval = DEFAULT_LETTER_INTERVAL, renderingLetterCount = DEFAULT_RENDERING_LETTER_COUNT, text, -}: UseStreamingMessageProps): { streamedMessageText: string } => { +}: UseStreamingMessageProps): { streamedMessageText: string } => { const [streamedMessageText, setStreamedMessageText] = useState(text); const textCursor = useRef(text.length); diff --git a/package/src/components/Message/utils/messageActions.ts b/package/src/components/Message/utils/messageActions.ts index dc4696e4b4..8d019238b9 100644 --- a/package/src/components/Message/utils/messageActions.ts +++ b/package/src/components/Message/utils/messageActions.ts @@ -1,12 +1,10 @@ import type { MessageContextValue } from '../../../contexts/messageContext/MessageContext'; import type { OwnCapabilitiesContextValue } from '../../../contexts/ownCapabilitiesContext/OwnCapabilitiesContext'; import { isClipboardAvailable } from '../../../native'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import type { MessageActionType } from '../../MessageMenu/MessageActionListItem'; -export type MessageActionsParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type MessageActionsParams = { banUser: MessageActionType; copyMessage: MessageActionType; deleteMessage: MessageActionType; @@ -27,15 +25,11 @@ export type MessageActionsParams< showMessageReactions: boolean; threadReply: MessageActionType; unpinMessage: MessageActionType; -} & Pick, 'message' | 'isMyMessage'>; +} & Pick; -export type MessageActionsProp< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = (param: MessageActionsParams) => MessageActionType[]; +export type MessageActionsProp = (param: MessageActionsParams) => MessageActionType[]; -export const messageActions = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const messageActions = ({ banUser, copyMessage, deleteMessage, @@ -53,7 +47,7 @@ export const messageActions = < showMessageReactions, threadReply, unpinMessage, -}: MessageActionsParams) => { +}: MessageActionsParams) => { if (showMessageReactions) { return []; } diff --git a/package/src/components/MessageInput/AttachButton.tsx b/package/src/components/MessageInput/AttachButton.tsx index ae53ccbc9e..281387c4b4 100644 --- a/package/src/components/MessageInput/AttachButton.tsx +++ b/package/src/components/MessageInput/AttachButton.tsx @@ -11,21 +11,14 @@ import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { Attach } from '../../icons/Attach'; import { isImageMediaLibraryAvailable } from '../../native'; -import type { DefaultStreamChatGenerics } from '../../types/types'; -type AttachButtonPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'disabled'> & { +type AttachButtonPropsWithContext = Pick & { /** Function that opens attachment options bottom sheet */ handleOnPress?: ((event: GestureResponderEvent) => void) & (() => void); selectedPicker?: 'images'; }; -const AttachButtonWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AttachButtonPropsWithContext, -) => { +const AttachButtonWithContext = (props: AttachButtonPropsWithContext) => { const [showAttachButtonPicker, setShowAttachButtonPicker] = useState(false); const [attachButtonLayoutRectangle, setAttachButtonLayoutRectangle] = useState(); const { disabled, handleOnPress, selectedPicker } = props; diff --git a/package/src/components/MessageInput/CommandsButton.tsx b/package/src/components/MessageInput/CommandsButton.tsx index bd049b8185..8cba55d77c 100644 --- a/package/src/components/MessageInput/CommandsButton.tsx +++ b/package/src/components/MessageInput/CommandsButton.tsx @@ -10,20 +10,12 @@ import { import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { Lightning } from '../../icons/Lightning'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - -type CommandsButtonPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'suggestions'> & { +type CommandsButtonPropsWithContext = Pick & { /** Function that opens commands selector */ handleOnPress?: ((event: GestureResponderEvent) => void) & (() => void); }; -const CommandsButtonWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: CommandsButtonPropsWithContext, -) => { +const CommandsButtonWithContext = (props: CommandsButtonPropsWithContext) => { const { handleOnPress, suggestions } = props; const { @@ -47,9 +39,9 @@ const CommandsButtonWithContext = < ); }; -const areEqual = ( - prevProps: CommandsButtonPropsWithContext, - nextProps: CommandsButtonPropsWithContext, +const areEqual = ( + prevProps: CommandsButtonPropsWithContext, + nextProps: CommandsButtonPropsWithContext, ) => { const { suggestions: prevSuggestions } = prevProps; const { suggestions: nextSuggestions } = nextProps; @@ -67,19 +59,13 @@ const MemoizedCommandsButton = React.memo( areEqual, ) as typeof CommandsButtonWithContext; -export type CommandsButtonProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; +export type CommandsButtonProps = Partial; /** * UI Component for attach button in MessageInput component. */ -export const CommandsButton = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: CommandsButtonProps, -) => { - const { suggestions } = useSuggestionsContext(); +export const CommandsButton = (props: CommandsButtonProps) => { + const { suggestions } = useSuggestionsContext(); return ; }; diff --git a/package/src/components/MessageInput/FileUploadPreview.tsx b/package/src/components/MessageInput/FileUploadPreview.tsx index fd06ec3d91..accf612708 100644 --- a/package/src/components/MessageInput/FileUploadPreview.tsx +++ b/package/src/components/MessageInput/FileUploadPreview.tsx @@ -17,7 +17,7 @@ import { useTranslationContext } from '../../contexts/translationContext/Transla import { Close } from '../../icons/Close'; import { Warning } from '../../icons/Warning'; import { isSoundPackageAvailable } from '../../native'; -import type { DefaultStreamChatGenerics, FileUpload } from '../../types/types'; +import type { AudioUpload, FileUpload } from '../../types/types'; import { getTrimmedAttachmentTitle } from '../../utils/getTrimmedAttachmentTitle'; import { getDurationLabelFromDuration, @@ -123,32 +123,49 @@ const UnsupportedFileTypeOrFileSizeIndicator = ({ ); }; -type FileUploadPreviewPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - MessageInputContextValue, - 'fileUploads' | 'removeFile' | 'uploadFile' | 'setFileUploads' | 'AudioAttachmentUploadPreview' +export type FileUploadPreviewProps = Partial< + Pick< + MessageInputContextValue, + 'fileUploads' | 'removeFile' | 'uploadFile' | 'setFileUploads' | 'AudioAttachmentUploadPreview' + > > & - Pick, 'FileAttachmentIcon'> & - Pick, 'enableOfflineSupport'>; + Partial> & + Partial>; -const FileUploadPreviewWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: FileUploadPreviewPropsWithContext, -) => { +/** + * FileUploadPreview + * UI Component to preview the files set for upload + */ +export const FileUploadPreview = (props: FileUploadPreviewProps) => { const { - AudioAttachmentUploadPreview, - enableOfflineSupport, - FileAttachmentIcon, - fileUploads, - removeFile, - uploadFile, + AudioAttachmentUploadPreview: propAudioAttachmentUploadPreview, + enableOfflineSupport: propEnableOfflineSupport, + FileAttachmentIcon: propFileAttachmentIcon, + fileUploads: propFileUploads, + removeFile: propRemoveFile, + uploadFile: propUploadFile, } = props; - const [filesToDisplay, setFilesToDisplay] = useState([]); + const { enableOfflineSupport: contextEnableOfflineSupport } = useChatContext(); + const { + AudioAttachmentUploadPreview: contextAudioAttachmentUploadPreview, + fileUploads: contextFileUploads, + removeFile: contextRemoveFile, + uploadFile: contextUploadFile, + } = useMessageInputContext(); + const { FileAttachmentIcon: contextFileAttachmentIcon } = useMessagesContext(); + + const enableOfflineSupport = propEnableOfflineSupport ?? contextEnableOfflineSupport; + const AudioAttachmentUploadPreview = + propAudioAttachmentUploadPreview ?? contextAudioAttachmentUploadPreview; + const fileUploads = propFileUploads ?? contextFileUploads; + const removeFile = propRemoveFile ?? contextRemoveFile; + const uploadFile = propUploadFile ?? contextUploadFile; + const FileAttachmentIcon = propFileAttachmentIcon ?? contextFileAttachmentIcon; - const flatListRef = useRef | null>(null); + const [filesToDisplay, setFilesToDisplay] = useState([]); + + const flatListRef = useRef | null>(null); const [flatListWidth, setFlatListWidth] = useState(0); useEffect(() => { @@ -213,9 +230,9 @@ const FileUploadPreviewWithContext = < }, } = useTheme(); - const renderItem = ({ item }: { item: FileUpload }) => { + const renderItem = ({ item }: { item: AudioUpload }) => { const indicatorType = getIndicatorTypeForFileState(item.state, enableOfflineSupport); - const isAudio = item.file.mimeType?.startsWith('audio/'); + const isAudio = item.file.type?.startsWith('audio/'); return ( <> @@ -247,7 +264,7 @@ const FileUploadPreviewWithContext = < ]} > - + ( - prevProps: FileUploadPreviewPropsWithContext, - nextProps: FileUploadPreviewPropsWithContext, -) => { - const { fileUploads: prevFileUploads } = prevProps; - const { fileUploads: nextFileUploads } = nextProps; - - return ( - prevFileUploads.length === nextFileUploads.length && - prevFileUploads.every( - (prevFileUpload, index) => - prevFileUpload.state === nextFileUploads[index].state && - prevFileUpload.paused === nextFileUploads[index].paused && - prevFileUpload.progress === nextFileUploads[index].progress && - prevFileUpload.duration === nextFileUploads[index].duration, - ) - ); -}; - -const MemoizedFileUploadPreview = React.memo( - FileUploadPreviewWithContext, - areEqual, -) as typeof FileUploadPreviewWithContext; - -export type FileUploadPreviewProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; - -/** - * FileUploadPreview - * UI Component to preview the files set for upload - */ -export const FileUploadPreview = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: FileUploadPreviewProps, -) => { - const { enableOfflineSupport } = useChatContext(); - const { AudioAttachmentUploadPreview, fileUploads, removeFile, setFileUploads, uploadFile } = - useMessageInputContext(); - const { FileAttachmentIcon } = useMessagesContext(); - - return ( - - ); -}; - FileUploadPreview.displayName = 'FileUploadPreview{messageInput{fileUploadPreview}}'; diff --git a/package/src/components/MessageInput/ImageUploadPreview.tsx b/package/src/components/MessageInput/ImageUploadPreview.tsx index e6c4068689..e1046f7ba6 100644 --- a/package/src/components/MessageInput/ImageUploadPreview.tsx +++ b/package/src/components/MessageInput/ImageUploadPreview.tsx @@ -20,7 +20,7 @@ import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; import { Close } from '../../icons/Close'; import { Warning } from '../../icons/Warning'; -import type { DefaultStreamChatGenerics, ImageUpload } from '../../types/types'; +import type { FileUpload } from '../../types/types'; import { getIndicatorTypeForFileState, ProgressIndicatorTypes } from '../../utils/utils'; const IMAGE_PREVIEW_SIZE = 100; @@ -73,19 +73,15 @@ const styles = StyleSheet.create({ }, }); -type ImageUploadPreviewPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - MessageInputContextValue, +type ImageUploadPreviewPropsWithContext = Pick< + MessageInputContextValue, 'imageUploads' | 'removeImage' | 'uploadImage' > & - Pick, 'enableOfflineSupport'>; + Pick; -export type ImageUploadPreviewProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; +export type ImageUploadPreviewProps = Partial; -type ImageUploadPreviewItem = { index: number; item: ImageUpload }; +type ImageUploadPreviewItem = { index: number; item: FileUpload }; export const UnsupportedImageTypeIndicator = ({ indicatorType, @@ -114,11 +110,7 @@ export const UnsupportedImageTypeIndicator = ({ ) : null; }; -const ImageUploadPreviewWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ImageUploadPreviewPropsWithContext, -) => { +const ImageUploadPreviewWithContext = (props: ImageUploadPreviewPropsWithContext) => { const { enableOfflineSupport, imageUploads, removeImage, uploadImage } = props; const { @@ -197,9 +189,9 @@ const DismissUpload = ({ onPress }: DismissUploadProps) => { ); }; -const areEqual = ( - prevProps: ImageUploadPreviewPropsWithContext, - nextProps: ImageUploadPreviewPropsWithContext, +const areEqual = ( + prevProps: ImageUploadPreviewPropsWithContext, + nextProps: ImageUploadPreviewPropsWithContext, ) => { const { imageUploads: prevImageUploads } = prevProps; const { imageUploads: nextImageUploads } = nextProps; @@ -220,13 +212,9 @@ const MemoizedImageUploadPreviewWithContext = React.memo( /** * UI Component to preview the images set for upload */ -export const ImageUploadPreview = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ImageUploadPreviewProps, -) => { - const { enableOfflineSupport } = useChatContext(); - const { imageUploads, removeImage, uploadImage } = useMessageInputContext(); +export const ImageUploadPreview = (props: ImageUploadPreviewProps) => { + const { enableOfflineSupport } = useChatContext(); + const { imageUploads, removeImage, uploadImage } = useMessageInputContext(); return ( = Partial>; +export type InputButtonsProps = Partial; -export type InputButtonsWithContextProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - MessageInputContextValue, +export type InputButtonsWithContextProps = Pick< + MessageInputContextValue, | 'AttachButton' | 'CommandsButton' | 'giphyActive' @@ -38,11 +32,7 @@ export type InputButtonsWithContextProps< | 'toggleAttachmentPicker' >; -export const InputButtonsWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: InputButtonsWithContextProps, -) => { +export const InputButtonsWithContext = (props: InputButtonsWithContextProps) => { const { AttachButton, CommandsButton, @@ -94,9 +84,9 @@ export const InputButtonsWithContext = < ); }; -const areEqual = ( - prevProps: InputButtonsWithContextProps, - nextProps: InputButtonsWithContextProps, +const areEqual = ( + prevProps: InputButtonsWithContextProps, + nextProps: InputButtonsWithContextProps, ) => { const { giphyActive: prevGiphyActive, @@ -160,11 +150,7 @@ const MemoizedInputButtonsWithContext = React.memo( areEqual, ) as typeof InputButtonsWithContext; -export const InputButtons = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: InputButtonsProps, -) => { +export const InputButtons = (props: InputButtonsProps) => { const { AttachButton, CommandsButton, @@ -180,7 +166,7 @@ export const InputButtons = < setShowMoreOptions, showMoreOptions, toggleAttachmentPicker, - } = useMessageInputContext(); + } = useMessageInputContext(); return ( = Pick & - Pick, 'isOnline'> & - Pick, 'channel' | 'members' | 'threadList' | 'watchers'> & +type MessageInputPropsWithContext = Pick< + AttachmentPickerContextValue, + 'AttachmentPickerSelectionBar' +> & + Pick & + Pick & Pick< - MessageInputContextValue, + MessageInputContextValue, | 'additionalTextInputProps' | 'asyncIds' | 'audioRecordingEnabled' @@ -128,6 +129,7 @@ type MessageInputPropsWithContext< | 'clearEditingState' | 'clearQuotedMessageState' | 'closeAttachmentPicker' + | 'compressImageQuality' | 'editing' | 'FileUploadPreview' | 'fileUploads' @@ -166,23 +168,19 @@ type MessageInputPropsWithContext< | 'CreatePollContent' | 'StopMessageStreamingButton' > & - Pick, 'Reply'> & + Pick & Pick< - SuggestionsContextValue, + SuggestionsContextValue, | 'AutoCompleteSuggestionHeader' | 'AutoCompleteSuggestionItem' | 'AutoCompleteSuggestionList' | 'suggestions' | 'triggerType' > & - Pick, 'thread'> & + Pick & Pick; -const MessageInputWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageInputPropsWithContext, -) => { +const MessageInputWithContext = (props: MessageInputPropsWithContext) => { const { additionalTextInputProps, asyncIds, @@ -201,6 +199,7 @@ const MessageInputWithContext = < channel, closeAttachmentPicker, closePollCreationDialog, + compressImageQuality, cooldownEndsAt, CooldownTimer, CreatePollContent, @@ -351,7 +350,7 @@ const MessageInputWithContext = < // eslint-disable-next-line react-hooks/exhaustive-deps }, [imagesForInput, imageUploadsLength]); - const uploadImagesHandler = () => { + const uploadImagesHandler = async () => { const imageToUpload = selectedImages.find((selectedImage) => { const uploadedImage = imageUploads.find( (imageUpload) => @@ -361,7 +360,11 @@ const MessageInputWithContext = < }); if (imageToUpload) { - uploadNewImage(imageToUpload); + const compressedImage = await compressedImageURI(imageToUpload, compressImageQuality); + uploadNewImage({ + ...imageToUpload, + uri: compressedImage, + }); } }; @@ -459,16 +462,7 @@ const MessageInputWithContext = < /** * User is editing some message which contains image attachments. **/ - setSelectedImages( - imageUploads - .map((imageUpload) => ({ - height: imageUpload.file.height, - source: imageUpload.file.source, - uri: imageUpload.url || imageUpload.file.uri, - width: imageUpload.file.width, - })) - .filter(Boolean) as Asset[], - ); + setSelectedImages(imageUploads.map((imageUpload) => imageUpload.file)); } } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -493,15 +487,7 @@ const MessageInputWithContext = < /** * User is editing some message which contains video attachments. **/ - setSelectedFiles( - fileUploads.map((fileUpload) => ({ - duration: fileUpload.file.duration, - mimeType: fileUpload.file.mimeType, - name: fileUpload.file.name, - size: fileUpload.file.size, - uri: fileUpload.file.uri, - })), - ); + setSelectedFiles(fileUploads.map((fileUpload) => fileUpload.file)); } } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -555,7 +541,7 @@ const MessageInputWithContext = < }, [asyncIdsString, asyncUploadsString, sendMessageAsync]); const getMembers = () => { - const result: UserResponse[] = []; + const result: UserResponse[] = []; if (members && Object.values(members).length) { Object.values(members).forEach((member) => { if (member.user) { @@ -571,7 +557,7 @@ const MessageInputWithContext = < const users = [...getMembers(), ...getWatchers()]; // make sure we don't list users twice - const uniqueUsers: { [key: string]: UserResponse } = {}; + const uniqueUsers: { [key: string]: UserResponse } = {}; for (const user of users) { if (user && !uniqueUsers[user.id]) { uniqueUsers[user.id] = user; @@ -583,7 +569,7 @@ const MessageInputWithContext = < }; const getWatchers = () => { - const result: UserResponse[] = []; + const result: UserResponse[] = []; if (watchers && Object.values(watchers).length) { result.push(...Object.values(watchers)); } @@ -848,7 +834,7 @@ const MessageInputWithContext = < ) : ( - + @@ -945,9 +931,9 @@ const MessageInputWithContext = < ); }; -const areEqual = ( - prevProps: MessageInputPropsWithContext, - nextProps: MessageInputPropsWithContext, +const areEqual = ( + prevProps: MessageInputPropsWithContext, + nextProps: MessageInputPropsWithContext, ) => { const { additionalTextInputProps: prevAdditionalTextInputProps, @@ -1144,9 +1130,7 @@ const MemoizedMessageInput = React.memo( areEqual, ) as typeof MessageInputWithContext; -export type MessageInputProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; +export type MessageInputProps = Partial; /** * UI Component for message input @@ -1157,16 +1141,12 @@ export type MessageInputProps< * [Suggestions Context](https://getstream.io/chat/docs/sdk/reactnative/contexts/suggestions-context/), and * [Translation Context](https://getstream.io/chat/docs/sdk/reactnative/contexts/translation-context/) */ -export const MessageInput = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageInputProps, -) => { +export const MessageInput = (props: MessageInputProps) => { const { AttachmentPickerSelectionBar } = useAttachmentPickerContext(); const { isOnline } = useChatContext(); const ownCapabilities = useOwnCapabilitiesContext(); - const { channel, members, threadList, watchers } = useChannelContext(); + const { channel, members, threadList, watchers } = useChannelContext(); const { additionalTextInputProps, @@ -1186,6 +1166,7 @@ export const MessageInput = < clearQuotedMessageState, closeAttachmentPicker, closePollCreationDialog, + compressImageQuality, cooldownEndsAt, CooldownTimer, CreatePollContent, @@ -1225,9 +1206,9 @@ export const MessageInput = < text, uploadNewFile, uploadNewImage, - } = useMessageInputContext(); + } = useMessageInputContext(); - const { Reply } = useMessagesContext(); + const { Reply } = useMessagesContext(); const { AutoCompleteSuggestionHeader, @@ -1235,9 +1216,9 @@ export const MessageInput = < AutoCompleteSuggestionList, suggestions, triggerType, - } = useSuggestionsContext(); + } = useSuggestionsContext(); - const { thread } = useThreadContext(); + const { thread } = useThreadContext(); const { t } = useTranslationContext(); @@ -1274,6 +1255,7 @@ export const MessageInput = < clearQuotedMessageState, closeAttachmentPicker, closePollCreationDialog, + compressImageQuality, cooldownEndsAt, CooldownTimer, CreatePollContent, diff --git a/package/src/components/MessageInput/MoreOptionsButton.tsx b/package/src/components/MessageInput/MoreOptionsButton.tsx index 813992adeb..5cda2d416d 100644 --- a/package/src/components/MessageInput/MoreOptionsButton.tsx +++ b/package/src/components/MessageInput/MoreOptionsButton.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import type { GestureResponderEvent } from 'react-native'; import { TouchableOpacity } from 'react-native'; +import type { GestureResponderEvent } from 'react-native'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { CircleRight } from '../../icons/CircleRight'; diff --git a/package/src/components/MessageInput/SendButton.tsx b/package/src/components/MessageInput/SendButton.tsx index 42d4ff5e39..f2bcec8afd 100644 --- a/package/src/components/MessageInput/SendButton.tsx +++ b/package/src/components/MessageInput/SendButton.tsx @@ -11,19 +11,11 @@ import { Search } from '../../icons/Search'; import { SendRight } from '../../icons/SendRight'; import { SendUp } from '../../icons/SendUp'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - -type SendButtonPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'giphyActive' | 'sendMessage'> & { +type SendButtonPropsWithContext = Pick & { /** Disables the button */ disabled: boolean; }; -const SendButtonWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: SendButtonPropsWithContext, -) => { +const SendButtonWithContext = (props: SendButtonPropsWithContext) => { const { disabled = false, giphyActive, sendMessage } = props; const { theme: { @@ -50,10 +42,7 @@ const SendButtonWithContext = < ); }; -const areEqual = ( - prevProps: SendButtonPropsWithContext, - nextProps: SendButtonPropsWithContext, -) => { +const areEqual = (prevProps: SendButtonPropsWithContext, nextProps: SendButtonPropsWithContext) => { const { disabled: prevDisabled, giphyActive: prevGiphyActive, @@ -88,19 +77,13 @@ const MemoizedSendButton = React.memo( areEqual, ) as typeof SendButtonWithContext; -export type SendButtonProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; +export type SendButtonProps = Partial; /** * UI Component for send button in MessageInput component. */ -export const SendButton = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: SendButtonProps, -) => { - const { giphyActive, sendMessage } = useMessageInputContext(); +export const SendButton = (props: SendButtonProps) => { + const { giphyActive, sendMessage } = useMessageInputContext(); return ( ; -export const ShowThreadMessageInChannelButton = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ShowThreadMessageInChannelButtonProps, -) => { +export const ShowThreadMessageInChannelButton = (props: ShowThreadMessageInChannelButtonProps) => { const { t } = useTranslationContext(); - const { allowThreadMessagesInChannel } = useThreadContext(); - const { sendThreadMessageInChannel, setSendThreadMessageInChannel } = - useMessageInputContext(); + const { allowThreadMessagesInChannel } = useThreadContext(); + const { sendThreadMessageInChannel, setSendThreadMessageInChannel } = useMessageInputContext(); return ( { const fileUploads = [ generateFileUploadPreview({ id: 'file-upload-id-1', - mimeType: 'audio/mp3', state: FileState.UPLOADED, + type: 'audio/mp3', }), ]; const removeFile = jest.fn(); diff --git a/package/src/components/MessageInput/__tests__/MessageInput.test.js b/package/src/components/MessageInput/__tests__/MessageInput.test.js index 4fa3726c03..dc3749ead0 100644 --- a/package/src/components/MessageInput/__tests__/MessageInput.test.js +++ b/package/src/components/MessageInput/__tests__/MessageInput.test.js @@ -129,7 +129,9 @@ describe('MessageInput', () => { ); // Both for files and for images triggered in one test itself. - expect(Alert.alert).toHaveBeenCalledTimes(4); + await waitFor(() => { + expect(Alert.alert).toHaveBeenCalledTimes(4); + }); }); it('should start the audio recorder on long press and cleanup on unmount', async () => { diff --git a/package/src/components/MessageInput/components/AudioRecorder/AudioRecorder.tsx b/package/src/components/MessageInput/components/AudioRecorder/AudioRecorder.tsx index 6e376b273e..fed76d05fc 100644 --- a/package/src/components/MessageInput/components/AudioRecorder/AudioRecorder.tsx +++ b/package/src/components/MessageInput/components/AudioRecorder/AudioRecorder.tsx @@ -14,11 +14,11 @@ import { useTranslationContext } from '../../../../contexts/translationContext/T import { ArrowLeft, CircleStop, Delete, Mic, SendCheck } from '../../../../icons'; import { AudioRecordingReturnType } from '../../../../native'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; -type AudioRecorderPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'asyncMessagesMultiSendEnabled'> & { +type AudioRecorderPropsWithContext = Pick< + MessageInputContextValue, + 'asyncMessagesMultiSendEnabled' +> & { /** * Function to stop and delete the voice recording. */ @@ -128,11 +128,7 @@ const DeleteRecording = ({ ); }; -const AudioRecorderWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AudioRecorderPropsWithContext, -) => { +const AudioRecorderWithContext = (props: AudioRecorderPropsWithContext) => { const { asyncMessagesMultiSendEnabled, deleteVoiceRecording, @@ -201,9 +197,9 @@ const AudioRecorderWithContext = < } }; -const areEqual = ( - prevProps: AudioRecorderPropsWithContext, - nextProps: AudioRecorderPropsWithContext, +const areEqual = ( + prevProps: AudioRecorderPropsWithContext, + nextProps: AudioRecorderPropsWithContext, ) => { const { asyncMessagesMultiSendEnabled: prevAsyncMessagesMultiSendEnabled, @@ -254,11 +250,9 @@ const MemoizedAudioRecorder = React.memo( areEqual, ) as typeof AudioRecorderWithContext; -export type AudioRecorderProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial> & +export type AudioRecorderProps = Partial & Pick< - AudioRecorderPropsWithContext, + AudioRecorderPropsWithContext, | 'deleteVoiceRecording' | 'micLocked' | 'recording' @@ -270,12 +264,8 @@ export type AudioRecorderProps< /** * Component to display the Recording UI in the Message Input. */ -export const AudioRecorder = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AudioRecorderProps, -) => { - const { asyncMessagesMultiSendEnabled } = useMessageInputContext(); +export const AudioRecorder = (props: AudioRecorderProps) => { + const { asyncMessagesMultiSendEnabled } = useMessageInputContext(); return ( = Pick, 'asyncMessagesMinimumPressDuration'> & { +type AudioRecordingButtonPropsWithContext = Pick< + MessageInputContextValue, + 'asyncMessagesMinimumPressDuration' +> & { /** * The current voice recording that is in progress. */ @@ -41,11 +40,7 @@ type AudioRecordingButtonPropsWithContext< startVoiceRecording?: () => Promise; }; -const AudioRecordingButtonWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AudioRecordingButtonPropsWithContext, -) => { +const AudioRecordingButtonWithContext = (props: AudioRecordingButtonPropsWithContext) => { const { asyncMessagesMinimumPressDuration, buttonSize, @@ -121,9 +116,9 @@ const AudioRecordingButtonWithContext = < ); }; -const areEqual = ( - prevProps: AudioRecordingButtonPropsWithContext, - nextProps: AudioRecordingButtonPropsWithContext, +const areEqual = ( + prevProps: AudioRecordingButtonPropsWithContext, + nextProps: AudioRecordingButtonPropsWithContext, ) => { const { asyncMessagesMinimumPressDuration: prevAsyncMessagesMinimumPressDuration, @@ -153,21 +148,15 @@ const MemoizedAudioRecordingButton = React.memo( areEqual, ) as typeof AudioRecordingButtonWithContext; -export type AudioRecordingButtonProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial> & { +export type AudioRecordingButtonProps = Partial & { recording: AudioRecordingReturnType; }; /** * Component to display the mic button on the Message Input. */ -export const AudioRecordingButton = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AudioRecordingButtonProps, -) => { - const { asyncMessagesMinimumPressDuration } = useMessageInputContext(); +export const AudioRecordingButton = (props: AudioRecordingButtonProps) => { + const { asyncMessagesMinimumPressDuration } = useMessageInputContext(); return ; }; @@ -177,6 +166,7 @@ const styles = StyleSheet.create({ alignItems: 'center', borderRadius: 50, justifyContent: 'center', + marginLeft: 8, }, }); diff --git a/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx index bcf55d177b..d83d4f022d 100644 --- a/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx +++ b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx @@ -9,11 +9,10 @@ import { } from '../../../../contexts/messageInputContext/MessageInputContext'; import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; - -type AudioRecordingInProgressPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'AudioRecordingWaveform'> & { +type AudioRecordingInProgressPropsWithContext = Pick< + MessageInputContextValue, + 'AudioRecordingWaveform' +> & { /** * The waveform data to be presented to show the audio levels. */ @@ -28,11 +27,7 @@ type AudioRecordingInProgressPropsWithContext< recordingDuration?: number; }; -const AudioRecordingInProgressWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AudioRecordingInProgressPropsWithContext, -) => { +const AudioRecordingInProgressWithContext = (props: AudioRecordingInProgressPropsWithContext) => { const { AudioRecordingWaveform, maxDataPointsDrawn = 80, @@ -60,9 +55,9 @@ const AudioRecordingInProgressWithContext = < ); }; -const areEqual = ( - prevProps: AudioRecordingInProgressPropsWithContext, - nextProps: AudioRecordingInProgressPropsWithContext, +const areEqual = ( + prevProps: AudioRecordingInProgressPropsWithContext, + nextProps: AudioRecordingInProgressPropsWithContext, ) => { const { recordingDuration: prevRecordingDuration } = prevProps; const { recordingDuration: nextRecordingDuration } = nextProps; @@ -81,21 +76,15 @@ const MemoizedAudioRecordingInProgress = React.memo( areEqual, ) as typeof AudioRecordingInProgressWithContext; -export type AudioRecordingInProgressProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial> & { +export type AudioRecordingInProgressProps = Partial & { waveformData: number[]; }; /** * Component displayed when the audio is in the recording state. */ -export const AudioRecordingInProgress = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: AudioRecordingInProgressProps, -) => { - const { AudioRecordingWaveform } = useMessageInputContext(); +export const AudioRecordingInProgress = (props: AudioRecordingInProgressProps) => { + const { AudioRecordingWaveform } = useMessageInputContext(); return ; }; diff --git a/package/src/components/MessageInput/components/InputEditingStateHeader.tsx b/package/src/components/MessageInput/components/InputEditingStateHeader.tsx index 1faab83718..5164971278 100644 --- a/package/src/components/MessageInput/components/InputEditingStateHeader.tsx +++ b/package/src/components/MessageInput/components/InputEditingStateHeader.tsx @@ -9,7 +9,6 @@ import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { CircleClose, Edit } from '../../../icons'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; const styles = StyleSheet.create({ editingBoxHeader: { @@ -24,19 +23,17 @@ const styles = StyleSheet.create({ }, }); -export type InputEditingStateHeaderProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial, 'clearEditingState' | 'resetInput'>>; +export type InputEditingStateHeaderProps = Partial< + Pick +>; -export const InputEditingStateHeader = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const InputEditingStateHeader = ({ clearEditingState: propClearEditingState, resetInput: propResetInput, -}: InputEditingStateHeaderProps) => { +}: InputEditingStateHeaderProps) => { const { t } = useTranslationContext(); const { clearEditingState: contextClearEditingState, resetInput: contextResetInput } = - useMessageInputContext(); + useMessageInputContext(); const clearEditingState = propClearEditingState || contextClearEditingState; const resetInput = propResetInput || contextResetInput; diff --git a/package/src/components/MessageInput/components/InputGiphySearch.tsx b/package/src/components/MessageInput/components/InputGiphySearch.tsx index f9c0638ae7..fc01b85650 100644 --- a/package/src/components/MessageInput/components/InputGiphySearch.tsx +++ b/package/src/components/MessageInput/components/InputGiphySearch.tsx @@ -8,7 +8,7 @@ import { import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { CircleClose, GiphyLightning } from '../../../icons'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { AutoCompleteInput } from '../../AutoCompleteInput/AutoCompleteInput'; import { useCountdown } from '../hooks/useCountdown'; @@ -33,32 +33,28 @@ const styles = StyleSheet.create({ }, }); -export type InputGiphySearchProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial< +export type InputGiphySearchProps = Partial< Pick< - MessageInputContextValue, + MessageInputContextValue, 'additionalTextInputProps' | 'cooldownEndsAt' | 'setGiphyActive' | 'setShowMoreOptions' > > & { disabled: boolean; }; -export const InputGiphySearch = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const InputGiphySearch = ({ additionalTextInputProps: propAdditionalTextInputProps, cooldownEndsAt: propCooldownEndsAt, disabled, setGiphyActive: propSetGiphyActive, setShowMoreOptions: propSetShowMoreOptions, -}: InputGiphySearchProps) => { +}: InputGiphySearchProps) => { const { additionalTextInputProps: contextAdditionalTextInputProps, cooldownEndsAt: contextCooldownEndsAt, setGiphyActive: contextSetGiphyActive, setShowMoreOptions: contextSetShowMoreOptions, - } = useMessageInputContext(); + } = useMessageInputContext(); const additionalTextInputProps = propAdditionalTextInputProps || contextAdditionalTextInputProps; const cooldownEndsAt = propCooldownEndsAt || contextCooldownEndsAt; @@ -84,7 +80,7 @@ export const InputGiphySearch = < GIPHY - + diff --git a/package/src/components/MessageInput/components/InputReplyStateHeader.tsx b/package/src/components/MessageInput/components/InputReplyStateHeader.tsx index 7e45f3145e..8860217967 100644 --- a/package/src/components/MessageInput/components/InputReplyStateHeader.tsx +++ b/package/src/components/MessageInput/components/InputReplyStateHeader.tsx @@ -9,7 +9,6 @@ import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { CircleClose, CurveLineLeftUp } from '../../../icons'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; const styles = StyleSheet.create({ replyBoxHeader: { @@ -24,18 +23,14 @@ const styles = StyleSheet.create({ }, }); -export type InputReplyStateHeaderProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial< - Pick, 'clearQuotedMessageState' | 'resetInput'> +export type InputReplyStateHeaderProps = Partial< + Pick >; -export const InputReplyStateHeader = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const InputReplyStateHeader = ({ clearQuotedMessageState: propClearQuotedMessageState, resetInput: propResetInput, -}: InputReplyStateHeaderProps) => { +}: InputReplyStateHeaderProps) => { const { t } = useTranslationContext(); const { clearQuotedMessageState: contextClearQuotedMessageState, resetInput: contextResetInput } = useMessageInputContext(); diff --git a/package/src/components/MessageInput/hooks/useAudioController.tsx b/package/src/components/MessageInput/hooks/useAudioController.tsx index e9c63d131d..4919a9b74e 100644 --- a/package/src/components/MessageInput/hooks/useAudioController.tsx +++ b/package/src/components/MessageInput/hooks/useAudioController.tsx @@ -10,7 +10,8 @@ import { RecordingStatus, SoundReturnType, } from '../../../native'; -import { File, FileTypes } from '../../../types/types'; +import type { File } from '../../../types/types'; +import { FileTypes } from '../../../types/types'; import { resampleWaveformData } from '../utils/audioSampling'; import { normalizeAudioLevel } from '../utils/normalizeAudioLevel'; @@ -267,18 +268,18 @@ export const useAudioController = () => { const file: File = { duration: durationInSeconds, - mimeType: 'audio/aac', name: `audio_recording_${date}.aac`, - type: FileTypes.VoiceRecording, + size: 0, + type: 'audio/aac', uri: typeof recording !== 'string' ? (recording?.getURI() as string) : (recording as string), waveform_data: resampledWaveformData, }; if (multiSendEnabled) { - await uploadNewFile(file); + await uploadNewFile(file, FileTypes.VoiceRecording); } else { // FIXME: cannot call handleSubmit() directly as the function has stale reference to file uploads - await uploadNewFile(file); + await uploadNewFile(file, FileTypes.VoiceRecording); setIsScheduleForSubmit(true); } resetState(); diff --git a/package/src/components/MessageInput/hooks/useCooldown.ts b/package/src/components/MessageInput/hooks/useCooldown.ts index 12484776f7..7e77d4cfbe 100644 --- a/package/src/components/MessageInput/hooks/useCooldown.ts +++ b/package/src/components/MessageInput/hooks/useCooldown.ts @@ -4,7 +4,7 @@ import { BuiltinRoles, ChannelResponse, Role } from 'stream-chat'; import { useChannelContext } from '../../../contexts/channelContext/ChannelContext'; import { useChatContext } from '../../../contexts/chatContext/ChatContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { ONE_SECOND_IN_MS } from '../../../utils/date'; type Roles = Array; @@ -13,14 +13,12 @@ type Roles = Array; * useCooldown can be used to start a cooldown defined * for a Channel by setting an end time for **/ -export const useCooldown = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { +export const useCooldown = () => { const [endsAt, setEndsAt] = useState(new Date()); - const { client } = useChatContext(); - const { channel } = useChannelContext(); - const { cooldown } = (channel?.data || {}) as ChannelResponse; + const { client } = useChatContext(); + const { channel } = useChannelContext(); + const { cooldown } = (channel?.data || {}) as ChannelResponse; const interval: number = cooldown ?? 0; /** diff --git a/package/src/components/MessageList/InlineLoadingMoreIndicator.tsx b/package/src/components/MessageList/InlineLoadingMoreIndicator.tsx index 334536d7d6..f369cb3eb1 100644 --- a/package/src/components/MessageList/InlineLoadingMoreIndicator.tsx +++ b/package/src/components/MessageList/InlineLoadingMoreIndicator.tsx @@ -4,8 +4,6 @@ import { ActivityIndicator, StyleSheet, View } from 'react-native'; import { usePaginatedMessageListContext } from '../../contexts/paginatedMessageListContext/PaginatedMessageListContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - const styles = StyleSheet.create({ activityIndicatorContainer: { padding: 10, @@ -57,10 +55,8 @@ const MemoizedInlineLoadingMoreIndicator = React.memo( areEqual, ) as typeof InlineLoadingMoreIndicatorWithContext; -export const InlineLoadingMoreIndicator = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const { loadingMore } = usePaginatedMessageListContext(); +export const InlineLoadingMoreIndicator = () => { + const { loadingMore } = usePaginatedMessageListContext(); return ; }; diff --git a/package/src/components/MessageList/InlineLoadingMoreRecentIndicator.tsx b/package/src/components/MessageList/InlineLoadingMoreRecentIndicator.tsx index 7027c26818..11fccfcea9 100644 --- a/package/src/components/MessageList/InlineLoadingMoreRecentIndicator.tsx +++ b/package/src/components/MessageList/InlineLoadingMoreRecentIndicator.tsx @@ -4,8 +4,6 @@ import { ActivityIndicator, StyleSheet, View } from 'react-native'; import { usePaginatedMessageListContext } from '../../contexts/paginatedMessageListContext/PaginatedMessageListContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - const styles = StyleSheet.create({ activityIndicatorContainer: { padding: 10, @@ -57,10 +55,8 @@ const MemoizedInlineLoadingMoreRecentIndicator = React.memo( areEqual, ) as typeof InlineLoadingMoreRecentIndicatorWithContext; -export const InlineLoadingMoreRecentIndicator = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const { loadingMoreRecent } = usePaginatedMessageListContext(); +export const InlineLoadingMoreRecentIndicator = () => { + const { loadingMoreRecent } = usePaginatedMessageListContext(); return ; }; diff --git a/package/src/components/MessageList/InlineLoadingMoreRecentThreadIndicator.tsx b/package/src/components/MessageList/InlineLoadingMoreRecentThreadIndicator.tsx index b68b393401..fd2e23c946 100644 --- a/package/src/components/MessageList/InlineLoadingMoreRecentThreadIndicator.tsx +++ b/package/src/components/MessageList/InlineLoadingMoreRecentThreadIndicator.tsx @@ -4,8 +4,6 @@ import { ActivityIndicator, StyleSheet, View } from 'react-native'; import { useThreadContext } from '../../contexts'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - const styles = StyleSheet.create({ activityIndicatorContainer: { padding: 10, @@ -57,10 +55,8 @@ const MemoizedInlineLoadingMoreRecentIndicator = React.memo( areEqual, ) as typeof InlineLoadingMoreRecentIndicatorWithContext; -export const InlineLoadingMoreRecentThreadIndicator = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const { threadLoadingMoreRecent } = useThreadContext(); +export const InlineLoadingMoreRecentThreadIndicator = () => { + const { threadLoadingMoreRecent } = useThreadContext(); return ; }; diff --git a/package/src/components/MessageList/MessageList.tsx b/package/src/components/MessageList/MessageList.tsx index 34922dae03..a5fa2a9883 100644 --- a/package/src/components/MessageList/MessageList.tsx +++ b/package/src/components/MessageList/MessageList.tsx @@ -10,13 +10,9 @@ import { ViewToken, } from 'react-native'; -import type { Channel, Event, FormatMessageResponse, MessageResponse } from 'stream-chat'; +import type { Channel, Event, LocalMessage, MessageResponse } from 'stream-chat'; -import { - isMessageWithStylesReadByAndDateSeparator, - MessageType, - useMessageList, -} from './hooks/useMessageList'; +import { useMessageList } from './hooks/useMessageList'; import { useShouldScrollToRecentOnNewOwnMessage } from './hooks/useShouldScrollToRecentOnNewOwnMessage'; import { InlineLoadingMoreIndicator } from './InlineLoadingMoreIndicator'; @@ -54,7 +50,7 @@ import { mergeThemes, ThemeProvider, useTheme } from '../../contexts/themeContex import { ThreadContextValue, useThreadContext } from '../../contexts/threadContext/ThreadContext'; import { useStableCallback } from '../../hooks'; -import { DefaultStreamChatGenerics, FileTypes } from '../../types/types'; +import { FileTypes } from '../../types/types'; // This is just to make sure that the scrolling happens in a different task queue. // TODO: Think if we really need this and strive to remove it if we can. @@ -91,11 +87,7 @@ const styles = StyleSheet.create({ }, }); -const keyExtractor = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - item: MessageType, -) => { +const keyExtractor = (item: LocalMessage) => { if (item.id) { return item.id; } @@ -109,23 +101,13 @@ const flatListViewabilityConfig: ViewabilityConfig = { viewAreaCoveragePercentThreshold: 1, }; -const hasReadLastMessage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, - userId: string, -) => { +const hasReadLastMessage = (channel: Channel, userId: string) => { const latestMessageIdInChannel = channel.state.latestMessages.slice(-1)[0]?.id; const lastReadMessageIdServer = channel.state.read[userId]?.last_read_message_id; return latestMessageIdInChannel === lastReadMessageIdServer; }; -const getPreviousLastMessage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - messages: MessageType[], - newMessage?: MessageResponse, -) => { +const getPreviousLastMessage = (messages: LocalMessage[], newMessage?: MessageResponse) => { if (!newMessage) return; let previousLastMessage; for (let i = messages.length - 1; i >= 0; i--) { @@ -139,11 +121,12 @@ const getPreviousLastMessage = < return previousLastMessage; }; -type MessageListPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick & +type MessageListPropsWithContext = Pick< + AttachmentPickerContextValue, + 'closePicker' | 'selectedPicker' | 'setSelectedPicker' +> & Pick< - ChannelContextValue, + ChannelContextValue, | 'channel' | 'channelUnreadState' | 'disabled' @@ -163,12 +146,12 @@ type MessageListPropsWithContext< | 'targetedMessage' | 'threadList' > & - Pick, 'client'> & - Pick, 'setMessages'> & - Pick, 'loadMore' | 'loadMoreRecent'> & + Pick & + Pick & + Pick & Pick & Pick< - MessagesContextValue, + MessagesContextValue, | 'DateHeader' | 'disableTypingIndicator' | 'FlatList' @@ -185,7 +168,7 @@ type MessageListPropsWithContext< | 'UnreadMessagesNotification' > & Pick< - ThreadContextValue, + ThreadContextValue, 'loadMoreRecentThread' | 'loadMoreThread' | 'thread' | 'threadInstance' > & { /** @@ -202,7 +185,7 @@ type MessageListPropsWithContext< * additionalFlatListProps={{ bounces: true, keyboardDismissMode: true }} /> * ``` */ - additionalFlatListProps?: Partial>>; + additionalFlatListProps?: Partial>; /** * UI component for footer of message list. By default message list will use `InlineLoadingMoreIndicator` * as FooterComponent. If you want to implement your own inline loading indicator, you can access `loadingMore` @@ -232,7 +215,7 @@ type MessageListPropsWithContext< * * @param message A message object to open the thread upon. */ - onThreadSelect?: (message: ThreadContextValue['thread']) => void; + onThreadSelect?: (message: ThreadContextValue['thread']) => void; /** * Use `setFlatListRef` to get access to ref to inner FlatList. * @@ -244,7 +227,7 @@ type MessageListPropsWithContext< * }} * ``` */ - setFlatListRef?: (ref: FlatListType> | null) => void; + setFlatListRef?: (ref: FlatListType | null) => void; }; /** @@ -256,11 +239,7 @@ type MessageListPropsWithContext< * [ThreadContext](https://getstream.io/chat/docs/sdk/reactnative/contexts/thread-context/) * [TranslationContext](https://getstream.io/chat/docs/sdk/reactnative/contexts/translation-context/) */ -const MessageListWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageListPropsWithContext, -) => { +const MessageListWithContext = (props: MessageListPropsWithContext) => { const LoadingMoreRecentIndicator = props.threadList ? InlineLoadingMoreRecentThreadIndicator : InlineLoadingMoreRecentIndicator; @@ -339,10 +318,11 @@ const MessageListWithContext = < * NOTE: rawMessageList changes only when messages array state changes * processedMessageList changes on any state change */ - const { processedMessageList, rawMessageList } = useMessageList({ - noGroupByUser, - threadList, - }); + const { dateSeparatorsRef, messageGroupStylesRef, processedMessageList, rawMessageList } = + useMessageList({ + noGroupByUser, + threadList, + }); const messageListLengthBeforeUpdate = useRef(0); const messageListLengthAfterUpdate = processedMessageList.length; @@ -350,11 +330,9 @@ const MessageListWithContext = < * We need topMessage and channelLastRead values to set the initial scroll position. * So these values only get used if `initialScrollToFirstUnreadMessage` prop is true. */ - const topMessageBeforeUpdate = useRef>(undefined); - const latestNonCurrentMessageBeforeUpdateRef = - useRef>(undefined); - const topMessageAfterUpdate: FormatMessageResponse | undefined = - rawMessageList[0]; + const topMessageBeforeUpdate = useRef(undefined); + const latestNonCurrentMessageBeforeUpdateRef = useRef(undefined); + const topMessageAfterUpdate: LocalMessage | undefined = rawMessageList[0]; const shouldScrollToRecentOnNewOwnMessageRef = useShouldScrollToRecentOnNewOwnMessage( rawMessageList, @@ -381,7 +359,7 @@ const MessageListWithContext = < const onStartReachedInPromise = useRef | null>(null); const onEndReachedInPromise = useRef | null>(null); - const flatListRef = useRef> | null>(null); + const flatListRef = useRef | null>(null); const channelResyncScrollSet = useRef(true); @@ -515,7 +493,7 @@ const MessageListWithContext = < const onViewableItemsChanged = useRef(unstableOnViewableItemsChanged); onViewableItemsChanged.current = unstableOnViewableItemsChanged; - const stableOnViwableItemsChanged = useCallback( + const stableOnViewableItemsChanged = useCallback( ({ viewableItems }: { viewableItems: ViewToken[] | undefined }) => { onViewableItemsChanged.current({ viewableItems }); }, @@ -549,16 +527,13 @@ const MessageListWithContext = < ); }; - const handleEvent = async (event: Event) => { + const handleEvent = async (event: Event) => { const mainChannelUpdated = !event.message?.parent_id || event.message?.show_in_channel; // When the scrollToBottomButtonVisible is true, we need to manually update the channelUnreadState. if (scrollToBottomButtonVisible || channelUnreadState?.first_unread_message_id) { setChannelUnreadState((prev) => { const previousUnreadCount = prev?.unread_messages ?? 0; - const previousLastMessage = getPreviousLastMessage( - channel.state.messages, - event.message, - ); + const previousLastMessage = getPreviousLastMessage(channel.state.messages, event.message); return { ...(prev || {}), last_read: @@ -757,7 +732,7 @@ const MessageListWithContext = < const shouldApplyAndroidWorkaround = inverted && Platform.OS === 'android'; const renderItem = useCallback( - ({ index, item: message }: { index: number; item: MessageType }) => { + ({ index, item: message }: { index: number; item: LocalMessage }) => { if (!channel || channel.disconnected || (!channel.initialized && !channel.offlineMode)) { return null; } @@ -778,14 +753,14 @@ const MessageListWithContext = < const showUnreadUnderlay = !!shouldShowUnreadUnderlay && showUnreadSeparator; const wrapMessageInTheme = client.userID === message.user?.id && !!myMessageTheme; - const renderDateSeperator = isMessageWithStylesReadByAndDateSeparator(message) && - message.dateSeparator && ; + const renderDateSeperator = dateSeparatorsRef.current[message.id] && ( + + ); + const renderMessage = ( (0); const failScrollTimeoutId = useRef>(undefined); - const onScrollToIndexFailedRef = useRef< - FlatListProps>['onScrollToIndexFailed'] - >((info) => { - // We got a failure as we tried to scroll to an item that was outside the render length - if (!flatListRef.current) { - return; - } - // we don't know the actual size of all items but we can see the average, so scroll to the closest offset - // since we used only an average offset... we won't go to the center of the item yet - // with a little delay to wait for scroll to offset to complete, we can then scroll to the index - failScrollTimeoutId.current = setTimeout(() => { - try { - flatListRef.current?.scrollToIndex({ - animated: true, - index: info.index, - viewPosition: 0.5, // try to place message in the center of the screen - }); - if (messageIdLastScrolledToRef.current) { - // in case the target message was cleared out - // the state being set again will trigger the highlight again - setTargetedMessage(messageIdLastScrolledToRef.current); - } - scrollToIndexFailedRetryCountRef.current = 0; - } catch (e) { - if ( - !onScrollToIndexFailedRef.current || - scrollToIndexFailedRetryCountRef.current > MAX_RETRIES_AFTER_SCROLL_FAILURE - ) { + const onScrollToIndexFailedRef = useRef['onScrollToIndexFailed']>( + (info) => { + // We got a failure as we tried to scroll to an item that was outside the render length + if (!flatListRef.current) { + return; + } + // we don't know the actual size of all items but we can see the average, so scroll to the closest offset + // since we used only an average offset... we won't go to the center of the item yet + // with a little delay to wait for scroll to offset to complete, we can then scroll to the index + failScrollTimeoutId.current = setTimeout(() => { + try { + flatListRef.current?.scrollToIndex({ + animated: true, + index: info.index, + viewPosition: 0.5, // try to place message in the center of the screen + }); + if (messageIdLastScrolledToRef.current) { + // in case the target message was cleared out + // the state being set again will trigger the highlight again + setTargetedMessage(messageIdLastScrolledToRef.current); + } scrollToIndexFailedRetryCountRef.current = 0; - return; + } catch (e) { + if ( + !onScrollToIndexFailedRef.current || + scrollToIndexFailedRetryCountRef.current > MAX_RETRIES_AFTER_SCROLL_FAILURE + ) { + scrollToIndexFailedRetryCountRef.current = 0; + return; + } + // At some cases the index we're trying to scroll to, doesn't exist yet in the messageList + // Scrolling to an index not in range of the Flatlist's data will result in a crash that + // won't call onScrollToIndexFailed. + // By catching this error we retry scrolling by calling onScrollToIndexFailedRef + scrollToIndexFailedRetryCountRef.current += 1; + onScrollToIndexFailedRef.current(info); } - // At some cases the index we're trying to scroll to, doesn't exist yet in the messageList - // Scrolling to an index not in range of the Flatlist's data will result in a crash that - // won't call onScrollToIndexFailed. - // By catching this error we retry scrolling by calling onScrollToIndexFailedRef - scrollToIndexFailedRetryCountRef.current += 1; - onScrollToIndexFailedRef.current(info); - } - }, WAIT_FOR_SCROLL_TIMEOUT); + }, WAIT_FOR_SCROLL_TIMEOUT); - // Only when index is greater than 0 and in range of items in FlatList - // this onScrollToIndexFailed will be called again - }); + // Only when index is greater than 0 and in range of items in FlatList + // this onScrollToIndexFailed will be called again + }, + ); const messagesWithImages = legacyImageViewerSwipeBehaviour && @@ -1105,7 +1082,7 @@ const MessageListWithContext = < isListActive && ((threadList && thread) || (!threadList && !thread)) ) { - setMessages(messagesWithImages as MessageType[]); + setMessages(messagesWithImages as LocalMessage[]); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ @@ -1134,7 +1111,7 @@ const MessageListWithContext = < onUserScrollEvent(event); }); - const refCallback = useStableCallback((ref: FlatListType>) => { + const refCallback = useStableCallback((ref: FlatListType) => { flatListRef.current = ref; if (setFlatListRef) { @@ -1273,7 +1250,7 @@ const MessageListWithContext = < onScrollEndDrag={onScrollEndDrag} onScrollToIndexFailed={onScrollToIndexFailedRef.current} onTouchEnd={dismissImagePicker} - onViewableItemsChanged={stableOnViwableItemsChanged} + onViewableItemsChanged={stableOnViewableItemsChanged} ref={refCallback} renderItem={renderItem} scrollEnabled={overlay === 'none'} @@ -1307,15 +1284,9 @@ const MessageListWithContext = < ); }; -export type MessageListProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial>; +export type MessageListProps = Partial; -export const MessageList = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageListProps, -) => { +export const MessageList = (props: MessageListProps) => { const { closePicker, selectedPicker, setSelectedPicker } = useAttachmentPickerContext(); const { channel, @@ -1339,9 +1310,9 @@ export const MessageList = < StickyHeader, targetedMessage, threadList, - } = useChannelContext(); - const { client } = useChatContext(); - const { setMessages } = useImageGalleryContext(); + } = useChannelContext(); + const { client } = useChatContext(); + const { setMessages } = useImageGalleryContext(); const { DateHeader, disableTypingIndicator, @@ -1357,11 +1328,10 @@ export const MessageList = < TypingIndicator, TypingIndicatorContainer, UnreadMessagesNotification, - } = useMessagesContext(); - const { loadMore, loadMoreRecent } = usePaginatedMessageListContext(); + } = useMessagesContext(); + const { loadMore, loadMoreRecent } = usePaginatedMessageListContext(); const { overlay } = useOverlayContext(); - const { loadMoreRecentThread, loadMoreThread, thread, threadInstance } = - useThreadContext(); + const { loadMoreRecentThread, loadMoreThread, thread, threadInstance } = useThreadContext(); return ( = { +export type MessageSystemProps = { /** Current [message object](https://getstream.io/chat/docs/#message_format) */ - message: MessageType; + message: LocalMessage; /** * Additional styles for the system message container. */ @@ -28,11 +25,7 @@ export type MessageSystemProps< * they can attach a message with that update. That message will be available * in message list as (type) system message. */ -export const MessageSystem = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageSystemProps, -) => { +export const MessageSystem = (props: MessageSystemProps) => { const { message, style } = props; const { diff --git a/package/src/components/MessageList/StickyHeader.tsx b/package/src/components/MessageList/StickyHeader.tsx index e48c9463e0..9ecaefac70 100644 --- a/package/src/components/MessageList/StickyHeader.tsx +++ b/package/src/components/MessageList/StickyHeader.tsx @@ -3,15 +3,12 @@ import React, { useMemo } from 'react'; import { MessagesContextValue } from '../../contexts/messagesContext/MessagesContext'; import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; -import { DefaultStreamChatGenerics } from '../../types/types'; import { getDateString } from '../../utils/i18n/getDateString'; /** * Props for the StickyHeader component. */ -export type StickyHeaderProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'DateHeader'> & { +export type StickyHeaderProps = Pick & { /** * Date to be displayed in the sticky header. */ diff --git a/package/src/components/MessageList/TypingIndicator.tsx b/package/src/components/MessageList/TypingIndicator.tsx index a1e96646d6..9d457b13f6 100644 --- a/package/src/components/MessageList/TypingIndicator.tsx +++ b/package/src/components/MessageList/TypingIndicator.tsx @@ -5,7 +5,6 @@ import { useTypingString } from './hooks/useTypingString'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { LoadingDots } from '../Indicators/LoadingDots'; const styles = StyleSheet.create({ @@ -23,16 +22,14 @@ const styles = StyleSheet.create({ }, }); -export const TypingIndicator = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { +export const TypingIndicator = () => { const { theme: { colors: { grey, white_snow }, typingIndicator: { container, text }, }, } = useTheme(); - const typingString = useTypingString(); + const typingString = useTypingString(); return ( = Pick, 'typing'> & - Pick, 'client'> & - Pick, 'thread'>; +type TypingIndicatorContainerPropsWithContext = Pick & + Pick & + Pick; -const TypingIndicatorContainerWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: PropsWithChildren>, +const TypingIndicatorContainerWithContext = ( + props: PropsWithChildren, ) => { const { children, client, thread, typing } = props; @@ -49,18 +43,14 @@ const TypingIndicatorContainerWithContext = < ); }; -export type TypingIndicatorContainerProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = PropsWithChildren>>; +export type TypingIndicatorContainerProps = PropsWithChildren< + Partial +>; -export const TypingIndicatorContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: TypingIndicatorContainerProps, -) => { - const { typing } = useTypingContext(); - const { client } = useChatContext(); - const { thread } = useThreadContext(); +export const TypingIndicatorContainer = (props: TypingIndicatorContainerProps) => { + const { typing } = useTypingContext(); + const { client } = useChatContext(); + const { thread } = useThreadContext(); return ; }; diff --git a/package/src/components/MessageList/__tests__/useMessageList.test.tsx b/package/src/components/MessageList/__tests__/useMessageList.test.tsx index 30e6b8cafd..e9191a204d 100644 --- a/package/src/components/MessageList/__tests__/useMessageList.test.tsx +++ b/package/src/components/MessageList/__tests__/useMessageList.test.tsx @@ -2,7 +2,7 @@ import React, { FC } from 'react'; import { renderHook } from '@testing-library/react-native'; -import type { DefaultGenerics, StreamChat } from 'stream-chat'; +import type { StreamChat } from 'stream-chat'; import { useCreatePaginatedMessageListContext } from '../../../components/Channel/hooks/useCreatePaginatedMessageListContext'; import { @@ -15,11 +15,11 @@ import { import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; import { getTestClientWithUser } from '../../../mock-builders/mock'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { useMessageList } from '../hooks/useMessageList'; const clientUser = generateUser(); -let chatClient: StreamChat | StreamChat; +let chatClient: StreamChat; beforeEach(async () => { chatClient = await getTestClientWithUser(clientUser); diff --git a/package/src/components/MessageList/hooks/useLastReadData.ts b/package/src/components/MessageList/hooks/useLastReadData.ts deleted file mode 100644 index f0922a08f8..0000000000 --- a/package/src/components/MessageList/hooks/useLastReadData.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useMemo } from 'react'; - -import type { ChannelState } from 'stream-chat'; - -import { PaginatedMessageListContextValue } from '../../../contexts/paginatedMessageListContext/PaginatedMessageListContext'; -import { ThreadContextValue } from '../../../contexts/threadContext/ThreadContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -import { getReadStates } from '../utils/getReadStates'; - -type UseLastReadDataParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - messages: - | PaginatedMessageListContextValue['messages'] - | ThreadContextValue['threadMessages']; - userID: string | undefined; - read?: ChannelState['read']; - returnAllReadData?: boolean; -}; - -export const useLastReadData = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: UseLastReadDataParams, -) => { - const { messages, read, returnAllReadData = true, userID } = props; - - return useMemo( - () => - getReadStates( - messages.filter(({ user }) => user?.id === userID), - read, - returnAllReadData, - ), - [messages, read, returnAllReadData, userID], - ); -}; diff --git a/package/src/components/MessageList/hooks/useMessageList.ts b/package/src/components/MessageList/hooks/useMessageList.ts index 2bba85ee69..3518ae1fd5 100644 --- a/package/src/components/MessageList/hooks/useMessageList.ts +++ b/package/src/components/MessageList/hooks/useMessageList.ts @@ -1,8 +1,6 @@ -import { useMemo } from 'react'; +import { useMemo, useRef } from 'react'; -import type { ChannelState, MessageResponse } from 'stream-chat'; - -import { useLastReadData } from './useLastReadData'; +import type { LocalMessage } from 'stream-chat'; import { useChannelContext } from '../../../contexts/channelContext/ChannelContext'; import { useChatContext } from '../../../contexts/chatContext/ChatContext'; @@ -12,8 +10,8 @@ import { } from '../../../contexts/messagesContext/MessagesContext'; import { usePaginatedMessageListContext } from '../../../contexts/paginatedMessageListContext/PaginatedMessageListContext'; import { useThreadContext } from '../../../contexts/threadContext/ThreadContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -import { getDateSeparators } from '../utils/getDateSeparators'; + +import { DateSeparators, getDateSeparators } from '../utils/getDateSeparators'; import { getGroupStyles } from '../utils/getGroupStyles'; export type UseMessageListParams = { @@ -24,32 +22,12 @@ export type UseMessageListParams = { export type GroupType = string; -export type MessagesWithStylesReadByAndDateSeparator< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = MessageResponse & { - groupStyles: GroupType[]; - readBy: boolean | number; - dateSeparator?: Date; +export type MessageGroupStyles = { + [key: string]: string[]; }; -export type MessageType< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = - | ReturnType['formatMessage']> - | MessagesWithStylesReadByAndDateSeparator; - -// Type guards to check MessageType -export const isMessageWithStylesReadByAndDateSeparator = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - message: MessageType, -): message is MessagesWithStylesReadByAndDateSeparator => - (message as MessagesWithStylesReadByAndDateSeparator).readBy !== undefined; - -export const shouldIncludeMessageInList = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - message: MessageType, +export const shouldIncludeMessageInList = ( + message: LocalMessage, options: { deletedMessagesVisibilityType?: DeletedMessagesVisibilityType; userId?: string }, ) => { const { deletedMessagesVisibilityType, userId } = options; @@ -69,48 +47,55 @@ export const shouldIncludeMessageInList = < } }; -export const useMessageList = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - params: UseMessageListParams, -) => { +export const useMessageList = (params: UseMessageListParams) => { const { noGroupByUser, threadList } = params; - const { client } = useChatContext(); - const { hideDateSeparators, maxTimeBetweenGroupedMessages, read } = - useChannelContext(); + const { client } = useChatContext(); + const { hideDateSeparators, maxTimeBetweenGroupedMessages } = useChannelContext(); const { deletedMessagesVisibilityType, getMessagesGroupStyles = getGroupStyles } = - useMessagesContext(); - const { messages } = usePaginatedMessageListContext(); - const { threadMessages } = useThreadContext(); - + useMessagesContext(); + const { messages } = usePaginatedMessageListContext(); + const { threadMessages } = useThreadContext(); const messageList = threadList ? threadMessages : messages; - const readList: ChannelState['read'] | undefined = threadList - ? undefined - : read; - - const readData = useLastReadData({ - messages: messageList, - read: readList, - userID: client.userID, - }); - - const processedMessageList = useMemo[]>(() => { - const dateSeparators = getDateSeparators({ - deletedMessagesVisibilityType, - hideDateSeparators, - messages: messageList, - userId: client.userID, - }); - const messageGroupStyles = getMessagesGroupStyles({ - dateSeparators, + const dateSeparators = useMemo( + () => + getDateSeparators({ + deletedMessagesVisibilityType, + hideDateSeparators, + messages: messageList, + userId: client.userID, + }), + [deletedMessagesVisibilityType, hideDateSeparators, messageList, client.userID], + ); + + const dateSeparatorsRef = useRef(dateSeparators); + dateSeparatorsRef.current = dateSeparators; + + const messageGroupStyles = useMemo( + () => + getMessagesGroupStyles({ + dateSeparators: dateSeparatorsRef.current, + hideDateSeparators, + maxTimeBetweenGroupedMessages, + messages: messageList, + noGroupByUser, + userId: client.userID, + }), + [ + dateSeparatorsRef, + getMessagesGroupStyles, hideDateSeparators, maxTimeBetweenGroupedMessages, - messages: messageList, + messageList, noGroupByUser, - userId: client.userID, - }); + client.userID, + ], + ); + + const messageGroupStylesRef = useRef(messageGroupStyles); + messageGroupStylesRef.current = messageGroupStyles; + const processedMessageList = useMemo(() => { const newMessageList = []; for (const message of messageList) { if ( @@ -119,28 +104,17 @@ export const useMessageList = < userId: client.userID, }) ) { - const messageId = message.id; - newMessageList.unshift({ - ...message, - dateSeparator: dateSeparators[messageId] || undefined, - groupStyles: messageGroupStyles[messageId] || ['single'], - readBy: messageId ? readData[messageId] || false : false, - }); + newMessageList.unshift(message); } } return newMessageList; - }, [ - client.userID, - deletedMessagesVisibilityType, - getMessagesGroupStyles, - hideDateSeparators, - maxTimeBetweenGroupedMessages, - messageList, - noGroupByUser, - readData, - ]); + }, [client.userID, deletedMessagesVisibilityType, messageList]); return { + /** Date separators */ + dateSeparatorsRef, + /** Message group styles */ + messageGroupStylesRef, /** Messages enriched with dates/readby/groups and also reversed in order */ processedMessageList, /** Raw messages from the channel state */ diff --git a/package/src/components/MessageList/hooks/useShouldScrollToRecentOnNewOwnMessage.ts b/package/src/components/MessageList/hooks/useShouldScrollToRecentOnNewOwnMessage.ts index 9f7c0f6855..43c722d87f 100644 --- a/package/src/components/MessageList/hooks/useShouldScrollToRecentOnNewOwnMessage.ts +++ b/package/src/components/MessageList/hooks/useShouldScrollToRecentOnNewOwnMessage.ts @@ -1,12 +1,11 @@ import { useEffect, useRef } from 'react'; -import type { FormatMessageResponse } from 'stream-chat'; +import type { LocalMessage } from 'stream-chat'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; - -export function useShouldScrollToRecentOnNewOwnMessage< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->(rawMessageList: FormatMessageResponse[], currentUserId?: string) { +export function useShouldScrollToRecentOnNewOwnMessage( + rawMessageList: LocalMessage[], + currentUserId?: string, +) { const lastFocusedOwnMessageId = useRef(''); const initialFocusRegistered = useRef(false); const messagesRef = useRef(rawMessageList); diff --git a/package/src/components/MessageList/hooks/useTypingString.ts b/package/src/components/MessageList/hooks/useTypingString.ts index d4a4b302e7..1efd15efe9 100644 --- a/package/src/components/MessageList/hooks/useTypingString.ts +++ b/package/src/components/MessageList/hooks/useTypingString.ts @@ -3,16 +3,13 @@ import { useThreadContext } from '../../../contexts/threadContext/ThreadContext' import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { useTypingContext } from '../../../contexts/typingContext/TypingContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; import { filterTypingUsers } from '../utils/filterTypingUsers'; -export const useTypingString = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const { client } = useChatContext(); - const { thread } = useThreadContext(); +export const useTypingString = () => { + const { client } = useChatContext(); + const { thread } = useThreadContext(); const { t } = useTranslationContext(); - const { typing } = useTypingContext(); + const { typing } = useTypingContext(); const filteredTypingUsers = filterTypingUsers({ client, thread, typing }); diff --git a/package/src/components/MessageList/utils/filterTypingUsers.ts b/package/src/components/MessageList/utils/filterTypingUsers.ts index 7cee43ab56..47a50f7e80 100644 --- a/package/src/components/MessageList/utils/filterTypingUsers.ts +++ b/package/src/components/MessageList/utils/filterTypingUsers.ts @@ -1,21 +1,12 @@ import type { ChatContextValue } from '../../../contexts/chatContext/ChatContext'; import type { ThreadContextValue } from '../../../contexts/threadContext/ThreadContext'; import type { TypingContextValue } from '../../../contexts/typingContext/TypingContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; - -type FilterTypingUsersParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'typing'> & - Pick, 'client'> & - Pick, 'thread'>; - -export const filterTypingUsers = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - client, - thread, - typing, -}: FilterTypingUsersParams) => { + +type FilterTypingUsersParams = Pick & + Pick & + Pick; + +export const filterTypingUsers = ({ client, thread, typing }: FilterTypingUsersParams) => { const nonSelfUsers: string[] = []; if (!client || !client.user || !typing) { diff --git a/package/src/components/MessageList/utils/getDateSeparators.ts b/package/src/components/MessageList/utils/getDateSeparators.ts index 3dd8bc7cc8..90f35d5c32 100644 --- a/package/src/components/MessageList/utils/getDateSeparators.ts +++ b/package/src/components/MessageList/utils/getDateSeparators.ts @@ -1,14 +1,9 @@ import type { DeletedMessagesVisibilityType } from '../../../contexts/messagesContext/MessagesContext'; import type { PaginatedMessageListContextValue } from '../../../contexts/paginatedMessageListContext/PaginatedMessageListContext'; import type { ThreadContextValue } from '../../../contexts/threadContext/ThreadContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; -export type GetDateSeparatorsParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - messages: - | PaginatedMessageListContextValue['messages'] - | ThreadContextValue['threadMessages']; +export type GetDateSeparatorsParams = { + messages: PaginatedMessageListContextValue['messages'] | ThreadContextValue['threadMessages']; deletedMessagesVisibilityType?: DeletedMessagesVisibilityType; hideDateSeparators?: boolean; userId?: string; @@ -18,11 +13,7 @@ export type DateSeparators = { [key: string]: Date; }; -export const getDateSeparators = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - params: GetDateSeparatorsParams, -) => { +export const getDateSeparators = (params: GetDateSeparatorsParams) => { const { deletedMessagesVisibilityType, hideDateSeparators, messages, userId } = params; const dateSeparators: DateSeparators = {}; diff --git a/package/src/components/MessageList/utils/getGroupStyles.ts b/package/src/components/MessageList/utils/getGroupStyles.ts index 07af64b9cb..19fbe4cbe1 100644 --- a/package/src/components/MessageList/utils/getGroupStyles.ts +++ b/package/src/components/MessageList/utils/getGroupStyles.ts @@ -1,18 +1,16 @@ +import { LocalMessage } from 'stream-chat'; + import type { DateSeparators } from './getDateSeparators'; import type { PaginatedMessageListContextValue } from '../../../contexts/paginatedMessageListContext/PaginatedMessageListContext'; import type { ThreadContextValue } from '../../../contexts/threadContext/ThreadContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { isEditedMessage } from '../../../utils/utils'; -import type { GroupType, MessageType } from '../hooks/useMessageList'; +import type { GroupType } from '../hooks/useMessageList'; -export type GetGroupStylesParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type GetGroupStylesParams = { dateSeparators: DateSeparators; - messages: - | PaginatedMessageListContextValue['messages'] - | ThreadContextValue['threadMessages']; + messages: PaginatedMessageListContextValue['messages'] | ThreadContextValue['threadMessages']; hideDateSeparators?: boolean; maxTimeBetweenGroupedMessages?: number; noGroupByUser?: boolean; @@ -21,13 +19,11 @@ export type GetGroupStylesParams< export type GroupStyle = '' | 'middle' | 'top' | 'bottom' | 'single'; -const getGroupStyle = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( +const getGroupStyle = ( dateSeparators: DateSeparators, - message: MessageType, - previousMessage: MessageType, - nextMessage: MessageType, + message: LocalMessage, + previousMessage: LocalMessage, + nextMessage: LocalMessage, hideDateSeparators?: boolean, maxTimeBetweenGroupedMessages?: number, ): GroupStyle[] => { @@ -44,7 +40,7 @@ const getGroupStyle = < previousMessage.type === 'error' || userId !== previousMessage?.user?.id || !!isPrevMessageTypeDeleted || - (!hideDateSeparators && dateSeparators[message.id]) || + (!hideDateSeparators && dateSeparators[previousMessage.id]) || isEditedMessage(previousMessage); const isBottomMessage = @@ -101,11 +97,7 @@ const getGroupStyle = < return groupStyles; }; -export const getGroupStyles = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - params: GetGroupStylesParams, -) => { +export const getGroupStyles = (params: GetGroupStylesParams) => { const { dateSeparators, hideDateSeparators, diff --git a/package/src/components/MessageList/utils/getLastReceivedMessage.ts b/package/src/components/MessageList/utils/getLastReceivedMessage.ts index d3f7d76543..2752950258 100644 --- a/package/src/components/MessageList/utils/getLastReceivedMessage.ts +++ b/package/src/components/MessageList/utils/getLastReceivedMessage.ts @@ -1,13 +1,8 @@ -import type { DefaultStreamChatGenerics } from '../../../types/types'; -import { MessageStatusTypes } from '../../../utils/utils'; +import { LocalMessage } from 'stream-chat'; -import type { MessageType } from '../hooks/useMessageList'; +import { MessageStatusTypes } from '../../../utils/utils'; -export const getLastReceivedMessage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - messages: MessageType[], -) => { +export const getLastReceivedMessage = (messages: LocalMessage[]) => { /** * There are no status on dates so they will be skipped */ diff --git a/package/src/components/MessageList/utils/getReadState.ts b/package/src/components/MessageList/utils/getReadState.ts new file mode 100644 index 0000000000..6b7821ca48 --- /dev/null +++ b/package/src/components/MessageList/utils/getReadState.ts @@ -0,0 +1,27 @@ +import { ChannelState, LocalMessage } from 'stream-chat'; + +/** + * Get the number of users who have read the message + * @param message - The message to get the read state for + * @param read - The read state of the channel + * @returns The number of users who have read the message + */ +export const getReadState = (message: LocalMessage, read?: ChannelState['read']) => { + if (!read) { + return 0; + } + + const readState = Object.values(read).reduce((acc, readState) => { + if (!readState.last_read) { + return acc; + } + + if (message.created_at && message.created_at < readState.last_read) { + return acc + 1; + } + + return acc; + }, 0); + + return readState; +}; diff --git a/package/src/components/MessageList/utils/getReadStates.ts b/package/src/components/MessageList/utils/getReadStates.ts deleted file mode 100644 index b581eb18d2..0000000000 --- a/package/src/components/MessageList/utils/getReadStates.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { ChannelState } from 'stream-chat'; - -import type { PaginatedMessageListContextValue } from '../../../contexts/paginatedMessageListContext/PaginatedMessageListContext'; -import type { ThreadContextValue } from '../../../contexts/threadContext/ThreadContext'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; - -export const getReadStates = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - messages: - | PaginatedMessageListContextValue['messages'] - | ThreadContextValue['threadMessages'], - read?: ChannelState['read'], - returnAllReadData?: boolean, -) => { - const readData: Record = {}; - - if (read) { - /** - * Array is in reverse order so newest message is at 0, - * we find the index of the first message that is older - * than the last read and then set last read to that, or - * if there are no newer messages, the first message is - * last read message. - */ - Object.values(read).forEach((readState) => { - if (!readState.last_read) { - return; - } - - let userLastReadMsgId: string | undefined; - - // loop messages sent by current user and add read data for other users in channel - messages.forEach((msg) => { - if (msg.created_at && msg.created_at < readState.last_read) { - userLastReadMsgId = msg.id; - - if (returnAllReadData) { - // if true, save other user's read data for all messages they've read - if (!readData[userLastReadMsgId]) { - readData[userLastReadMsgId] = 0; - } - readData[userLastReadMsgId] = readData[userLastReadMsgId] + 1; - } - } - }); - - // if true, only save read data for other user's last read message - if (userLastReadMsgId && !returnAllReadData) { - if (!readData[userLastReadMsgId]) { - readData[userLastReadMsgId] = 0; - } - - readData[userLastReadMsgId] = readData[userLastReadMsgId] + 1; - } - }); - } - - return readData; -}; diff --git a/package/src/components/MessageMenu/MessageMenu.tsx b/package/src/components/MessageMenu/MessageMenu.tsx index 2c6c5b34e0..f8201ac560 100644 --- a/package/src/components/MessageMenu/MessageMenu.tsx +++ b/package/src/components/MessageMenu/MessageMenu.tsx @@ -13,14 +13,11 @@ import { useMessagesContext, } from '../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import { DefaultStreamChatGenerics } from '../../types/types'; import { BottomSheetModal } from '../UIComponents/BottomSheetModal'; -export type MessageMenuProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial< +export type MessageMenuProps = Partial< Pick< - MessagesContextValue, + MessagesContextValue, | 'MessageActionList' | 'MessageActionListItem' | 'MessageReactionPicker' @@ -29,7 +26,7 @@ export type MessageMenuProps< | 'MessageUserReactionsItem' > > & - Partial, 'message'>> & { + Partial> & { /** * Function to close the message actions bottom sheet * @returns void @@ -59,11 +56,7 @@ export type MessageMenuProps< selectedReaction?: string; }; -export const MessageMenu = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageMenuProps, -) => { +export const MessageMenu = (props: MessageMenuProps) => { const { dismissOverlay, handleReaction, @@ -87,8 +80,8 @@ export const MessageMenu = < MessageUserReactions: contextMessageUserReactions, MessageUserReactionsAvatar: contextMessageUserReactionsAvatar, MessageUserReactionsItem: contextMessageUserReactionsItem, - } = useMessagesContext(); - const { message: contextMessage } = useMessageContext(); + } = useMessagesContext(); + const { message: contextMessage } = useMessageContext(); const MessageActionList = propMessageActionList ?? contextMessageActionList; const MessageActionListItem = propMessageActionListItem ?? contextMessageActionListItem; const MessageReactionPicker = propMessageReactionPicker ?? contextMessageReactionPicker; diff --git a/package/src/components/MessageMenu/MessageReactionPicker.tsx b/package/src/components/MessageMenu/MessageReactionPicker.tsx index 5288a18562..72131b94be 100644 --- a/package/src/components/MessageMenu/MessageReactionPicker.tsx +++ b/package/src/components/MessageMenu/MessageReactionPicker.tsx @@ -12,13 +12,11 @@ import { import { useOwnCapabilitiesContext } from '../../contexts/ownCapabilitiesContext/OwnCapabilitiesContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { NativeHandlers } from '../../native'; -import type { DefaultStreamChatGenerics } from '../../types/types'; + import { ReactionData } from '../../utils/utils'; -export type MessageReactionPickerProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'supportedReactions'> & - Pick, 'handleReaction' | 'dismissOverlay'> & { +export type MessageReactionPickerProps = Pick & + Pick & { /** * An array of reaction types that the current user has reacted with */ @@ -43,11 +41,7 @@ const renderItem = ({ index, item }: { index: number; item: ReactionPickerItemTy /** * MessageReactionPicker - A high level component which implements all the logic required for a message overlay reaction list */ -export const MessageReactionPicker = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: MessageReactionPickerProps, -) => { +export const MessageReactionPicker = (props: MessageReactionPickerProps) => { const { dismissOverlay, handleReaction, diff --git a/package/src/components/MessageMenu/MessageUserReactions.tsx b/package/src/components/MessageMenu/MessageUserReactions.tsx index f2836350ae..8f53b5c6c8 100644 --- a/package/src/components/MessageMenu/MessageUserReactions.tsx +++ b/package/src/components/MessageMenu/MessageUserReactions.tsx @@ -14,18 +14,16 @@ import { } from '../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; -import { DefaultStreamChatGenerics, Reaction } from '../../types/types'; +import { Reaction } from '../../types/types'; import { ReactionData } from '../../utils/utils'; -export type MessageUserReactionsProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial< +export type MessageUserReactionsProps = Partial< Pick< - MessagesContextValue, + MessagesContextValue, 'MessageUserReactionsAvatar' | 'MessageUserReactionsItem' | 'supportedReactions' > > & - Partial, 'message'>> & { + Partial> & { /** * An array of reactions */ diff --git a/package/src/components/MessageMenu/MessageUserReactionsItem.tsx b/package/src/components/MessageMenu/MessageUserReactionsItem.tsx index 7372ab4da9..7dff4e45e0 100644 --- a/package/src/components/MessageMenu/MessageUserReactionsItem.tsx +++ b/package/src/components/MessageMenu/MessageUserReactionsItem.tsx @@ -7,12 +7,13 @@ import { MessagesContextValue } from '../../contexts/messagesContext/MessagesCon import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { Unknown } from '../../icons'; -import type { DefaultStreamChatGenerics, Reaction } from '../../types/types'; +import type { Reaction } from '../../types/types'; import { ReactionData } from '../../utils/utils'; -export type MessageUserReactionsItemProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'MessageUserReactionsAvatar'> & { +export type MessageUserReactionsItemProps = Pick< + MessagesContextValue, + 'MessageUserReactionsAvatar' +> & { /** * The reaction object */ @@ -23,13 +24,11 @@ export type MessageUserReactionsItemProps< supportedReactions: ReactionData[]; }; -export const MessageUserReactionsItem = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const MessageUserReactionsItem = ({ MessageUserReactionsAvatar, reaction, supportedReactions, -}: MessageUserReactionsItemProps) => { +}: MessageUserReactionsItemProps) => { const { id, name, type } = reaction; const { theme: { diff --git a/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx b/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx index 50db95dd39..30d6f243dc 100644 --- a/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx +++ b/package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx @@ -4,7 +4,7 @@ import { Text } from 'react-native'; import { fireEvent, render } from '@testing-library/react-native'; -import { ReactionResponse } from 'stream-chat'; +import { LocalMessage, ReactionResponse } from 'stream-chat'; import { MessagesContextValue, @@ -17,7 +17,6 @@ import { TranslationProvider, } from '../../../contexts/translationContext/TranslationContext'; import { generateMessage } from '../../../mock-builders/generator/message'; -import { MessageType } from '../../MessageList/hooks/useMessageList'; import * as useFetchReactionsModule from '../hooks/useFetchReactions'; import { MessageUserReactions } from '../MessageUserReactions'; import { MessageUserReactionsItemProps } from '../MessageUserReactionsItem'; @@ -35,7 +34,7 @@ const defaultProps = { message: { ...generateMessage(), reaction_groups: { like: { count: 1, sum_scores: 1 }, love: { count: 1, sum_scores: 1 } }, - } as unknown as MessageType, + } as unknown as LocalMessage, supportedReactions: mockSupportedReactions, }; diff --git a/package/src/components/MessageMenu/hooks/useFetchReactions.ts b/package/src/components/MessageMenu/hooks/useFetchReactions.ts index 689c00b3a6..c91411c637 100644 --- a/package/src/components/MessageMenu/hooks/useFetchReactions.ts +++ b/package/src/components/MessageMenu/hooks/useFetchReactions.ts @@ -1,30 +1,24 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { ReactionResponse, ReactionSort } from 'stream-chat'; +import { LocalMessage, ReactionResponse, ReactionSort } from 'stream-chat'; -import { MessageType } from '../../../components/MessageList/hooks/useMessageList'; import { useChatContext } from '../../../contexts/chatContext/ChatContext'; import { getReactionsForFilterSort } from '../../../store/apis/getReactionsforFilterSort'; -import { DefaultStreamChatGenerics } from '../../../types/types'; -export type UseFetchReactionParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type UseFetchReactionParams = { limit?: number; - message?: MessageType; + message?: LocalMessage; reactionType?: string; - sort?: ReactionSort; + sort?: ReactionSort; }; -export const useFetchReactions = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useFetchReactions = ({ limit = 25, message, reactionType, sort, }: UseFetchReactionParams) => { - const [reactions, setReactions] = useState[]>([]); + const [reactions, setReactions] = useState([]); const [loading, setLoading] = useState(true); const [next, setNext] = useState(undefined); const messageId = message?.id; diff --git a/package/src/components/Poll/Poll.tsx b/package/src/components/Poll/Poll.tsx index dc27cb895c..dc64810994 100644 --- a/package/src/components/Poll/Poll.tsx +++ b/package/src/components/Poll/Poll.tsx @@ -14,12 +14,9 @@ import { useTheme, useTranslationContext, } from '../../contexts'; -import type { DefaultStreamChatGenerics } from '../../types/types'; -export type PollProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'poll' | 'message'> & - Pick, 'PollContent'>; +export type PollProps = Pick & + Pick; export type PollContentProps = { PollButtons?: React.ComponentType; @@ -90,13 +87,7 @@ export const PollContent = ({ ); }; -export const Poll = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - message, - poll, - PollContent: PollContentOverride, -}: PollProps) => ( +export const Poll = ({ message, poll, PollContent: PollContentOverride }: PollProps) => ( = { - onPress?: ({ - message, - poll, - }: { - message: MessageType; - poll: Poll; - }) => void; +export type PollButtonProps = { + onPress?: ({ message, poll }: { message: LocalMessage; poll: Poll }) => void; }; export type PollVoteButtonProps = { diff --git a/package/src/components/Poll/components/PollAnswersList.tsx b/package/src/components/Poll/components/PollAnswersList.tsx index 541ec27c27..a4949602a9 100644 --- a/package/src/components/Poll/components/PollAnswersList.tsx +++ b/package/src/components/Poll/components/PollAnswersList.tsx @@ -13,7 +13,6 @@ import { useTheme, useTranslationContext, } from '../../../contexts'; -import { DefaultStreamChatGenerics } from '../../../types/types'; import { getDateString } from '../../../utils/i18n/getDateString'; import { Avatar } from '../../Avatar/Avatar'; import { usePollAnswersPagination } from '../hooks/usePollAnswersPagination'; @@ -73,10 +72,8 @@ export const AnswerListAddCommentButton = (props: PollButtonProps) => { ); }; -export type PollAnswersListProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = PollContextValue & { - additionalFlatListProps?: Partial>>; +export type PollAnswersListProps = PollContextValue & { + additionalFlatListProps?: Partial>; PollAnswersListContent?: React.ComponentType; }; diff --git a/package/src/components/Poll/components/PollResults/PollOptionFullResults.tsx b/package/src/components/Poll/components/PollResults/PollOptionFullResults.tsx index 362fce31e4..03a8f4ec82 100644 --- a/package/src/components/Poll/components/PollResults/PollOptionFullResults.tsx +++ b/package/src/components/Poll/components/PollResults/PollOptionFullResults.tsx @@ -11,16 +11,14 @@ import { useTheme, useTranslationContext, } from '../../../../contexts'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; + import { usePollOptionVotesPagination } from '../../hooks/usePollOptionVotesPagination'; import { usePollState } from '../../hooks/usePollState'; -export type PollOptionFullResultsProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = PollContextValue & { - option: PollOption; - additionalFlatListProps?: Partial>>; - PollOptionFullResultsContent?: React.ComponentType<{ option: PollOption }>; +export type PollOptionFullResultsProps = PollContextValue & { + option: PollOption; + additionalFlatListProps?: Partial>; + PollOptionFullResultsContent?: React.ComponentType<{ option: PollOption }>; }; export const PollOptionFullResultsItem = ({ item }: { item: PollVoteClass }) => ( diff --git a/package/src/components/Poll/components/PollResults/PollResultItem.tsx b/package/src/components/Poll/components/PollResults/PollResultItem.tsx index 38fdcb1c3d..7dad29fcca 100644 --- a/package/src/components/Poll/components/PollResults/PollResultItem.tsx +++ b/package/src/components/Poll/components/PollResults/PollResultItem.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useState } from 'react'; import { Modal, SafeAreaView, StyleSheet, Text, View } from 'react-native'; -import { Poll, PollOption, PollVote as PollVoteClass } from 'stream-chat'; +import { LocalMessage, Poll, PollOption, PollVote as PollVoteClass } from 'stream-chat'; import { PollOptionFullResults } from './PollOptionFullResults'; import { PollVote } from './PollVote'; @@ -12,24 +12,21 @@ import { useTheme, useTranslationContext, } from '../../../../contexts'; -import type { DefaultStreamChatGenerics } from '../../../../types/types'; -import { MessageType } from '../../../MessageList/hooks/useMessageList'; + import { usePollState } from '../../hooks/usePollState'; import { GenericPollButton } from '../Button'; import { PollModalHeader } from '../PollModalHeader'; -export type ShowAllVotesButtonProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - option: PollOption; +export type ShowAllVotesButtonProps = { + option: PollOption; onPress?: ({ message, option, poll, }: { - message: MessageType; - option: PollOption; - poll: Poll; + message: LocalMessage; + option: PollOption; + poll: Poll; }) => void; }; @@ -79,10 +76,8 @@ export const ShowAllVotesButton = (props: ShowAllVotesButtonProps) => { ); }; -export type PollResultItemProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - option: PollOption; +export type PollResultItemProps = { + option: PollOption; }; const PollResultsVoteItem = (vote: PollVoteClass) => ( diff --git a/package/src/components/Poll/hooks/usePollState.ts b/package/src/components/Poll/hooks/usePollState.ts index 0c64c67c38..9100724b5e 100644 --- a/package/src/components/Poll/hooks/usePollState.ts +++ b/package/src/components/Poll/hooks/usePollState.ts @@ -16,8 +16,6 @@ import { usePollStateStore } from './usePollStateStore'; import { usePollContext } from '../../../contexts'; -import { DefaultStreamChatGenerics } from '../../../types/types'; - export type UsePollStateSelectorReturnType = { allowAnswers: boolean | undefined; allowUserSuggestedOptions: boolean | undefined; @@ -36,19 +34,13 @@ export type UsePollStateSelectorReturnType = { votingVisibility: VotingVisibility | undefined; }; -export type UsePollStateReturnType< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = UsePollStateSelectorReturnType & { - addComment: ( - answerText: string, - ) => Promise>; +export type UsePollStateReturnType = UsePollStateSelectorReturnType & { + addComment: (answerText: string) => Promise; addOption: (optionText: string) => Promise; - endVote: () => Promise>; + endVote: () => Promise; }; -const selector = ( - nextValue: PollState, -): UsePollStateSelectorReturnType => ({ +const selector = (nextValue: PollState): UsePollStateSelectorReturnType => ({ allowAnswers: nextValue.allow_answers, allowUserSuggestedOptions: nextValue.allow_user_suggested_options, answersCount: nextValue.answers_count, diff --git a/package/src/components/Reply/Reply.tsx b/package/src/components/Reply/Reply.tsx index 6f3d9f069d..35ff5acceb 100644 --- a/package/src/components/Reply/Reply.tsx +++ b/package/src/components/Reply/Reply.tsx @@ -25,7 +25,7 @@ import { useTranslationContext, } from '../../contexts/translationContext/TranslationContext'; import { useStateStore } from '../../hooks'; -import { DefaultStreamChatGenerics, FileTypes } from '../../types/types'; +import { FileTypes } from '../../types/types'; import { getResizedImageUrl } from '../../utils/getResizedImageUrl'; import { getTrimmedAttachmentTitle } from '../../utils/getTrimmedAttachmentTitle'; import { hasOnlyEmojis } from '../../utils/utils'; @@ -34,7 +34,6 @@ 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: { @@ -80,16 +79,12 @@ export type ReplySelectorReturnType = { name?: string; }; -const selector = ( - nextValue: PollState, -): ReplySelectorReturnType => ({ +const selector = (nextValue: PollState): ReplySelectorReturnType => ({ name: nextValue.name, }); -type ReplyPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'quotedMessage'> & - Pick, 'FileAttachmentIcon' | 'MessageAvatar'> & +type ReplyPropsWithContext = Pick & + Pick & Pick & { attachmentSize?: number; styles?: Partial<{ @@ -101,11 +96,7 @@ type ReplyPropsWithContext< }>; }; -const getMessageType = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - lastAttachment: Attachment, -) => { +const getMessageType = (lastAttachment: Attachment) => { let messageType; const isLastAttachmentFile = lastAttachment.type === FileTypes.File; @@ -149,11 +140,7 @@ const getMessageType = < return messageType; }; -const ReplyWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ReplyPropsWithContext, -) => { +const ReplyWithContext = (props: ReplyPropsWithContext) => { const { client } = useChatContext(); const { attachmentSize = 40, @@ -189,7 +176,7 @@ const ReplyWithContext = < }, } = useTheme(); - const poll = client.polls.fromState((quotedMessage as MessageType)?.poll_id ?? ''); + const poll = client.polls.fromState(quotedMessage?.poll_id ?? ''); const { name: pollName }: ReplySelectorReturnType = useStateStore(poll?.state, selector) ?? {}; const messageText = quotedMessage ? quotedMessage.text : ''; @@ -205,7 +192,7 @@ const ReplyWithContext = < return null; } - const lastAttachment = quotedMessage.attachments?.slice(-1)[0] as Attachment; + const lastAttachment = quotedMessage.attachments?.slice(-1)[0] as Attachment; const messageType = lastAttachment && getMessageType(lastAttachment); const trimmedLastAttachmentTitle = getTrimmedAttachmentTitle(lastAttachment?.title); @@ -281,7 +268,7 @@ const ReplyWithContext = < /> ) : null} - + () => { - const contextValue = useContext( - MessageInputContext, - ) as unknown as MessageInputContextValue; +const useMessageInputContextIfAvailable = () => { + const contextValue = useContext(MessageInputContext) as unknown as MessageInputContextValue; return contextValue; }; -const areEqual = ( - prevProps: ReplyPropsWithContext, - nextProps: ReplyPropsWithContext, -) => { +const areEqual = (prevProps: ReplyPropsWithContext, nextProps: ReplyPropsWithContext) => { const { quotedMessage: prevQuotedMessage } = prevProps; const { quotedMessage: nextQuotedMessage } = nextProps; @@ -396,28 +376,22 @@ const areEqual = = Partial>; +export type ReplyProps = Partial; /** * UI Component for reply */ -export const Reply = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ReplyProps, -) => { - const { message } = useMessageContext(); +export const Reply = (props: ReplyProps) => { + const { message } = useMessageContext(); const { FileAttachmentIcon = FileIconDefault, MessageAvatar = MessageAvatarDefault } = - useMessagesContext(); + useMessagesContext(); - const { editing, quotedMessage } = useMessageInputContextIfAvailable(); + const { editing, quotedMessage } = useMessageInputContextIfAvailable(); const quotedEditingMessage = ( typeof editing !== 'boolean' ? editing?.quoted_message || false : false - ) as MessageInputContextValue['quotedMessage']; + ) as MessageInputContextValue['quotedMessage']; const { t } = useTranslationContext(); @@ -427,7 +401,7 @@ export const Reply = < FileAttachmentIcon, MessageAvatar, quotedMessage: message - ? (message.quoted_message as MessageInputContextValue['quotedMessage']) + ? (message.quoted_message as MessageInputContextValue['quotedMessage']) : quotedMessage || quotedEditingMessage, t, }} diff --git a/package/src/components/Thread/Thread.tsx b/package/src/components/Thread/Thread.tsx index 465fc67158..791b2d6000 100644 --- a/package/src/components/Thread/Thread.tsx +++ b/package/src/components/Thread/Thread.tsx @@ -10,19 +10,16 @@ import { } from '../../contexts/messagesContext/MessagesContext'; import { ThreadContextValue, useThreadContext } from '../../contexts/threadContext/ThreadContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { MessageInput as DefaultMessageInput, MessageInputProps, } from '../MessageInput/MessageInput'; import type { MessageListProps } from '../MessageList/MessageList'; -type ThreadPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'client'> & - Pick, 'MessageList'> & +type ThreadPropsWithContext = Pick & + Pick & Pick< - ThreadContextValue, + ThreadContextValue, | 'closeThread' | 'loadMoreThread' | 'parentMessagePreventPress' @@ -34,12 +31,12 @@ type ThreadPropsWithContext< * Additional props for underlying MessageInput component. * Available props - https://getstream.io/chat/docs/sdk/reactnative/ui-components/message-input/#props * */ - additionalMessageInputProps?: Partial>; + additionalMessageInputProps?: Partial; /** * Additional props for underlying MessageList component. * Available props - https://getstream.io/chat/docs/sdk/reactnative/ui-components/message-list/#props * */ - additionalMessageListProps?: Partial>; + additionalMessageListProps?: Partial; /** Make input focus on mounting thread */ autoFocus?: boolean; /** Closes thread on dismount, defaults to true */ @@ -50,18 +47,14 @@ type ThreadPropsWithContext< * **Customized MessageInput component to used within Thread instead of default MessageInput * **Available from [MessageInput](https://getstream.io/chat/docs/sdk/reactnative/ui-components/message-input)** */ - MessageInput?: React.ComponentType>; + MessageInput?: React.ComponentType; /** * Call custom function on closing thread if handling thread state elsewhere */ onThreadDismount?: () => void; }; -const ThreadWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ThreadPropsWithContext, -) => { +const ThreadWithContext = (props: ThreadPropsWithContext) => { const { additionalMessageInputProps, additionalMessageListProps, @@ -120,7 +113,7 @@ const ThreadWithContext = < threadList {...additionalMessageListProps} /> - + = Partial>; +export type ThreadProps = Partial; /** * Thread - The Thread renders a parent message with a list of replies. Use the standard message list of the main channel's messages. @@ -142,16 +133,11 @@ export type ThreadProps< * - additionalMessageListProps * - additionalMessageInputProps */ -export const Thread = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ThreadProps, -) => { - const { client } = useChatContext(); - const { threadList } = useChannelContext(); - const { MessageList } = useMessagesContext(); - const { closeThread, loadMoreThread, reloadThread, thread, threadInstance } = - useThreadContext(); +export const Thread = (props: ThreadProps) => { + const { client } = useChatContext(); + const { threadList } = useChannelContext(); + const { MessageList } = useMessagesContext(); + const { closeThread, loadMoreThread, reloadThread, thread, threadInstance } = useThreadContext(); if (thread?.id && !threadList) { throw new Error( @@ -160,7 +146,7 @@ export const Thread = < } return ( - + regular

", "attachments": [], "created_at": 2020-05-05T14:50:00.000Z, - "dateSeparator": undefined, "deleted_at": null, - "groupStyles": [ - "single", - ], + "error": null, "html": "

regular

", "id": "38ef6f7c-3090-5759-a37f-ab0053aadb96", "message_text_updated_at": "2020-05-05T14:50:00.000Z", "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04", "pinned_at": null, + "quoted_message": null, "reaction_groups": null, - "readBy": false, "status": "received", "text": "Message6", "type": "regular", @@ -80,21 +76,17 @@ exports[`Thread should match thread snapshot 1`] = ` }, }, { - "__html": "

regular

", "attachments": [], "created_at": 2020-05-05T14:50:00.000Z, - "dateSeparator": undefined, "deleted_at": null, - "groupStyles": [ - "single", - ], + "error": null, "html": "

regular

", "id": "516efa25-5d29-5c9a-ad2d-4cc183e785bd", "message_text_updated_at": "2020-05-05T14:50:00.000Z", "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04", "pinned_at": null, + "quoted_message": null, "reaction_groups": null, - "readBy": false, "status": "received", "text": "Message5", "type": "regular", @@ -111,21 +103,17 @@ exports[`Thread should match thread snapshot 1`] = ` }, }, { - "__html": "

regular

", "attachments": [], "created_at": 2020-05-05T14:50:00.000Z, - "dateSeparator": 2020-05-05T14:50:00.000Z, "deleted_at": null, - "groupStyles": [ - "single", - ], + "error": null, "html": "

regular

", "id": "82a83b16-b611-527c-b3ac-765ef6220490", "message_text_updated_at": "2020-05-05T14:50:00.000Z", "parent_id": "b4612a73-fa2b-5787-bf71-1adc8f291a04", "pinned_at": null, + "quoted_message": null, "reaction_groups": null, - "readBy": false, "status": "received", "text": "Message4", "type": "regular", diff --git a/package/src/components/Thread/components/ThreadFooterComponent.tsx b/package/src/components/Thread/components/ThreadFooterComponent.tsx index fe60410e60..7b1df5c268 100644 --- a/package/src/components/Thread/components/ThreadFooterComponent.tsx +++ b/package/src/components/Thread/components/ThreadFooterComponent.tsx @@ -16,7 +16,6 @@ import { import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { useStateStore } from '../../../hooks'; import { useViewport } from '../../../hooks/useViewport'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; const styles = StyleSheet.create({ absolute: { position: 'absolute' }, @@ -42,13 +41,8 @@ const styles = StyleSheet.create({ }, }); -type ThreadFooterComponentPropsWithContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'Message'> & - Pick< - ThreadContextValue, - 'parentMessagePreventPress' | 'thread' | 'threadInstance' - >; +type ThreadFooterComponentPropsWithContext = Pick & + Pick; export const InlineLoadingMoreThreadIndicator = () => { const { threadLoadingMore } = useThreadContext(); @@ -74,11 +68,7 @@ const selector = (nextValue: ThreadState) => replyCount: nextValue.replyCount, }) as const; -const ThreadFooterComponentWithContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ThreadFooterComponentPropsWithContext, -) => { +const ThreadFooterComponentWithContext = (props: ThreadFooterComponentPropsWithContext) => { const { Message, parentMessagePreventPress, thread, threadInstance } = props; const { t } = useTranslationContext(); const { vw } = useViewport(); @@ -111,6 +101,7 @@ const ThreadFooterComponentWithContext = < groupStyles={['single']} message={thread} preventPress={parentMessagePreventPress} + readBy={0} threadList />
@@ -152,9 +143,9 @@ const ThreadFooterComponentWithContext = < ); }; -const areEqual = ( - prevProps: ThreadFooterComponentPropsWithContext, - nextProps: ThreadFooterComponentPropsWithContext, +const areEqual = ( + prevProps: ThreadFooterComponentPropsWithContext, + nextProps: ThreadFooterComponentPropsWithContext, ) => { const { parentMessagePreventPress: prevParentMessagePreventPress, @@ -206,19 +197,13 @@ const MemoizedThreadFooter = React.memo( areEqual, ) as typeof ThreadFooterComponentWithContext; -export type ThreadFooterComponentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial, 'Message'>> & - Partial, 'parentMessagePreventPress' | 'thread'>>; +export type ThreadFooterComponentProps = Partial> & + Partial>; -export const ThreadFooterComponent = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: ThreadFooterComponentProps, -) => { - const { Message } = useMessagesContext(); +export const ThreadFooterComponent = (props: ThreadFooterComponentProps) => { + const { Message } = useMessagesContext(); const { parentMessagePreventPress, thread, threadInstance, threadLoadingMore } = - useThreadContext(); + useThreadContext(); return ( threads: nextValue.threads, }) as const; -export type ThreadListProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick< - ThreadsContextValue, +export type ThreadListProps = Pick< + ThreadsContextValue, | 'additionalFlatListProps' | 'isFocused' | 'onThreadSelect' diff --git a/package/src/components/ThreadList/ThreadListItem.tsx b/package/src/components/ThreadList/ThreadListItem.tsx index 72f3e3f850..e2cce5b64d 100644 --- a/package/src/components/ThreadList/ThreadListItem.tsx +++ b/package/src/components/ThreadList/ThreadListItem.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useMemo } from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -import { Thread, ThreadState } from 'stream-chat'; +import { LocalMessage, Thread, ThreadState } from 'stream-chat'; import { TranslationContextValue, @@ -19,7 +19,6 @@ import { MessageBubble } from '../../icons'; import { getDateString } from '../../utils/i18n/getDateString'; import { Avatar } from '../Avatar/Avatar'; import { useChannelPreviewDisplayName } from '../ChannelPreview/hooks/useChannelPreviewDisplayName'; -import { MessageType } from '../MessageList/hooks/useMessageList'; export type ThreadListItemProps = { thread: Thread; @@ -76,7 +75,7 @@ const getTitleFromMessage = ({ }: { t: TranslationContextValue['t']; currentUserId?: string; - message?: MessageType | undefined; + message?: LocalMessage; }) => { const attachment = message?.attachments?.at(0); @@ -132,7 +131,10 @@ export const ThreadListItemComponent = () => { { if (onThreadSelect) { - onThreadSelect({ thread: parentMessage as MessageType, threadInstance: thread }, channel); + onThreadSelect( + { thread: parentMessage as LocalMessage, threadInstance: thread }, + channel, + ); } }} style={[styles.touchableWrapper, threadListItem.touchableWrapper]} diff --git a/package/src/components/index.ts b/package/src/components/index.ts index d19c9ced21..f0740576cf 100644 --- a/package/src/components/index.ts +++ b/package/src/components/index.ts @@ -45,16 +45,7 @@ export * from './ChannelList/ChannelListHeaderErrorIndicator'; export * from './ChannelList/ChannelListHeaderNetworkDownIndicator'; export * from './ChannelList/ChannelListLoadingIndicator'; export * from './ChannelList/ChannelListMessenger'; -export * from './ChannelList/hooks/listeners/useAddedToChannelNotification'; -export * from './ChannelList/hooks/listeners/useChannelDeleted'; -export * from './ChannelList/hooks/listeners/useChannelHidden'; -export * from './ChannelList/hooks/listeners/useChannelMemberUpdated'; -export * from './ChannelList/hooks/listeners/useChannelTruncated'; export * from './ChannelList/hooks/listeners/useChannelUpdated'; -export * from './ChannelList/hooks/listeners/useNewMessage'; -export * from './ChannelList/hooks/listeners/useNewMessageNotification'; -export * from './ChannelList/hooks/listeners/useRemovedFromChannelNotification'; -export * from './ChannelList/hooks/listeners/useUserPresence'; export * from './ChannelList/hooks/useCreateChannelsContext'; export * from './ChannelList/hooks/usePaginatedChannels'; export * from './ChannelList/hooks/useChannelMembershipState'; @@ -158,7 +149,7 @@ export * from './MessageList/TypingIndicatorContainer'; export * from './MessageList/utils/getDateSeparators'; export * from './MessageList/utils/getGroupStyles'; export * from './MessageList/utils/getLastReceivedMessage'; -export * from './MessageList/utils/getReadStates'; +export * from './MessageList/utils/getReadState'; export * from './MessageMenu/MessageActionList'; export * from './MessageMenu/MessageActionListItem'; diff --git a/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx b/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx index a8b34e29c1..189d6a80c7 100644 --- a/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx +++ b/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx @@ -2,7 +2,8 @@ import React, { PropsWithChildren, useContext, useEffect, useState } from 'react import { BottomSheetHandleProps } from '@gorhom/bottom-sheet'; -import type { Asset, File } from '../../types/types'; +import type { File } from '../../types/types'; + import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; @@ -82,11 +83,11 @@ export type AttachmentPickerContextValue = { maxNumberOfFiles: number; openPicker: () => void; selectedFiles: File[]; - selectedImages: Asset[]; + selectedImages: File[]; setBottomInset: React.Dispatch>; setMaxNumberOfFiles: React.Dispatch>; setSelectedFiles: React.Dispatch>; - setSelectedImages: React.Dispatch>; + setSelectedImages: React.Dispatch>; setSelectedPicker: React.Dispatch>; setTopInset: React.Dispatch>; topInset: number; @@ -124,7 +125,7 @@ export const AttachmentPickerProvider = ({ const [bottomInset, setBottomInset] = useState(bottomInsetValue ?? 0); const [maxNumberOfFiles, setMaxNumberOfFiles] = useState(10); - const [selectedImages, setSelectedImages] = useState([]); + const [selectedImages, setSelectedImages] = useState([]); const [selectedFiles, setSelectedFiles] = useState([]); const [selectedPicker, setSelectedPicker] = useState<'images'>(); const [topInset, setTopInset] = useState(topInsetValue ?? 0); diff --git a/package/src/contexts/channelContext/ChannelContext.tsx b/package/src/contexts/channelContext/ChannelContext.tsx index eea9c855dd..c7a7f59894 100644 --- a/package/src/contexts/channelContext/ChannelContext.tsx +++ b/package/src/contexts/channelContext/ChannelContext.tsx @@ -6,14 +6,12 @@ import { MarkReadFunctionOptions } from '../../components/Channel/Channel'; import type { EmptyStateProps } from '../../components/Indicators/EmptyStateIndicator'; import type { LoadingProps } from '../../components/Indicators/LoadingIndicator'; import { StickyHeaderProps } from '../../components/MessageList/StickyHeader'; -import type { ChannelUnreadState, DefaultStreamChatGenerics } from '../../types/types'; +import type { ChannelUnreadState } from '../../types/types'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; -export type ChannelContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type ChannelContextValue = { /** * Instance of channel object from stream-chat package. * @@ -36,7 +34,7 @@ export type ChannelContextValue< * * @overrideType Channel */ - channel: Channel; + channel: Channel; /** * Custom UI component to display empty state when channel has no messages. * @@ -87,11 +85,9 @@ export type ChannelContextValue< limit, setTargetedMessage, }: { - channelUnreadState?: ChannelUnreadState; + channelUnreadState?: ChannelUnreadState; limit?: number; - setChannelUnreadState?: React.Dispatch< - React.SetStateAction | undefined> - >; + setChannelUnreadState?: React.Dispatch>; setTargetedMessage?: (messageId: string) => void; }) => Promise; @@ -123,17 +119,15 @@ export type ChannelContextValue< * } * ``` */ - members: ChannelState['members']; + members: ChannelState['members']; /** * Custom network down indicator to override the Stream default */ NetworkDownIndicator: React.ComponentType; - read: ChannelState['read']; + read: ChannelState['read']; reloadChannel: () => Promise; scrollToFirstUnreadThreshold: number; - setChannelUnreadState: React.Dispatch< - React.SetStateAction | undefined> - >; + setChannelUnreadState: React.Dispatch>; setLastRead: React.Dispatch>; setTargetedMessage: (messageId?: string) => void; /** @@ -141,7 +135,7 @@ export type ChannelContextValue< * Its a map of filename and AbortController */ uploadAbortControllerRef: React.MutableRefObject>; - channelUnreadState?: ChannelUnreadState; + channelUnreadState?: ChannelUnreadState; disabled?: boolean; enableMessageGroupingByUser?: boolean; /** @@ -170,7 +164,7 @@ export type ChannelContextValue< */ targetedMessage?: string; threadList?: boolean; - watcherCount?: ChannelState['watcher_count']; + watcherCount?: ChannelState['watcher_count']; /** * * ```json @@ -194,32 +188,26 @@ export type ChannelContextValue< * } * ``` */ - watchers?: ChannelState['watchers']; + watchers?: ChannelState['watchers']; }; export const ChannelContext = React.createContext( DEFAULT_BASE_CONTEXT_VALUE as ChannelContextValue, ); -export const ChannelProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const ChannelProvider = ({ children, value, }: PropsWithChildren<{ - value: ChannelContextValue; + value: ChannelContextValue; }>) => ( {children} ); -export const useChannelContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const contextValue = useContext( - ChannelContext, - ) as unknown as ChannelContextValue; +export const useChannelContext = () => { + const contextValue = useContext(ChannelContext) as unknown as ChannelContextValue; if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( diff --git a/package/src/contexts/channelsContext/ChannelsContext.tsx b/package/src/contexts/channelsContext/ChannelsContext.tsx index 0202c58354..64bbc4f500 100644 --- a/package/src/contexts/channelsContext/ChannelsContext.tsx +++ b/package/src/contexts/channelsContext/ChannelsContext.tsx @@ -16,14 +16,12 @@ import type { ChannelPreviewUnreadCountProps } from '../../components/ChannelPre import type { EmptyStateProps } from '../../components/Indicators/EmptyStateIndicator'; import type { LoadingErrorProps } from '../../components/Indicators/LoadingErrorIndicator'; import type { LoadingProps } from '../../components/Indicators/LoadingIndicator'; -import type { DefaultStreamChatGenerics } from '../../types/types'; + import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; -export type ChannelsContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type ChannelsContextValue = { /** * Besides the existing default behavior of the ChannelListMessenger component, you can attach * additional props to the underlying React Native FlatList. @@ -41,7 +39,7 @@ export type ChannelsContextValue< * * **Note:** Don't use `additionalFlatListProps` to access the FlatList ref, use `setFlatListRef` */ - additionalFlatListProps: Partial>>; + additionalFlatListProps: Partial>; /** * A control prop used to determine whether the first query of the channel list has succeeded. */ @@ -49,7 +47,7 @@ export type ChannelsContextValue< /** * Channels can be either an array of channels or a promise which resolves to an array of channels */ - channels: Channel[] | null; + channels: Channel[] | null; /** * Custom indicator to use when channel list is empty * @@ -123,7 +121,7 @@ export type ChannelsContextValue< * * Default: [ChannelPreviewMessenger](https://getstream.io/chat/docs/sdk/reactnative/ui-components/channel-preview-messenger/) */ - Preview: React.ComponentType>; + Preview: React.ComponentType; /** * Triggered when the channel list is refreshing, displays a loading spinner at the top of the list */ @@ -142,7 +140,7 @@ export type ChannelsContextValue< // * // * @param channel A channel object // */ - // setActiveChannel?: (channel: Channel) => void; + // setActiveChannel?: (channel: Channel) => void; /** * Function to gain access to the inner FlatList ref * @@ -155,7 +153,7 @@ export type ChannelsContextValue< * }} * ``` */ - setFlatListRef: (ref: FlatList> | null) => void; + setFlatListRef: (ref: FlatList | null) => void; /** * Custom UI component to display loading channel skeletons * @@ -172,19 +170,19 @@ export type ChannelsContextValue< * * @param channel A channel object */ - onSelect?: (channel: Channel) => void; + onSelect?: (channel: Channel) => void; /** * Custom UI component to render preview avatar. * * **Default** [ChannelAvatar](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelAvatar.tsx) */ - PreviewAvatar?: React.ComponentType>; + PreviewAvatar?: React.ComponentType; /** * Custom UI component to render preview of latest message on channel. * * **Default** [ChannelPreviewMessage](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx) */ - PreviewMessage?: React.ComponentType>; + PreviewMessage?: React.ComponentType; /** * Custom UI component to render muted status. * @@ -196,44 +194,38 @@ export type ChannelsContextValue< * * **Default** [ChannelPreviewStatus](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelPreviewStatus.tsx) */ - PreviewStatus?: React.ComponentType>; + PreviewStatus?: React.ComponentType; /** * Custom UI component to render preview avatar. * * **Default** [ChannelPreviewTitle](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelPreviewTitle.tsx) */ - PreviewTitle?: React.ComponentType>; + PreviewTitle?: React.ComponentType; /** * Custom UI component to render preview avatar. * * **Default** [ChannelPreviewUnreadCount](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/ChannelPreview/ChannelPreviewUnreadCount.tsx) */ - PreviewUnreadCount?: React.ComponentType>; + PreviewUnreadCount?: React.ComponentType; }; export const ChannelsContext = React.createContext( DEFAULT_BASE_CONTEXT_VALUE as ChannelsContextValue, ); -export const ChannelsProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const ChannelsProvider = ({ children, value, }: PropsWithChildren<{ - value: ChannelsContextValue; + value: ChannelsContextValue; }>) => ( {children} ); -export const useChannelsContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const contextValue = useContext( - ChannelsContext, - ) as unknown as ChannelsContextValue; +export const useChannelsContext = () => { + const contextValue = useContext(ChannelsContext) as unknown as ChannelsContextValue; if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( diff --git a/package/src/contexts/channelsStateContext/ChannelsStateContext.tsx b/package/src/contexts/channelsStateContext/ChannelsStateContext.tsx index 4d2ed02692..ac9d493f10 100644 --- a/package/src/contexts/channelsStateContext/ChannelsStateContext.tsx +++ b/package/src/contexts/channelsStateContext/ChannelsStateContext.tsx @@ -8,7 +8,6 @@ import React, { useRef, } from 'react'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { ActiveChannelsProvider } from '../activeChannelsRefContext/ActiveChannelsRefContext'; import type { ThreadContextValue } from '../threadContext/ThreadContext'; @@ -16,50 +15,35 @@ import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; -export type ChannelState< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - threadMessages: ThreadContextValue['threadMessages']; +export type ChannelState = { + threadMessages: ThreadContextValue['threadMessages']; }; -type ChannelsState< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - [cid: string]: ChannelState; +type ChannelsState = { + [cid: string]: ChannelState; }; export type Keys = keyof ChannelState; -export type Payload< - Key extends Keys = Keys, - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type Payload = { cid: string; key: Key; - value: ChannelState[Key]; + value: ChannelState[Key]; }; -type SetStateAction< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - payload: Payload; +type SetStateAction = { + payload: Payload; type: 'SET_STATE'; }; -type Action = - SetStateAction; +type Action = SetStateAction; -export type ChannelsStateContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - setState: (value: Payload) => void; - state: ChannelsState; +export type ChannelsStateContextValue = { + setState: (value: Payload) => void; + state: ChannelsState; }; -type Reducer = ( - state: ChannelsState, - action: Action, -) => ChannelsState; +type Reducer = (state: ChannelsState, action: Action) => ChannelsState; function reducer(state: ChannelsState, action: Action) { switch (action.type) { @@ -81,16 +65,10 @@ const ChannelsStateContext = React.createContext( DEFAULT_BASE_CONTEXT_VALUE as ChannelsStateContextValue, ); -export const ChannelsStateProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - children, -}: { - children: ReactNode; -}) => { - const [state, dispatch] = useReducer(reducer as unknown as Reducer, {}); +export const ChannelsStateProvider = ({ children }: { children: ReactNode }) => { + const [state, dispatch] = useReducer(reducer as unknown as Reducer, {}); - const setState = useCallback((payload: Payload) => { + const setState = useCallback((payload: Payload) => { dispatch({ payload, type: 'SET_STATE' }); }, []); @@ -116,12 +94,8 @@ export const ChannelsStateProvider = < ); }; -export const useChannelsStateContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const contextValue = useContext( - ChannelsStateContext, - ) as unknown as ChannelsStateContextValue; +export const useChannelsStateContext = () => { + const contextValue = useContext(ChannelsStateContext) as unknown as ChannelsStateContextValue; if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( diff --git a/package/src/contexts/channelsStateContext/useChannelState.ts b/package/src/contexts/channelsStateContext/useChannelState.ts index b868b327ea..8500a90f18 100644 --- a/package/src/contexts/channelsStateContext/useChannelState.ts +++ b/package/src/contexts/channelsStateContext/useChannelState.ts @@ -6,12 +6,7 @@ import { useChannelsStateContext } from './ChannelsStateContext'; import type { ChannelsStateContextValue, ChannelState, Keys } from './ChannelsStateContext'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - -type StateManagerParams< - Key extends Keys, - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = ChannelsStateContextValue & { +type StateManagerParams = ChannelsStateContextValue & { cid: string; key: Key; }; @@ -21,20 +16,16 @@ type StateManagerParams< updates to the ChannelsStateContext reducer. It receives the cid and key which it wants to update and perform the state updates. Also supports a initialState. */ -export function useStateManager< - Key extends Keys, - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - { cid, key, setState, state }: StateManagerParams, - initialValue?: ChannelState[Key], +export function useStateManager( + { cid, key, setState, state }: StateManagerParams, + initialValue?: ChannelState[Key], ) { // eslint-disable-next-line react-hooks/exhaustive-deps const memoizedInitialValue = useMemo(() => initialValue, []); - const value = - state[cid]?.[key] || (memoizedInitialValue as ChannelState[Key]); + const value = state[cid]?.[key] || (memoizedInitialValue as ChannelState[Key]); const setValue = useCallback( - (value: ChannelState[Key]) => setState({ cid, key, value }), + (value: ChannelState[Key]) => setState({ cid, key, value }), // eslint-disable-next-line react-hooks/exhaustive-deps [cid, key], ); @@ -42,21 +33,17 @@ export function useStateManager< return [value, setValue] as const; } -export type UseChannelStateValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - setThreadMessages: (value: ChannelState['threadMessages']) => void; - threadMessages: ChannelState['threadMessages']; +export type UseChannelStateValue = { + setThreadMessages: (value: ChannelState['threadMessages']) => void; + threadMessages: ChannelState['threadMessages']; }; -export function useChannelState< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: ChannelType | undefined, +export function useChannelState( + channel: ChannelType | undefined, threadId?: string, -): UseChannelStateValue { +): UseChannelStateValue { const cid = channel?.id || 'id'; // in case channel is not initialized, use generic id string for indexing - const { setState, state } = useChannelsStateContext(); + const { setState, state } = useChannelsStateContext(); const [threadMessages, setThreadMessagesInternal] = useStateManager( { diff --git a/package/src/contexts/chatContext/ChatContext.tsx b/package/src/contexts/chatContext/ChatContext.tsx index 9c19d705f7..9c26b0cc83 100644 --- a/package/src/contexts/chatContext/ChatContext.tsx +++ b/package/src/contexts/chatContext/ChatContext.tsx @@ -3,19 +3,16 @@ import type { ImageProps } from 'react-native'; import type { AppSettingsAPIResponse, Channel, Mute, StreamChat } from 'stream-chat'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { MessageContextValue } from '../messageContext/MessageContext'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; -export type ChatContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type ChatContextValue = { /** * Object of application settings returned from Stream. * */ - appSettings: AppSettingsAPIResponse | null; + appSettings: AppSettingsAPIResponse | null; /** * The StreamChat client object * @@ -32,7 +29,7 @@ export type ChatContextValue< * * @overrideType StreamChat * */ - client: StreamChat; + client: StreamChat; connectionRecovering: boolean; enableOfflineSupport: boolean; /** @@ -40,13 +37,13 @@ export type ChatContextValue< */ ImageComponent: React.ComponentType; isOnline: boolean | null; - mutedUsers: Mute[]; + mutedUsers: Mute[]; /** * @param newChannel Channel to set as active. * * @overrideType Function */ - setActiveChannel: (newChannel?: Channel) => void; + setActiveChannel: (newChannel?: Channel) => void; /** * Instance of channel object from stream-chat package. * @@ -64,28 +61,24 @@ export type ChatContextValue< * * @overrideType Channel */ - channel?: Channel; -} & Partial, 'isMessageAIGenerated'>>; + channel?: Channel; +} & Partial>; export const ChatContext = React.createContext(DEFAULT_BASE_CONTEXT_VALUE as ChatContextValue); -export const ChatProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const ChatProvider = ({ children, value, }: PropsWithChildren<{ - value?: ChatContextValue; + value?: ChatContextValue; }>) => ( {children} ); -export const useChatContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const contextValue = useContext(ChatContext) as unknown as ChatContextValue; +export const useChatContext = () => { + const contextValue = useContext(ChatContext) as unknown as ChatContextValue; if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( diff --git a/package/src/contexts/debugContext/DebugContext.tsx b/package/src/contexts/debugContext/DebugContext.tsx index 03b82339a2..069c9af1af 100644 --- a/package/src/contexts/debugContext/DebugContext.tsx +++ b/package/src/contexts/debugContext/DebugContext.tsx @@ -1,61 +1,52 @@ import React, { PropsWithChildren, useContext, useRef } from 'react'; -import type { Channel, ChannelState, StreamChat } from 'stream-chat'; - -import type { MessageType } from '../../components/MessageList/hooks/useMessageList'; -import type { DefaultStreamChatGenerics } from '../../types/types'; +import type { Channel, ChannelState, LocalMessage, StreamChat } from 'stream-chat'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; -export type DebugDataType< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = - | StreamChat['user'] +export type DebugDataType = + | StreamChat['user'] | { - data: Channel['data']; - members: ChannelState['members']; + data: Channel['data']; + members: ChannelState['members']; }[] - | MessageType[]; + | LocalMessage[]; -export type DebugContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type DebugContextValue = { eventType?: string; sendEventParams?: { action: string; - data: DebugDataType; + data: DebugDataType; }; setEventType?: (data: string) => void; - setSendEventParams?: (data: { action: string; data: DebugDataType }) => void; + setSendEventParams?: (data: { action: string; data: DebugDataType }) => void; }; export const DebugContext = React.createContext( DEFAULT_BASE_CONTEXT_VALUE as React.MutableRefObject, ); -export const DebugContextProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const DebugContextProvider = ({ children, useFlipper, }: PropsWithChildren<{ useFlipper: () => { - updateData: (ref: React.RefObject>) => void; + updateData: (ref: React.RefObject) => void; }; }>) => { - const debugRef = useRef>({ + const debugRef = useRef({ eventType: undefined, sendEventParams: undefined, }); const { updateData } = useFlipper(); - const ref = useRef>({ + const ref = useRef({ setEventType: (data: string) => { debugRef.current.eventType = data; updateData(debugRef); }, - setSendEventParams: (data: { action: string; data: DebugDataType }) => { + setSendEventParams: (data: { action: string; data: DebugDataType }) => { debugRef.current.sendEventParams = data; updateData(debugRef); }, diff --git a/package/src/contexts/imageGalleryContext/ImageGalleryContext.tsx b/package/src/contexts/imageGalleryContext/ImageGalleryContext.tsx index dc9efcd1db..6643114a42 100644 --- a/package/src/contexts/imageGalleryContext/ImageGalleryContext.tsx +++ b/package/src/contexts/imageGalleryContext/ImageGalleryContext.tsx @@ -1,7 +1,8 @@ import React, { PropsWithChildren, useContext, useState } from 'react'; -import type { MessageType } from '../../components/MessageList/hooks/useMessageList'; -import type { DefaultStreamChatGenerics, UnknownType } from '../../types/types'; +import { LocalMessage } from 'stream-chat'; + +import type { UnknownType } from '../../types/types'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; @@ -11,11 +12,9 @@ type SelectedMessage = { url?: string; }; -export type ImageGalleryContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - messages: MessageType[]; - setMessages: React.Dispatch[]>>; +export type ImageGalleryContextValue = { + messages: LocalMessage[]; + setMessages: React.Dispatch>; setSelectedMessage: React.Dispatch>; selectedMessage?: SelectedMessage; }; @@ -24,12 +23,8 @@ export const ImageGalleryContext = React.createContext( DEFAULT_BASE_CONTEXT_VALUE as ImageGalleryContextValue, ); -export const ImageGalleryProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - children, -}: PropsWithChildren) => { - const [messages, setMessages] = useState[]>([]); +export const ImageGalleryProvider = ({ children }: PropsWithChildren) => { + const [messages, setMessages] = useState([]); const [selectedMessage, setSelectedMessage] = useState(); return ( @@ -48,12 +43,8 @@ export const ImageGalleryProvider = < ); }; -export const useImageGalleryContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const contextValue = useContext( - ImageGalleryContext, - ) as unknown as ImageGalleryContextValue; +export const useImageGalleryContext = () => { + const contextValue = useContext(ImageGalleryContext) as unknown as ImageGalleryContextValue; if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( @@ -61,5 +52,5 @@ export const useImageGalleryContext = < ); } - return contextValue as ImageGalleryContextValue; + return contextValue as ImageGalleryContextValue; }; diff --git a/package/src/contexts/messageContext/MessageContext.tsx b/package/src/contexts/messageContext/MessageContext.tsx index a6422c2957..8d4280478b 100644 --- a/package/src/contexts/messageContext/MessageContext.tsx +++ b/package/src/contexts/messageContext/MessageContext.tsx @@ -1,6 +1,6 @@ import React, { PropsWithChildren, useContext } from 'react'; -import type { Attachment } from 'stream-chat'; +import type { Attachment, LocalMessage } from 'stream-chat'; import type { ActionHandler } from '../../components/Attachment/Attachment'; import { ReactionSummary } from '../../components/Message/hooks/useProcessReactions'; @@ -8,19 +8,17 @@ import type { MessagePressableHandlerPayload, PressableHandlerPayload, } from '../../components/Message/Message'; -import type { GroupType, MessageType } from '../../components/MessageList/hooks/useMessageList'; +import type { GroupType } from '../../components/MessageList/hooks/useMessageList'; import type { ChannelContextValue } from '../../contexts/channelContext/ChannelContext'; import type { MessageContentType } from '../../contexts/messagesContext/MessagesContext'; import type { DeepPartial } from '../../contexts/themeContext/ThemeContext'; import type { Theme } from '../../contexts/themeContext/utils/theme'; -import type { DefaultStreamChatGenerics } from '../../types/types'; + import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; export type Alignment = 'right' | 'left'; -export type MessageContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type MessageContextValue = { /** Whether or not actions can be performed on message */ actionsEnabled: boolean; /** Position of the message, either 'right' or 'left' */ @@ -30,7 +28,7 @@ export type MessageContextValue< */ dismissOverlay: () => void; /** The files attached to a message */ - files: Attachment[]; + files: Attachment[]; /** * Position of message in group - top, bottom, middle, single. * @@ -44,19 +42,19 @@ export type MessageContextValue< /** Whether or not message has reactions */ hasReactions: boolean; /** The images attached to a message */ - images: Attachment[]; + images: Attachment[]; /** Boolean that determines if the edited message is pressed. */ isEditedMessageOpen: boolean; /** * A factory function that determines whether a message is AI generated or not. */ - isMessageAIGenerated: (message: MessageType) => boolean; + isMessageAIGenerated: (message: LocalMessage) => boolean; /** Whether or not this is the active user's message */ isMyMessage: boolean; /** Whether or not this is the last message in a group of messages */ lastGroupMessage: boolean; /** Current [message object](https://getstream.io/chat/docs/#message_format) */ - message: MessageType; + message: LocalMessage; /** Order to render the message content */ messageContentOrder: MessageContentType[]; /** @@ -87,8 +85,10 @@ export type MessageContextValue< onPress: (payload: MessagePressableHandlerPayload) => void; onPressIn: ((payload: PressableHandlerPayload) => void) | null; /** The images attached to a message */ - otherAttachments: Attachment[]; + otherAttachments: Attachment[]; reactions: ReactionSummary[]; + /** Whether or not the message has been read by the current user */ + readBy: number | boolean; /** React set state function to set the state of `isEditedMessageOpen` */ setIsEditedMessageOpen: React.Dispatch>; /** @@ -101,7 +101,7 @@ export type MessageContextValue< /** Whether or not the Message is part of a Thread */ threadList: boolean; /** The videos attached to a message */ - videos: Attachment[]; + videos: Attachment[]; goToMessage?: (messageId: string) => void; /** * Function to handle reaction on message @@ -119,31 +119,25 @@ export type MessageContextValue< preventPress?: boolean; /** Whether or not the avatar show show next to Message */ showAvatar?: boolean; -} & Pick, 'channel' | 'members'>; +} & Pick; export const MessageContext = React.createContext( DEFAULT_BASE_CONTEXT_VALUE as MessageContextValue, ); -export const MessageProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const MessageProvider = ({ children, value, }: PropsWithChildren<{ - value?: MessageContextValue; + value?: MessageContextValue; }>) => ( {children} ); -export const useMessageContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const contextValue = useContext( - MessageContext, - ) as unknown as MessageContextValue; +export const useMessageContext = () => { + const contextValue = useContext(MessageContext) as unknown as MessageContextValue; return contextValue; }; diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index 4af1fd68c0..3e1a6eb549 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -11,9 +11,9 @@ import React, { import { Alert, Keyboard, Linking, TextInput, TextInputProps } from 'react-native'; import uniq from 'lodash/uniq'; -import { lookup } from 'mime-types'; import { Attachment, + LocalMessage, logChatPromiseExecution, Message, SendFileAPIResponse, @@ -53,7 +53,6 @@ import type { MessageInputProps } from '../../components/MessageInput/MessageInp import type { MoreOptionsButtonProps } from '../../components/MessageInput/MoreOptionsButton'; import type { SendButtonProps } from '../../components/MessageInput/SendButton'; import type { UploadProgressIndicatorProps } from '../../components/MessageInput/UploadProgressIndicator'; -import type { MessageType } from '../../components/MessageList/hooks/useMessageList'; import type { Emoji } from '../../emoji-data'; import { isDocumentPickerAvailable, @@ -61,14 +60,7 @@ import { MediaTypes, NativeHandlers, } from '../../native'; -import { - Asset, - DefaultStreamChatGenerics, - File, - FileTypes, - FileUpload, - ImageUpload, -} from '../../types/types'; +import { File, FileTypes, FileUpload } from '../../types/types'; import { ACITriggerSettings, ACITriggerSettingsParams, @@ -81,6 +73,7 @@ import { FileStateValue, generateRandomId, getFileNameFromPath, + getFileTypeFromMimeType, isBouncedMessage, } from '../../utils/utils'; import { useAttachmentPickerContext } from '../attachmentPickerContext/AttachmentPickerContext'; @@ -107,17 +100,13 @@ export type EmojiSearchIndex = { search: (query: string) => PromiseLike> | Array | null; }; -export type MentionAllAppUsersQuery< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - filters?: UserFilters; +export type MentionAllAppUsersQuery = { + filters?: UserFilters; options?: UserOptions; - sort?: UserSort; + sort?: UserSort; }; -export type LocalMessageInputContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type LocalMessageInputContext = { appendText: (newText: string) => void; asyncIds: string[]; asyncUploads: { @@ -173,13 +162,13 @@ export type LocalMessageInputContext< * ``` * */ - imageUploads: ImageUpload[]; + imageUploads: FileUpload[]; inputBoxRef: React.MutableRefObject; isValidMessage: () => boolean; mentionedUsers: string[]; numberOfUploads: number; onChange: (newText: string) => void; - onSelectItem: (item: UserResponse) => void; + onSelectItem: (item: UserResponse) => void; openAttachmentPicker: () => void; openCommandsPicker: () => void; openFilePicker: () => void; @@ -201,12 +190,10 @@ export type LocalMessageInputContext< * @param id string ID of image in `imageUploads` object in state of MessageInput */ removeImage: (id: string) => void; - resetInput: (pendingAttachments?: Attachment[]) => void; + resetInput: (pendingAttachments?: Attachment[]) => void; selectedPicker: string | undefined; sending: React.MutableRefObject; - sendMessage: (params?: { - customMessageData?: Partial>; - }) => Promise; + sendMessage: (params?: { customMessageData?: Partial }) => Promise; sendMessageAsync: (id: string) => void; sendThreadMessageInChannel: boolean; setAsyncIds: React.Dispatch>; @@ -220,7 +207,7 @@ export type LocalMessageInputContext< >; setFileUploads: React.Dispatch>; setGiphyActive: React.Dispatch>; - setImageUploads: React.Dispatch>; + setImageUploads: React.Dispatch>; /** * Ref callback to set reference on input box */ @@ -240,19 +227,17 @@ export type LocalMessageInputContext< /** * Mapping of input triggers to the outputs to be displayed by the AutoCompleteInput */ - triggerSettings: TriggerSettings; + triggerSettings: TriggerSettings; updateMessage: () => Promise; /** Function for attempting to upload a file */ uploadFile: ({ newFile }: { newFile: FileUpload }) => Promise; /** Function for attempting to upload an image */ - uploadImage: ({ newImage }: { newImage: ImageUpload }) => Promise; - uploadNewFile: (file: File) => Promise; - uploadNewImage: (image: Partial) => Promise; + uploadImage: ({ newImage }: { newImage: FileUpload }) => Promise; + uploadNewFile: (file: File, fileType?: FileTypes) => Promise; + uploadNewImage: (image: File) => Promise; }; -export type InputMessageInputContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type InputMessageInputContextValue = { /** * Controls how many pixels to the top side the user has to scroll in order to lock the recording view and allow the user to lift their finger from the screen without stopping the recording. */ @@ -286,7 +271,7 @@ export type InputMessageInputContextValue< * * Defaults to and accepts same props as: [AudioRecorder](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/AudioRecorder.tsx) */ - AudioRecorder: React.ComponentType>; + AudioRecorder: React.ComponentType; /** * Controls whether the async audio feature is enabled. */ @@ -323,7 +308,7 @@ export type InputMessageInputContextValue< * * Defaults to and accepts same props as: [CommandsButton](https://getstream.io/chat/docs/sdk/reactnative/ui-components/commands-button/) */ - CommandsButton: React.ComponentType>; + CommandsButton: React.ComponentType; /** * Custom UI component to display the remaining cooldown a user will have to wait before * being allowed to send another message. This component is displayed in place of the @@ -332,12 +317,12 @@ export type InputMessageInputContextValue< * **default** [CooldownTimer](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/CooldownTimer.tsx) */ CooldownTimer: React.ComponentType; - editMessage: StreamChat['updateMessage']; + editMessage: StreamChat['updateMessage']; /** * Custom UI component for FileUploadPreview. * Defaults to and accepts same props as: https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/FileUploadPreview.tsx */ - FileUploadPreview: React.ComponentType>; + FileUploadPreview: React.ComponentType; /** When false, CameraSelectorIcon will be hidden */ hasCameraPicker: boolean; @@ -352,10 +337,10 @@ export type InputMessageInputContextValue< * Custom UI component for ImageUploadPreview. * Defaults to and accepts same props as: https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/ImageUploadPreview.tsx */ - ImageUploadPreview: React.ComponentType>; - InputEditingStateHeader: React.ComponentType>; - InputGiphySearch: React.ComponentType>; - InputReplyStateHeader: React.ComponentType>; + ImageUploadPreview: React.ComponentType; + InputEditingStateHeader: React.ComponentType; + InputGiphySearch: React.ComponentType; + InputReplyStateHeader: React.ComponentType; /** Limit on allowed number of files to attach at a time. */ maxNumberOfFiles: number; /** @@ -372,10 +357,10 @@ export type InputMessageInputContextValue< * * Defaults to and accepts same props as: [SendButton](https://getstream.io/chat/docs/sdk/reactnative/ui-components/send-button/) */ - SendButton: React.ComponentType>; + SendButton: React.ComponentType; sendImageAsync: boolean; - sendMessage: (message: Partial>) => Promise; - setQuotedMessageState: (message: MessageType) => void; + sendMessage: (message: Partial) => Promise; + setQuotedMessageState: (message: LocalMessage) => void; /** * Custom UI component to render checkbox with text ("Also send to channel") in Thread's input box. * When ticked, message will also be sent in parent channel. @@ -389,7 +374,7 @@ export type InputMessageInputContextValue< * * Defaults to and accepts same props as: [AudioRecordingButton](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingButton.tsx) */ - StartAudioRecordingButton: React.ComponentType>; + StartAudioRecordingButton: React.ComponentType; StopMessageStreamingButton: React.ComponentType | null; /** * Custom UI component to render upload progress indicator on attachment preview. @@ -408,9 +393,7 @@ export type InputMessageInputContextValue< /** * Mapping of input triggers to the outputs to be displayed by the AutoCompleteInput */ - autoCompleteTriggerSettings?: ( - settings: ACITriggerSettingsParams, - ) => TriggerSettings; + autoCompleteTriggerSettings?: (settings: ACITriggerSettingsParams) => TriggerSettings; closePollCreationDialog?: () => void; /** * Compress image with quality (from 0 to 1, where 1 is best quality). @@ -436,7 +419,7 @@ export type InputMessageInputContextValue< */ doDocUploadRequest?: ( file: File, - channel: ChannelContextValue['channel'], + channel: ChannelContextValue['channel'], ) => Promise; /** @@ -448,18 +431,15 @@ export type InputMessageInputContextValue< * @overrideType Function */ doImageUploadRequest?: ( - file: { - name?: string; - uri?: string; - }, - channel: ChannelContextValue['channel'], + file: File, + channel: ChannelContextValue['channel'], ) => Promise; /** * Variable that tracks the editing state. * It is defined with message type if the editing state is true, else its undefined. */ - editing?: MessageType; + editing?: LocalMessage; /** * Prop to override the default emoji search index in auto complete suggestion list. */ @@ -475,9 +455,9 @@ export type InputMessageInputContextValue< * Has access to all of [MessageInputContext](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/contexts/messageInputContext/MessageInputContext.tsx) */ Input?: React.ComponentType< - Omit, 'Input'> & - InputButtonsProps & { - getUsers: () => UserResponse[]; + Omit & + InputButtonsProps & { + getUsers: () => UserResponse[]; } >; /** @@ -495,17 +475,17 @@ export type InputMessageInputContextValue< * - openCommandsPicker * - toggleAttachmentPicker */ - InputButtons?: React.ComponentType>; + InputButtons?: React.ComponentType; maxMessageLength?: number; /** Object containing filters/sort/options overrides for an @mention user query */ mentionAllAppUsersEnabled?: boolean; - mentionAllAppUsersQuery?: MentionAllAppUsersQuery; + mentionAllAppUsersQuery?: MentionAllAppUsersQuery; /** * Callback that is called when the text input's text changes. Changed text is passed as a single string argument to the callback handler. */ onChangeText?: (newText: string) => void; openPollCreationDialog?: ({ sendMessage }: Pick) => void; - quotedMessage?: MessageType; + quotedMessage?: LocalMessage; SendMessageDisallowedIndicator?: React.ComponentType; /** * ref for input setter function @@ -518,22 +498,18 @@ export type InputMessageInputContextValue< showPollCreationDialog?: boolean; }; -export type MessageInputContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = LocalMessageInputContext & - Omit, 'sendMessage'>; +export type MessageInputContextValue = LocalMessageInputContext & + Omit; export const MessageInputContext = React.createContext( DEFAULT_BASE_CONTEXT_VALUE as MessageInputContextValue, ); -export const MessageInputProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const MessageInputProvider = ({ children, value, }: PropsWithChildren<{ - value: InputMessageInputContextValue; + value: InputMessageInputContextValue; }>) => { const { closePicker, @@ -545,8 +521,7 @@ export const MessageInputProvider = < setSelectedImages, setSelectedPicker, } = useAttachmentPickerContext(); - const { appSettings, client, enableOfflineSupport, isOnline } = - useChatContext(); + const { appSettings, client, enableOfflineSupport, isOnline } = useChatContext(); const { removeMessage } = useMessagesContext(); const getFileUploadConfig = () => { @@ -568,9 +543,8 @@ export const MessageInputProvider = < const channelCapabities = useOwnCapabilitiesContext(); - const { channel, giphyEnabled, uploadAbortControllerRef } = - useChannelContext(); - const { thread } = useThreadContext(); + const { channel, giphyEnabled, uploadAbortControllerRef } = useChannelContext(); + const { thread } = useThreadContext(); const { t } = useTranslationContext(); const inputBoxRef = useRef(null); const sending = useRef(false); @@ -609,8 +583,8 @@ export const MessageInputProvider = < setText, showMoreOptions, text, - } = useMessageDetailsForState(editing, initialValue); - const { endsAt: cooldownEndsAt, start: startCooldown } = useCooldown(); + } = useMessageDetailsForState(editing, initialValue); + const { endsAt: cooldownEndsAt, start: startCooldown } = useCooldown(); const { onChangeText, emojiSearchIndex, autoCompleteTriggerSettings } = value; const threadId = thread?.id; @@ -697,11 +671,11 @@ export const MessageInputProvider = < const takeAndUploadImage = useStableCallback(async (mediaType?: MediaTypes) => { setSelectedPicker(undefined); closePicker(); - const photo = await NativeHandlers.takePhoto({ + const file = await NativeHandlers.takePhoto({ compressImageQuality: value.compressImageQuality, mediaType, }); - if (photo.askToOpenSettings) { + if (file.askToOpenSettings) { Alert.alert( t('Allow camera access in device settings'), t('Device camera is used to take photos or videos.'), @@ -711,11 +685,12 @@ export const MessageInputProvider = < ], ); } - if (!photo.cancelled) { - if (photo.type.includes('image')) { - await uploadNewImage(photo); + if (!file.cancelled) { + if (file.type.includes('image')) { + // We already compressed the image in the native handler, so we can upload it directly. + await uploadNewImage(file); } else { - await uploadNewFile({ ...photo, mimeType: photo.type, type: FileTypes.Video }); + await uploadNewFile(file); } } }); @@ -750,9 +725,13 @@ export const MessageInputProvider = < } result.assets.forEach(async (asset) => { if (asset.type.includes('image')) { - await uploadNewImage(asset); + const compressedURI = await compressedImageURI(asset, value.compressImageQuality); + await uploadNewImage({ + ...asset, + uri: compressedURI, + }); } else { - await uploadNewFile({ ...asset, mimeType: asset.type, type: FileTypes.Video }); + await uploadNewFile(asset); } }); } @@ -786,7 +765,7 @@ export const MessageInputProvider = < } }, [closeAttachmentPicker, openAttachmentPicker, selectedPicker]); - const onSelectItem = useStableCallback((item: UserResponse) => { + const onSelectItem = useStableCallback((item: UserResponse) => { setMentionedUsers((prevMentionedUsers) => [...prevMentionedUsers, item.id]); }); @@ -809,14 +788,15 @@ export const MessageInputProvider = < if (!result.cancelled && result.assets) { result.assets.forEach(async (asset) => { - /** - * TODO: The current tight coupling of images to the image - * picker does not allow images picked from the file picker - * to be rendered in a preview via the uploadNewImage call. - * This should be updated alongside allowing image a file - * uploads together. - */ - await uploadNewFile(asset); + if (asset.type.includes('image')) { + const compressedURI = await compressedImageURI(asset, value.compressImageQuality); + await uploadNewImage({ + ...asset, + uri: compressedURI, + }); + } else { + await uploadNewFile(asset); + } }); } }); @@ -841,108 +821,102 @@ export const MessageInputProvider = < [imageUploads, setImageUploads, setNumberOfUploads], ); - const resetInput = useStableCallback( - (pendingAttachments: Attachment[] = []) => { - /** - * If the MediaLibrary is available, reset the selected files and images - */ - if (isImageMediaLibraryAvailable()) { - setSelectedFiles([]); - setSelectedImages([]); - } + const resetInput = useStableCallback((pendingAttachments: Attachment[] = []) => { + /** + * If the MediaLibrary is available, reset the selected files and images + */ + if (isImageMediaLibraryAvailable()) { + setSelectedFiles([]); + setSelectedImages([]); + } - setFileUploads([]); - setGiphyActive(false); - setShowMoreOptions(true); - setImageUploads([]); - setMentionedUsers([]); - setNumberOfUploads( - (prevNumberOfUploads) => prevNumberOfUploads - (pendingAttachments?.length || 0), - ); - setText(''); - if (value.editing) { - value.clearEditingState(); - } - }, - ); + setFileUploads([]); + setGiphyActive(false); + setShowMoreOptions(true); + setImageUploads([]); + setMentionedUsers([]); + setNumberOfUploads( + (prevNumberOfUploads) => prevNumberOfUploads - (pendingAttachments?.length || 0), + ); + setText(''); + if (value.editing) { + value.clearEditingState(); + } + }); + + const mapImageUploadToAttachment = useStableCallback((image: FileUpload): Attachment => { + return { + fallback: image.file.name, + image_url: image.url, + mime_type: image.file.type, + original_height: image.file.height, + original_width: image.file.width, + originalImage: image.file, + type: FileTypes.Image, + }; + }); - const mapImageUploadToAttachment = useStableCallback( - (image: ImageUpload): Attachment => { - const mime_type: string | boolean = lookup(image.file.name as string); - const name = image.file.name as string; + const mapFileUploadToAttachment = useStableCallback((file: FileUpload): Attachment => { + if (file.type === FileTypes.Image) { return { - fallback: name, - image_url: image.url, - mime_type: mime_type ? mime_type : undefined, - original_height: image.height, - original_width: image.width, - originalImage: image.file, + fallback: file.file.name, + image_url: file.url, + mime_type: file.file.type, + original_height: file.file.height, + original_width: file.file.width, + originalFile: file.file, type: FileTypes.Image, }; - }, - ); - - const mapFileUploadToAttachment = useStableCallback( - (file: FileUpload): Attachment => { - if (file.type === FileTypes.Image) { - return { - fallback: file.file.name, - image_url: file.url, - mime_type: file.file.mimeType, - originalFile: file.file, - type: FileTypes.Image, - }; - } else if (file.type === FileTypes.Audio) { - return { - asset_url: file.url || file.file.uri, - duration: file.file.duration, - file_size: file.file.size, - mime_type: file.file.mimeType, - originalFile: file.file, - title: file.file.name, - type: FileTypes.Audio, - }; - } else if (file.type === FileTypes.Video) { - return { - asset_url: file.url || file.file.uri, - duration: file.file.duration, - file_size: file.file.size, - mime_type: file.file.mimeType, - originalFile: file.file, - thumb_url: file.thumb_url, - title: file.file.name, - type: FileTypes.Video, - }; - } else if (file.type === FileTypes.VoiceRecording) { - return { - asset_url: file.url || file.file.uri, - duration: file.file.duration, - file_size: file.file.size, - mime_type: file.file.mimeType, - originalFile: file.file, - title: file.file.name, - type: FileTypes.VoiceRecording, - waveform_data: file.file.waveform_data, - }; - } else { - return { - asset_url: file.url || file.file.uri, - file_size: file.file.size, - mime_type: file.file.mimeType, - originalFile: file.file, - title: file.file.name, - type: FileTypes.File, - }; - } - }, - ); + } else if (file.type === FileTypes.Audio) { + return { + asset_url: file.url || file.file.uri, + duration: file.file.duration, + file_size: file.file.size, + mime_type: file.file.type, + originalFile: file.file, + title: file.file.name, + type: FileTypes.Audio, + }; + } else if (file.type === FileTypes.Video) { + return { + asset_url: file.url || file.file.uri, + duration: file.file.duration, + file_size: file.file.size, + mime_type: file.file.type, + originalFile: file.file, + thumb_url: file.thumb_url, + title: file.file.name, + type: FileTypes.Video, + }; + } else if (file.type === FileTypes.VoiceRecording) { + return { + asset_url: file.url || file.file.uri, + duration: file.file.duration, + file_size: file.file.size, + mime_type: file.file.type, + originalFile: file.file, + title: file.file.name, + type: FileTypes.VoiceRecording, + waveform_data: file.file.waveform_data, + }; + } else { + return { + asset_url: file.url || file.file.uri, + file_size: file.file.size, + mime_type: file.file.type, + originalFile: file.file, + title: file.file.name, + type: FileTypes.File, + }; + } + }); // TODO: Figure out why this is async, as it doesn't await any promise. const sendMessage = useStableCallback( async ({ customMessageData, }: { - customMessageData?: Partial>; + customMessageData?: Partial; } = {}) => { if (sending.current) { return; @@ -969,7 +943,7 @@ export const MessageInputProvider = < inputBoxRef.current.clear(); } - const attachments = [] as Attachment[]; + const attachments = [] as Attachment[]; for (const image of imageUploads) { if (enableOfflineSupport) { if (image.state === FileState.NOT_SUPPORTED) { @@ -1038,11 +1012,11 @@ export const MessageInputProvider = < const updatedMessage = { ...message, attachments, - mentioned_users: mentionedUsers, + mentioned_users: mentionedUsers.map((userId) => ({ id: userId })), quoted_message: undefined, text: prevText, ...customMessageData, - } as Parameters['updateMessage']>[0]; + } as Parameters[0]; // TODO: Remove this line and show an error when submit fails value.clearEditingState(); @@ -1062,7 +1036,7 @@ export const MessageInputProvider = < /** * If the message is bounced by moderation, we firstly remove the message from message list and then send a new message. */ - if (message && isBouncedMessage(message as MessageType)) { + if (message && isBouncedMessage(message)) { await removeMessage(message); } value.sendMessage({ @@ -1074,7 +1048,7 @@ export const MessageInputProvider = < show_in_channel: sendThreadMessageInChannel || undefined, text: prevText, ...customMessageData, - } as unknown as StreamMessage); + } as unknown as StreamMessage); value.clearQuotedMessageState(); sending.current = false; @@ -1103,7 +1077,7 @@ export const MessageInputProvider = < image_url: image.url, type: FileTypes.Image, }, - ] as StreamMessage['attachments']; + ] as StreamMessage['attachments']; startCooldown(); try { @@ -1114,7 +1088,7 @@ export const MessageInputProvider = < quoted_message_id: value.quotedMessage ? value.quotedMessage.id : undefined, show_in_channel: sendThreadMessageInChannel || undefined, text: '', - } as unknown as Partial>); + } as unknown as Partial); setAsyncIds((prevAsyncIds) => prevAsyncIds.splice(prevAsyncIds.indexOf(id), 1)); setAsyncUploads((prevAsyncUploads) => { @@ -1138,7 +1112,7 @@ export const MessageInputProvider = < const triggerSettings = useMemo(() => { try { - let triggerSettings: TriggerSettings = {}; + let triggerSettings: TriggerSettings = {}; if (channel) { if (autoCompleteTriggerSettings) { triggerSettings = autoCompleteTriggerSettings({ @@ -1148,7 +1122,7 @@ export const MessageInputProvider = < onMentionSelectItem: onSelectItem, }); } else { - triggerSettings = ACITriggerSettings({ + triggerSettings = ACITriggerSettings({ channel, client, emojiSearchIndex, @@ -1172,7 +1146,7 @@ export const MessageInputProvider = < ...value.editing, quoted_message: undefined, text: giphyEnabled && giphyActive ? `/giphy ${text}` : text, - } as Parameters['updateMessage']>[0]); + } as Parameters[0]); } value.clearEditingState(); @@ -1185,7 +1159,7 @@ export const MessageInputProvider = < const regexCondition = /File (extension \.\w{2,4}|type \S+) is not supported/; const getUploadSetStateAction = useStableCallback( - ( + ( id: string, fileState: FileStateValue, extraData: Partial = {}, @@ -1245,11 +1219,11 @@ export const MessageInputProvider = < client.createAbortControllerForNextRequest(), ); // Compress images selected through file picker when uploading them - if (file.mimeType?.includes('image')) { + if (file.type?.includes('image')) { const compressedUri = await compressedImageURI(file, value.compressImageQuality); - response = await channel.sendFile(compressedUri, filename, file.mimeType); + response = await channel.sendFile(compressedUri, filename, file.type); } else { - response = await channel.sendFile(file.uri, filename, file.mimeType); + response = await channel.sendFile(file.uri, filename, file.type); } uploadAbortControllerRef.current.delete(filename); } @@ -1272,7 +1246,7 @@ export const MessageInputProvider = < } }); - const uploadImage = useStableCallback(async ({ newImage }: { newImage: ImageUpload }) => { + const uploadImage = useStableCallback(async ({ newImage }: { newImage: FileUpload }) => { const { file, id } = newImage || {}; if (!file) { @@ -1286,17 +1260,16 @@ export const MessageInputProvider = < const filename = escapeRegExp(file.name ?? getFileNameFromPath(uri)); try { - const compressedUri = await compressedImageURI(file, value.compressImageQuality); - const contentType = lookup(filename) || 'multipart/form-data'; + const contentType = file.type || 'multipart/form-data'; if (value.doImageUploadRequest) { response = await value.doImageUploadRequest(file, channel); - } else if (compressedUri && channel) { + } else if (channel) { if (value.sendImageAsync) { uploadAbortControllerRef.current.set( filename, client.createAbortControllerForNextRequest(), ); - channel.sendImage(compressedUri, filename, contentType).then( + channel.sendImage(file.uri, filename, contentType).then( (res) => { uploadAbortControllerRef.current.delete(filename); if (asyncIds.includes(id)) { @@ -1310,7 +1283,7 @@ export const MessageInputProvider = < return prevAsyncUploads; }); } else { - const newImageUploads = getUploadSetStateAction( + const newImageUploads = getUploadSetStateAction( id, FileState.UPLOADED, { @@ -1329,13 +1302,13 @@ export const MessageInputProvider = < filename, client.createAbortControllerForNextRequest(), ); - response = await channel.sendImage(compressedUri, filename, contentType); + response = await channel.sendImage(file.uri, filename, contentType); uploadAbortControllerRef.current.delete(filename); } } if (Object.keys(response).length) { - const newImageUploads = getUploadSetStateAction(id, FileState.UPLOADED, { + const newImageUploads = getUploadSetStateAction(id, FileState.UPLOADED, { height: file.height, url: response.file, width: file.width, @@ -1355,7 +1328,12 @@ export const MessageInputProvider = < } }); - const uploadNewFile = useStableCallback(async (file: File) => { + /** + * The fileType is optional and is used to override the file type detection. + * This is useful for voice recordings, where the file type is not always detected correctly. + * This will change if we unify the file uploads to attachments. + */ + const uploadNewFile = useStableCallback(async (file: File, fileType?: FileTypes) => { try { const id: string = generateRandomId(); const fileConfig = getFileUploadConfig(); @@ -1377,16 +1355,16 @@ export const MessageInputProvider = < } const fileState = isAllowed ? FileState.UPLOADING : FileState.NOT_SUPPORTED; - - // If file type is explicitly provided while upload we use it, else we derive the file type. - const fileType = file.type || file.mimeType?.split('/')[0]; + const derivedFileType = fileType ?? getFileTypeFromMimeType(file.type); const newFile: FileUpload = { duration: file.duration || 0, file, - id: file.id || id, + id, + mime_type: file.type, state: fileState, - type: fileType, + thumb_url: file.thumb_url, + type: derivedFileType, url: file.uri, }; @@ -1403,7 +1381,7 @@ export const MessageInputProvider = < } }); - const uploadNewImage = useStableCallback(async (image: Partial) => { + const uploadNewImage = useStableCallback(async (image: File) => { try { const id = generateRandomId(); const imageUploadConfig = getImageUploadConfig(); @@ -1429,11 +1407,13 @@ export const MessageInputProvider = < const imageState = isAllowed ? FileState.UPLOADING : FileState.NOT_SUPPORTED; - const newImage: ImageUpload = { + const newImage: FileUpload = { file: image, height: image.height, id, + mime_type: image.type, state: imageState, + type: FileTypes.Image, url: image.uri, width: image.width, }; @@ -1527,12 +1507,8 @@ export const MessageInputProvider = < ); }; -export const useMessageInputContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const contextValue = useContext( - MessageInputContext, - ) as unknown as MessageInputContextValue; +export const useMessageInputContext = () => { + const contextValue = useContext(MessageInputContext) as unknown as MessageInputContextValue; if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( diff --git a/package/src/contexts/messageInputContext/__tests__/MessageInputContext.test.tsx b/package/src/contexts/messageInputContext/__tests__/MessageInputContext.test.tsx index 3670fce03d..c3aac6c485 100644 --- a/package/src/contexts/messageInputContext/__tests__/MessageInputContext.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/MessageInputContext.test.tsx @@ -12,7 +12,6 @@ import { generateImageAttachment } from '../../../mock-builders/generator/attach import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; import { FileState } from '../../../utils/utils'; import { InputMessageInputContextValue, @@ -21,20 +20,14 @@ import { useMessageInputContext, } from '../MessageInputContext'; -type WrapperType = - Partial>; +type WrapperType = Partial; afterEach(jest.clearAllMocks); const user1 = generateUser(); const message = generateMessage({ user: user1 }); describe('MessageInputContext', () => { - const Wrapper = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, - >({ - children, - ...rest - }: PropsWithChildren>) => ( + const Wrapper = ({ children, ...rest }: PropsWithChildren) => ( { blocked_mime_types: ['image/png'], }, }, - } as unknown as AppSettingsAPIResponse, + } as unknown as AppSettingsAPIResponse, client: { updateMessage: jest.fn().mockResolvedValue({ message }), - } as unknown as StreamChat, - } as ChatContextValue + } as unknown as StreamChat, + } as ChatContextValue } > + } as MessageInputContextValue } > {children} diff --git a/package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessage.test.tsx.snap b/package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessage.test.tsx.snap index 7ce13b373f..a4dd5829ed 100644 --- a/package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessage.test.tsx.snap +++ b/package/src/contexts/messageInputContext/__tests__/__snapshots__/sendMessage.test.tsx.snap @@ -18,8 +18,12 @@ exports[`MessageInputContext's sendMessage exit sendMessage when edit message is "html": "

regular

", "id": "7a85f744-cc89-4f82-a1d4-5456432cc8bf", "mentioned_users": [ - "dummy1", - "dummy2", + { + "id": "dummy1", + }, + { + "id": "dummy2", + }, ], "quoted_message": undefined, "text": "", @@ -42,7 +46,7 @@ exports[`MessageInputContext's sendMessage exit sendMessage when file upload sta { "asset_url": undefined, "file_size": undefined, - "mime_type": undefined, + "mime_type": "file", "originalFile": { "name": "dummy.pdf", "state": "uploaded", @@ -54,7 +58,7 @@ exports[`MessageInputContext's sendMessage exit sendMessage when file upload sta { "asset_url": undefined, "file_size": undefined, - "mime_type": undefined, + "mime_type": "video/mp4", "originalFile": { "name": "dummy.pdf", "state": "finished", @@ -66,7 +70,7 @@ exports[`MessageInputContext's sendMessage exit sendMessage when file upload sta { "asset_url": undefined, "file_size": undefined, - "mime_type": undefined, + "mime_type": "audio/mp3", "originalFile": { "name": "dummy.pdf", "state": "uploaded", @@ -78,7 +82,7 @@ exports[`MessageInputContext's sendMessage exit sendMessage when file upload sta { "asset_url": undefined, "file_size": undefined, - "mime_type": undefined, + "mime_type": "image/jpeg", "originalFile": { "name": "dummy.pdf", "state": "finished", diff --git a/package/src/contexts/messageInputContext/__tests__/isValidMessage.test.tsx b/package/src/contexts/messageInputContext/__tests__/isValidMessage.test.tsx index 2e9a04c1cc..9ad3fbda9f 100644 --- a/package/src/contexts/messageInputContext/__tests__/isValidMessage.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/isValidMessage.test.tsx @@ -9,7 +9,7 @@ import { } from '../../../mock-builders/generator/attachment'; import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { FileState } from '../../../utils/utils'; import { InputMessageInputContextValue, @@ -20,18 +20,14 @@ import { const user1 = generateUser(); const message = generateMessage({ user: user1 }); -type WrapperType = - Partial>; +type WrapperType = Partial; -const Wrapper = ({ - children, - ...rest -}: PropsWithChildren>) => ( +const Wrapper = ({ children, ...rest }: PropsWithChildren) => ( + } as InputMessageInputContextValue } > {children} diff --git a/package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx b/package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx index 2eeb4520bb..7a2e8be634 100644 --- a/package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/pickFile.test.tsx @@ -9,7 +9,7 @@ import { generateFileAttachment } from '../../../mock-builders/generator/attachm import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; import { NativeHandlers } from '../../../native'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import * as AttachmentPickerContext from '../../attachmentPickerContext/AttachmentPickerContext'; import { @@ -21,18 +21,14 @@ import { const user1 = generateUser(); const message = generateMessage({ user: user1 }); -type WrapperType = - Partial>; +type WrapperType = Partial; -const Wrapper = ({ - children, - ...rest -}: PropsWithChildren>) => ( +const Wrapper = ({ children, ...rest }: PropsWithChildren) => ( + } as MessageInputContextValue } > {children} diff --git a/package/src/contexts/messageInputContext/__tests__/removeFile.test.tsx b/package/src/contexts/messageInputContext/__tests__/removeFile.test.tsx index d45b1c6656..b07c10e402 100644 --- a/package/src/contexts/messageInputContext/__tests__/removeFile.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/removeFile.test.tsx @@ -6,7 +6,7 @@ import { renderHook, waitFor } from '@testing-library/react-native'; import { generateFileUploadPreview } from '../../../mock-builders/generator/attachment'; import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { InputMessageInputContextValue, MessageInputContextValue, @@ -14,18 +14,14 @@ import { useMessageInputContext, } from '../MessageInputContext'; -type WrapperType = - Partial>; +type WrapperType = Partial; -const Wrapper = ({ - children, - ...rest -}: PropsWithChildren>) => ( +const Wrapper = ({ children, ...rest }: PropsWithChildren) => ( + } as MessageInputContextValue } > {children} diff --git a/package/src/contexts/messageInputContext/__tests__/removeImage.test.tsx b/package/src/contexts/messageInputContext/__tests__/removeImage.test.tsx index 4351d3cd37..a1a0cb6c34 100644 --- a/package/src/contexts/messageInputContext/__tests__/removeImage.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/removeImage.test.tsx @@ -6,7 +6,7 @@ import { renderHook, waitFor } from '@testing-library/react-native'; import { generateImageUploadPreview } from '../../../mock-builders/generator/attachment'; import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { InputMessageInputContextValue, MessageInputContextValue, @@ -14,18 +14,14 @@ import { useMessageInputContext, } from '../MessageInputContext'; -type WrapperType = - Partial>; +type WrapperType = Partial; -const Wrapper = ({ - children, - ...rest -}: PropsWithChildren>) => ( +const Wrapper = ({ children, ...rest }: PropsWithChildren) => ( + } as MessageInputContextValue } > {children} diff --git a/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx b/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx index b091ab12da..322656999c 100644 --- a/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx @@ -3,14 +3,15 @@ import { act } from 'react-test-renderer'; import { renderHook, waitFor } from '@testing-library/react-native'; -import type { MessageType } from '../../../components/MessageList/hooks/useMessageList'; +import { LocalMessage } from 'stream-chat'; + import { generateFileUploadPreview, generateImageUploadPreview, } from '../../../mock-builders/generator/attachment'; import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { FileState } from '../../../utils/utils'; import * as AttachmentPickerContext from '../../attachmentPickerContext/AttachmentPickerContext'; import { @@ -20,18 +21,14 @@ import { useMessageInputContext, } from '../MessageInputContext'; -type WrapperType = - Partial>; +type WrapperType = Partial; -const Wrapper = ({ - children, - ...rest -}: PropsWithChildren>) => ( +const Wrapper = ({ children, ...rest }: PropsWithChildren) => ( + } as MessageInputContextValue } > {children} @@ -44,7 +41,7 @@ describe("MessageInputContext's sendMessage", () => { setSelectedFiles: jest.fn(), setSelectedImages: jest.fn(), })); - const message: boolean | MessageType = generateMessage({ + const message: LocalMessage | undefined = generateMessage({ created_at: 'Sat Jul 02 2022 23:55:13 GMT+0530 (India Standard Time)', id: '7a85f744-cc89-4f82-a1d4-5456432cc8bf', updated_at: 'Sat Jul 02 2022 23:55:13 GMT+0530 (India Standard Time)', @@ -53,7 +50,7 @@ describe("MessageInputContext's sendMessage", () => { image: 'fc86ddcb-bac4-400c-9afd-b0c0a1c0cd33', name: '50cbdd0e-ca7e-4478-9e2c-be0f1ac6a995', }), - }) as unknown as MessageType; + }) as unknown as LocalMessage; it('exit sendMessage when file upload status failed', async () => { const initialProps = { @@ -227,7 +224,7 @@ describe("MessageInputContext's sendMessage", () => { it('exit sendMessage when image upload has an error and catch block is executed', () => { const setQuotedMessageStateMock = jest.fn(); const clearQuotedMessageStateMock = jest.fn(); - const generatedQuotedMessage: boolean | MessageType = message; + const generatedQuotedMessage: boolean | LocalMessage = message; const images = [ generateImageUploadPreview({ state: FileState.UPLOADED }), generateImageUploadPreview({ state: FileState.FINISHED }), @@ -272,7 +269,7 @@ describe("MessageInputContext's sendMessage", () => { const clearEditingStateMock = jest.fn(); const editMessageMock = jest.fn().mockResolvedValue({ data: {} }); const images = generateImageUploadPreview({ state: FileState.UPLOADED }); - const generatedMessage: boolean | MessageType = message; + const generatedMessage: boolean | LocalMessage = message; const initialProps = { clearEditingState: clearEditingStateMock, clearQuotedMessageState: clearQuotedMessageStateMock, diff --git a/package/src/contexts/messageInputContext/__tests__/sendMessageAsync.test.tsx b/package/src/contexts/messageInputContext/__tests__/sendMessageAsync.test.tsx index 3b802c6c2e..b552eff3f2 100644 --- a/package/src/contexts/messageInputContext/__tests__/sendMessageAsync.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/sendMessageAsync.test.tsx @@ -6,7 +6,6 @@ import { renderHook, waitFor } from '@testing-library/react-native'; import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; import { FileState } from '../../../utils/utils'; import { InputMessageInputContextValue, @@ -15,18 +14,14 @@ import { useMessageInputContext, } from '../MessageInputContext'; -type WrapperType = - Partial>; +type WrapperType = Partial; -const Wrapper = ({ - children, - ...rest -}: PropsWithChildren>) => ( +const Wrapper = ({ children, ...rest }: PropsWithChildren) => ( + } as MessageInputContextValue } > {children} diff --git a/package/src/contexts/messageInputContext/__tests__/updateMessage.test.tsx b/package/src/contexts/messageInputContext/__tests__/updateMessage.test.tsx index 39f5e131ff..5e342b4488 100644 --- a/package/src/contexts/messageInputContext/__tests__/updateMessage.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/updateMessage.test.tsx @@ -3,14 +3,12 @@ import { act } from 'react-test-renderer'; import { renderHook } from '@testing-library/react-native'; -import type { StreamChat } from 'stream-chat'; - -import type { MessageType } from '../../../components/MessageList/hooks/useMessageList'; +import type { LocalMessage, StreamChat } from 'stream-chat'; import { ChatContextValue, ChatProvider } from '../../../contexts/chatContext/ChatContext'; import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import * as AttachmentPickerContext from '../../attachmentPickerContext/AttachmentPickerContext'; import { InputMessageInputContextValue, @@ -21,27 +19,23 @@ import { const message = generateMessage({}); -type WrapperType = - Partial>; +type WrapperType = Partial; -const Wrapper = ({ - children, - ...rest -}: PropsWithChildren>) => ( +const Wrapper = ({ children, ...rest }: PropsWithChildren) => ( , - } as ChatContextValue + } as unknown as StreamChat, + } as ChatContextValue } > + } as MessageInputContextValue } > {children} @@ -55,7 +49,7 @@ describe("MessageInputContext's updateMessage", () => { setSelectedImages: jest.fn(), })); const clearEditingStateMock = jest.fn(); - const generatedMessage: boolean | MessageType = generateMessage({ + const generatedMessage: boolean | LocalMessage = generateMessage({ created_at: 'Sat Jul 02 2022 23:55:13 GMT+0530 (India Standard Time)', id: '7a85f744-cc89-4f82-a1d4-5456432cc8bf', text: 'hey', @@ -65,7 +59,7 @@ describe("MessageInputContext's updateMessage", () => { image: 'fc86ddcb-bac4-400c-9afd-b0c0a1c0cd33', name: '50cbdd0e-ca7e-4478-9e2c-be0f1ac6a995', }), - }) as unknown as MessageType; + }) as unknown as LocalMessage; it('updateMessage throws error as clearEditingState is not available', async () => { const initialProps = { diff --git a/package/src/contexts/messageInputContext/__tests__/uploadFile.test.tsx b/package/src/contexts/messageInputContext/__tests__/uploadFile.test.tsx index 7d7b3c5049..2af2fdde78 100644 --- a/package/src/contexts/messageInputContext/__tests__/uploadFile.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/uploadFile.test.tsx @@ -6,28 +6,24 @@ import { renderHook, waitFor } from '@testing-library/react-native'; import { generateFileUploadPreview } from '../../../mock-builders/generator/attachment'; import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { InputMessageInputContextValue, MessageInputProvider, useMessageInputContext, } from '../MessageInputContext'; -type WrapperType = - Partial>; +type WrapperType = Partial; const user1 = generateUser(); const message = generateMessage({ user: user1 }); -const Wrapper = ({ - children, - ...rest -}: PropsWithChildren>) => ( +const Wrapper = ({ children, ...rest }: PropsWithChildren) => ( + } as InputMessageInputContextValue } > {children} diff --git a/package/src/contexts/messageInputContext/__tests__/uploadImage.test.tsx b/package/src/contexts/messageInputContext/__tests__/uploadImage.test.tsx index 7bd2a571fc..97d34b05bd 100644 --- a/package/src/contexts/messageInputContext/__tests__/uploadImage.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/uploadImage.test.tsx @@ -5,25 +5,21 @@ import { renderHook, waitFor } from '@testing-library/react-native'; import { generateImageUploadPreview } from '../../../mock-builders/generator/attachment'; import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { InputMessageInputContextValue, MessageInputProvider, useMessageInputContext, } from '../MessageInputContext'; -type WrapperType = - Partial>; +type WrapperType = Partial; -const Wrapper = ({ - children, - ...rest -}: PropsWithChildren>) => ( +const Wrapper = ({ children, ...rest }: PropsWithChildren) => ( + } as InputMessageInputContextValue } > {children} diff --git a/package/src/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.tsx b/package/src/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.tsx index fa10fba349..c39d95a712 100644 --- a/package/src/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.tsx @@ -1,6 +1,6 @@ import { renderHook } from '@testing-library/react-native'; -import type { MessageType } from '../../../components'; +import { LocalMessage } from 'stream-chat'; import { generateFileAttachment, @@ -9,28 +9,33 @@ import { import { generateMessage } from '../../../mock-builders/generator/message'; import { generateUser } from '../../../mock-builders/generator/user'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; + import { useMessageDetailsForState } from '../hooks/useMessageDetailsForState'; describe('useMessageDetailsForState', () => { - it.each([[{ message: true }], [{ initialValue: '', message: true }]])( + const message = generateMessage({ text: 'Dummy text' }); + it.each([[{ message }, { initialValue: '', message }]])( 'test state of useMessageDetailsForState when initialProps differ', () => { - const { result } = renderHook(({ message }) => useMessageDetailsForState(message), { - initialProps: { message: true }, - }); + const { result } = renderHook( + ({ message }) => useMessageDetailsForState(message as unknown as LocalMessage), + { + initialProps: { message }, + }, + ); - expect(result.current.text).toBe(''); + expect(result.current.text).toBe(message.text); }, ); it('showMoreOptions is true when initialValue and text is same', () => { const { result } = renderHook( - ({ initialValue, message }) => useMessageDetailsForState(message, initialValue), + ({ initialValue, message }) => + useMessageDetailsForState(message as unknown as LocalMessage, initialValue), { initialProps: { initialValue: 'Dummy text', - message: generateMessage({ text: 'Dummy text' }) as MessageType, + message: generateMessage({ text: 'Dummy text' }), }, }, ); @@ -41,10 +46,7 @@ describe('useMessageDetailsForState', () => { it('fileUploads, imageUploads and mentionedUsers are not empty when attachments are present in message', () => { const { result } = renderHook( ({ initialValue, message }) => - useMessageDetailsForState( - message as unknown as MessageType | boolean, - initialValue, - ), + useMessageDetailsForState(message as unknown as LocalMessage, initialValue), { initialProps: { initialValue: '', diff --git a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts index b9e48da58f..6fda3788f7 100644 --- a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts +++ b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts @@ -1,12 +1,9 @@ import { useMemo } from 'react'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; import type { ThreadContextValue } from '../../threadContext/ThreadContext'; import type { MessageInputContextValue } from '../MessageInputContext'; -export const useCreateMessageInputContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const useCreateMessageInputContext = ({ additionalTextInputProps, appendText, asyncIds, @@ -117,12 +114,9 @@ export const useCreateMessageInputContext = < uploadNewFile, uploadNewImage, UploadProgressIndicator, -}: MessageInputContextValue & - Pick, 'thread'>) => { +}: MessageInputContextValue & Pick) => { const editingdep = editing?.id; - const fileUploadsValue = fileUploads - .map(({ duration, paused, progress, state }) => `${state},${paused},${progress},${duration}`) - .join(); + const fileUploadsValue = fileUploads.map(({ state }) => state).join(); const imageUploadsValue = imageUploads.map(({ state }) => state).join(); const asyncUploadsValue = Object.keys(asyncUploads).join(); const mentionedUsersLength = mentionedUsers.length; @@ -130,7 +124,7 @@ export const useCreateMessageInputContext = < const threadId = thread?.id; const asyncIdsLength = asyncIds.length; - const messageInputContext: MessageInputContextValue = useMemo( + const messageInputContext: MessageInputContextValue = useMemo( () => ({ additionalTextInputProps, appendText, diff --git a/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts b/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts index 9ff7843a73..4900583c22 100644 --- a/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts +++ b/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts @@ -2,24 +2,17 @@ import { useEffect, useState } from 'react'; import { Attachment } from 'stream-chat'; -import { - DefaultStreamChatGenerics, - FileTypes, - FileUpload, - ImageUpload, -} from '../../../types/types'; -import { generateRandomId, stringifyMessage } from '../../../utils/utils'; +import { FileTypes, FileUpload } from '../../../types/types'; +import { generateRandomId, getFileTypeFromMimeType, stringifyMessage } from '../../../utils/utils'; import type { MessageInputContextValue } from '../MessageInputContext'; -export const useMessageDetailsForState = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - message: MessageInputContextValue['editing'], +export const useMessageDetailsForState = ( + message: MessageInputContextValue['editing'], initialValue?: string, ) => { const [fileUploads, setFileUploads] = useState([]); - const [imageUploads, setImageUploads] = useState([]); + const [imageUploads, setImageUploads] = useState([]); const [mentionedUsers, setMentionedUsers] = useState([]); const [numberOfUploads, setNumberOfUploads] = useState(0); @@ -49,72 +42,65 @@ export const useMessageDetailsForState = < // eslint-disable-next-line react-hooks/exhaustive-deps }, [messageValue]); - const mapAttachmentToFileUpload = (attachment: Attachment): FileUpload => { + const mapAttachmentToFileUpload = (attachment: Attachment): FileUpload => { const id = generateRandomId(); if (attachment.type === FileTypes.Audio) { return { file: { - duration: attachment.duration, - mimeType: attachment.mime_type, + duration: attachment.duration || 0, name: attachment.title || '', - size: attachment.file_size, - uri: attachment.asset_url, + size: attachment.file_size || 0, + type: attachment.mime_type || '', + uri: attachment.asset_url || '', }, id, state: 'finished', + type: FileTypes.Audio, url: attachment.asset_url, }; } else if (attachment.type === FileTypes.Video) { return { file: { - duration: attachment.duration, - mimeType: attachment.mime_type, + duration: attachment.duration || 0, name: attachment.title || '', - size: attachment.file_size, - uri: attachment.asset_url, + size: attachment.file_size || 0, + thumb_url: attachment.thumb_url || '', + type: attachment.mime_type || '', + uri: attachment.asset_url || '', }, id, state: 'finished', thumb_url: attachment.thumb_url, + type: FileTypes.Video, url: attachment.asset_url, }; } else if (attachment.type === FileTypes.VoiceRecording) { return { file: { - duration: attachment.duration, - mimeType: attachment.mime_type, + duration: attachment.duration || 0, name: attachment.title || '', - size: attachment.file_size, - uri: attachment.asset_url, + size: attachment.file_size || 0, + type: attachment.mime_type || '', + uri: attachment.asset_url || '', waveform_data: attachment.waveform_data, }, id, state: 'finished', - url: attachment.asset_url, - }; - } else if (attachment.type === FileTypes.File) { - return { - file: { - mimeType: attachment.mime_type, - name: attachment.title || '', - size: attachment.file_size, - uri: attachment.asset_url, - }, - id, - state: 'finished', + type: FileTypes.VoiceRecording, url: attachment.asset_url, }; } else { return { file: { - mimeType: attachment.mime_type, name: attachment.title || '', - size: attachment.file_size, - uri: attachment.asset_url, + size: attachment.file_size || 0, + type: attachment.mime_type || '', + uri: attachment.asset_url || '', }, id, state: 'finished', + type: getFileTypeFromMimeType(attachment.mime_type || ''), url: attachment.asset_url, }; } @@ -124,7 +110,7 @@ export const useMessageDetailsForState = < if (message) { setText(message?.text || ''); const newFileUploads: FileUpload[] = []; - const newImageUploads: ImageUpload[] = []; + const newImageUploads: FileUpload[] = []; const attachments = Array.isArray(message.attachments) ? message.attachments : []; @@ -133,14 +119,16 @@ export const useMessageDetailsForState = < const id = generateRandomId(); newImageUploads.push({ file: { - height: attachment.original_height, - name: attachment.fallback, - size: attachment.file_size, - type: attachment.type, - width: attachment.original_width, + height: attachment.original_height || 0, + name: attachment.fallback || '', + size: attachment.file_size || 0, + type: attachment.type || '', + uri: attachment.image_url || '', + width: attachment.original_width || 0, }, id, state: 'finished', + type: FileTypes.Image, url: attachment.image_url || attachment.asset_url || attachment.thumb_url, }); } else { diff --git a/package/src/contexts/messageInputContext/utils/utils.ts b/package/src/contexts/messageInputContext/utils/utils.ts index 4999a1f1ec..9fbc3ead8a 100644 --- a/package/src/contexts/messageInputContext/utils/utils.ts +++ b/package/src/contexts/messageInputContext/utils/utils.ts @@ -1,13 +1,13 @@ import { lookup } from 'mime-types'; import type { FileUploadConfig } from 'stream-chat'; -import { Asset, File } from '../../../types/types'; +import { File } from '../../../types/types'; export const MAX_FILE_SIZE_TO_UPLOAD = 100 * 1024 * 1024; // 100 MB type CheckUploadPermissionsParams = { config: FileUploadConfig; - file: File | Partial; + file: File; }; /** @@ -44,10 +44,9 @@ export const isUploadAllowed = ({ config, file }: CheckUploadPermissionsParams) } if (allowed_mime_types?.length) { - if (file.mimeType) { + if (file.type) { const allowed = allowed_mime_types.some( - (mimeType: string) => - file.mimeType && file.mimeType.toLowerCase() === mimeType.toLowerCase(), + (mimeType: string) => file.type && file.type.toLowerCase() === mimeType.toLowerCase(), ); if (!allowed) { @@ -66,10 +65,9 @@ export const isUploadAllowed = ({ config, file }: CheckUploadPermissionsParams) } if (blocked_mime_types?.length) { - if (file.mimeType) { + if (file.type) { const blocked = blocked_mime_types.some( - (mimeType: string) => - file.mimeType && file.mimeType.toLowerCase() === mimeType.toLowerCase(), + (mimeType: string) => file.type && file.type.toLowerCase() === mimeType.toLowerCase(), ); if (blocked) { diff --git a/package/src/contexts/messagesContext/MessagesContext.tsx b/package/src/contexts/messagesContext/MessagesContext.tsx index 19de7a0104..2442ca713c 100644 --- a/package/src/contexts/messagesContext/MessagesContext.tsx +++ b/package/src/contexts/messagesContext/MessagesContext.tsx @@ -2,7 +2,7 @@ import React, { PropsWithChildren, useContext } from 'react'; import { PressableProps, ViewProps } from 'react-native'; -import type { Attachment, ChannelState, MessageResponse } from 'stream-chat'; +import type { Attachment, ChannelState, LocalMessage, MessageResponse } from 'stream-chat'; import type { PollContentProps, StreamingMessageViewProps } from '../../components'; import type { AttachmentProps } from '../../components/Attachment/Attachment'; @@ -41,7 +41,6 @@ import type { ReactionListTopProps } from '../../components/Message/MessageSimpl import type { MarkdownRules } from '../../components/Message/MessageSimple/utils/renderText'; import type { MessageActionsParams } from '../../components/Message/utils/messageActions'; import type { DateHeaderProps } from '../../components/MessageList/DateHeader'; -import type { MessageType } from '../../components/MessageList/hooks/useMessageList'; import type { InlineDateSeparatorProps } from '../../components/MessageList/InlineDateSeparator'; import type { MessageListProps } from '../../components/MessageList/MessageList'; import type { MessageSystemProps } from '../../components/MessageList/MessageSystem'; @@ -61,7 +60,7 @@ import { MessageUserReactionsAvatarProps } from '../../components/MessageMenu/Me import { MessageUserReactionsItemProps } from '../../components/MessageMenu/MessageUserReactionsItem'; import type { ReplyProps } from '../../components/Reply/Reply'; import { NativeHandlers } from '../../native'; -import type { DefaultStreamChatGenerics } from '../../types/types'; + import type { ReactionData } from '../../utils/utils'; import type { Alignment, MessageContextValue } from '../messageContext/MessageContext'; import type { SuggestionCommand } from '../suggestionsContext/SuggestionsContext'; @@ -81,26 +80,24 @@ export type MessageContentType = | 'text'; export type DeletedMessagesVisibilityType = 'always' | 'never' | 'receiver' | 'sender'; -export type MessagesContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Pick, 'isMessageAIGenerated'> & { +export type MessagesContextValue = Pick & { /** * UI component for Attachment. * Defaults to: [Attachment](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/Attachment.tsx) */ - Attachment: React.ComponentType>; + Attachment: React.ComponentType; /** * UI component to display AttachmentActions. e.g., send, shuffle, cancel in case of giphy * Defaults to: [AttachmentActions](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/AttachmentActions.tsx) */ - AttachmentActions: React.ComponentType>; + AttachmentActions: React.ComponentType; /** Custom UI component for AudioAttachment. */ AudioAttachment: React.ComponentType; /** * UI component to display generic media type e.g. giphy, url preview etc * Defaults to: [Card](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/Card.tsx) */ - Card: React.ComponentType>; + Card: React.ComponentType; /** * Handler to clear the quoted state of the message. */ @@ -110,7 +107,7 @@ export type MessagesContextValue< * Defaults to: [DateHeader](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageList/DateHeader.tsx) **/ DateHeader: React.ComponentType; - deleteMessage: (message: MessageResponse) => Promise; + deleteMessage: (message: LocalMessage) => Promise; deleteReaction: (type: string, messageId: string) => Promise; /** Should keyboard be dismissed when messaged is touched */ @@ -122,12 +119,12 @@ export type MessagesContextValue< * UI component to display File type attachment. * Defaults to: [FileAttachment](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/FileAttachment.tsx) */ - FileAttachment: React.ComponentType>; + FileAttachment: React.ComponentType; /** * UI component to display group of File type attachments or multiple file attachments (in single message). * Defaults to: [FileAttachmentGroup](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/FileAttachmentGroup.tsx) */ - FileAttachmentGroup: React.ComponentType>; + FileAttachmentGroup: React.ComponentType; /** * UI component for attachment icon for type 'file' attachment. * Defaults to: https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/FileIcon.tsx @@ -138,12 +135,12 @@ export type MessagesContextValue< * UI component to display image attachments * Defaults to: [Gallery](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/Gallery.tsx) */ - Gallery: React.ComponentType>; + Gallery: React.ComponentType; /** * UI component for Giphy * Defaults to: [Giphy](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/Giphy.tsx) */ - Giphy: React.ComponentType>; + Giphy: React.ComponentType; /** * The giphy version to render - check the keys of the [Image Object](https://developers.giphy.com/docs/api/schema#image-object) for possible values. Uses 'fixed_height' by default * */ @@ -179,7 +176,7 @@ export type MessagesContextValue< **/ InlineUnreadIndicator: React.ComponentType; - Message: React.ComponentType>; + Message: React.ComponentType; /** * Custom UI component for rendering Message actions in message menu. * @@ -196,23 +193,23 @@ export type MessagesContextValue< * UI component for MessageAvatar * Defaults to: [MessageAvatar](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageSimple/MessageAvatar.tsx) **/ - MessageAvatar: React.ComponentType>; + MessageAvatar: React.ComponentType; /** * UI Component for MessageBounce */ - MessageBounce: React.ComponentType>; + MessageBounce: React.ComponentType; /** * UI component for MessageContent * Defaults to: [MessageContent](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageSimple/MessageContent.tsx) */ - MessageContent: React.ComponentType>; + MessageContent: React.ComponentType; /** Order to render the message content */ messageContentOrder: MessageContentType[]; /** * UI component for MessageDeleted * Defaults to: [MessageDeleted](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageSimple/MessageDeleted.tsx) */ - MessageDeleted: React.ComponentType>; + MessageDeleted: React.ComponentType; /** * UI component for MessageEditedTimestamp * Defaults to: [MessageEditedTimestamp](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageSimple/MessageEditedTimestamp.tsx) @@ -225,45 +222,45 @@ export type MessagesContextValue< /** * Custom message footer component */ - MessageFooter: React.ComponentType>; - MessageList: React.ComponentType>; + MessageFooter: React.ComponentType; + MessageList: React.ComponentType; /** * UI component for MessageMenu */ - MessageMenu: React.ComponentType>; + MessageMenu: React.ComponentType; /** * Custom message pinned component */ - MessagePinnedHeader: React.ComponentType>; + MessagePinnedHeader: React.ComponentType; /** * UI component for MessageReactionPicker */ - MessageReactionPicker: React.ComponentType>; + MessageReactionPicker: React.ComponentType; /** * UI component for MessageReplies * Defaults to: [MessageReplies](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageSimple/MessageReplies.tsx) */ - MessageReplies: React.ComponentType>; + MessageReplies: React.ComponentType; /** * UI Component for MessageRepliesAvatars * Defaults to: [MessageRepliesAvatars](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageSimple/MessageRepliesAvatars.tsx) */ - MessageRepliesAvatars: React.ComponentType>; + MessageRepliesAvatars: React.ComponentType; /** * UI component for MessageSimple * Defaults to: [MessageSimple](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageSimple/MessageSimple.tsx) */ - MessageSimple: React.ComponentType>; + MessageSimple: React.ComponentType; /** * UI component for MessageStatus (delivered/read) * Defaults to: [MessageStatus](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageSimple/MessageStatus.tsx) */ - MessageStatus: React.ComponentType>; + MessageStatus: React.ComponentType; /** * UI component for MessageSystem * Defaults to: [MessageSystem](https://getstream.io/chat/docs/sdk/reactnative/ui-components/message-system/) */ - MessageSystem: React.ComponentType>; + MessageSystem: React.ComponentType; /** * UI component for MessageTimestamp * Defaults to: [MessageTimestamp](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/Message/MessageSimple/MessageTimestamp.tsx) @@ -274,7 +271,7 @@ export type MessagesContextValue< * * **Default** [MessageUserReactions](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageMenu/MessageUserReactions.tsx) */ - MessageUserReactions: React.ComponentType>; + MessageUserReactions: React.ComponentType; /** * Custom UI component for rendering user reactions avatar under `MessageUserReactionsItem`, in message menu. * @@ -293,23 +290,23 @@ export type MessagesContextValue< * UI component for Reply * Defaults to: [Reply](https://getstream.io/chat/docs/sdk/reactnative/ui-components/reply/) */ - Reply: React.ComponentType>; + Reply: React.ComponentType; /** * Override the api request for retry message functionality. */ - retrySendMessage: (message: MessageType) => Promise; + retrySendMessage: (message: LocalMessage) => Promise; /** * UI component for ScrollToBottomButton * Defaults to: [ScrollToBottomButton](https://getstream.io/chat/docs/sdk/reactnative/ui-components/scroll-to-bottom-button/) */ ScrollToBottomButton: React.ComponentType; sendReaction: (type: string, messageId: string) => Promise; - setEditingState: (message?: MessageType) => void; - setQuotedMessageState: (message?: MessageType) => void; + setEditingState: (message?: LocalMessage) => void; + setQuotedMessageState: (message?: LocalMessage) => void; /** * UI component for StreamingMessageView. Displays the text of a message with a typewriter animation. */ - StreamingMessageView: React.ComponentType>; + StreamingMessageView: React.ComponentType; /** * UI component for TypingIndicator * Defaults to: [TypingIndicator](https://getstream.io/chat/docs/sdk/reactnative/ui-components/typing-indicator/) @@ -322,11 +319,11 @@ export type MessagesContextValue< TypingIndicatorContainer: React.ComponentType; UnreadMessagesNotification: React.ComponentType; updateMessage: ( - updatedMessage: MessageResponse, + updatedMessage: MessageResponse | LocalMessage, extraState?: { - commands?: SuggestionCommand[]; + commands?: SuggestionCommand[]; messageInput?: string; - threadMessages?: ChannelState['threads'][string]; + threadMessages?: ChannelState['threads'][string]; }, throttled?: boolean, ) => void; @@ -334,7 +331,7 @@ export type MessagesContextValue< * Custom UI component to display enriched url preview. * Defaults to https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/Card.tsx */ - UrlPreview: React.ComponentType>; + UrlPreview: React.ComponentType; VideoThumbnail: React.ComponentType; /** * Provide any additional props for `Pressable` which wraps inner MessageContent component here. @@ -347,18 +344,18 @@ export type MessagesContextValue< * Custom UI component to override default cover (between Header and Footer) of Card component. * Accepts the same props as Card component. */ - CardCover?: React.ComponentType>; + CardCover?: React.ComponentType; /** * Custom UI component to override default Footer of Card component. * Accepts the same props as Card component. */ - CardFooter?: React.ComponentType>; + CardFooter?: React.ComponentType; /** * Custom UI component to override default header of Card component. * Accepts the same props as Card component. */ - CardHeader?: React.ComponentType>; + CardHeader?: React.ComponentType; /** * Full override of the delete message button in the Message Actions @@ -385,39 +382,33 @@ export type MessagesContextValue< * Handler to access when a ban user action is invoked. * @param message */ - handleBan?: (message: MessageType) => Promise; + handleBan?: (message: LocalMessage) => Promise; /** Handler to access when a copy message action is invoked */ - handleCopy?: (message: MessageType) => Promise; + handleCopy?: (message: LocalMessage) => Promise; /** Handler to access when a delete message action is invoked */ - handleDelete?: (message: MessageType) => Promise; + handleDelete?: (message: LocalMessage) => Promise; /** Handler to access when an edit message action is invoked */ - handleEdit?: (message: MessageType) => void; + handleEdit?: (message: LocalMessage) => void; /** Handler to access when a flag message action is invoked */ - handleFlag?: (message: MessageType) => Promise; + handleFlag?: (message: LocalMessage) => Promise; /** Handler to access when a mark unread action is invoked */ - handleMarkUnread?: (message: MessageType) => Promise; + handleMarkUnread?: (message: LocalMessage) => Promise; /** Handler to access when a mute user action is invoked */ - handleMute?: (message: MessageType) => Promise; + handleMute?: (message: LocalMessage) => Promise; /** Handler to access when a pin/unpin user action is invoked*/ - handlePinMessage?: ((message: MessageType) => MessageActionType) | null; + handlePinMessage?: ((message: LocalMessage) => MessageActionType) | null; /** Handler to access when a quoted reply action is invoked */ - handleQuotedReply?: (message: MessageType) => Promise; + handleQuotedReply?: (message: LocalMessage) => Promise; /** Handler to process a reaction */ - handleReaction?: ( - message: MessageType, - reactionType: string, - ) => Promise; + handleReaction?: (message: LocalMessage, reactionType: string) => Promise; /** Handler to access when a retry action is invoked */ - handleRetry?: (message: MessageType) => Promise; + handleRetry?: (message: LocalMessage) => Promise; /** Handler to access when a thread reply action is invoked */ - handleThreadReply?: (message: MessageType) => Promise; + handleThreadReply?: (message: LocalMessage) => Promise; /** A flag specifying whether the poll creation button is available or not. */ hasCreatePoll?: boolean; /** Handler to deal with custom memoization logic of Attachment */ - isAttachmentEqual?: ( - prevAttachment: Attachment, - nextAttachment: Attachment, - ) => boolean; + isAttachmentEqual?: (prevAttachment: Attachment, nextAttachment: Attachment) => boolean; legacyImageViewerSwipeBehaviour?: boolean; /** Object specifying rules defined within simple-markdown https://github.com/Khan/simple-markdown#adding-a-simple-extension */ markdownRules?: MarkdownRules; @@ -473,19 +464,19 @@ export type MessagesContextValue< * * @overrideType Function | Array */ - messageActions?: (param: MessageActionsParams) => MessageActionType[]; + messageActions?: (param: MessageActionsParams) => MessageActionType[]; /** * Custom message header component */ - MessageHeader?: React.ComponentType>; + MessageHeader?: React.ComponentType; MessageSwipeContent?: React.ComponentType; /** * HitSlop for the message swipe to reply gesture */ messageSwipeToReplyHitSlop?: ViewProps['hitSlop']; /** Custom UI component for message text */ - MessageText?: React.ComponentType>; + MessageText?: React.ComponentType; /** * The number of lines of the message text to be displayed */ @@ -520,7 +511,7 @@ export type MessagesContextValue< * /> * ``` */ - onLongPressMessage?: (payload: MessagePressableHandlerPayload) => void; + onLongPressMessage?: (payload: MessagePressableHandlerPayload) => void; /** * Add onPressIn handler for attachments. You have access to payload of that handler as param: * @@ -547,7 +538,7 @@ export type MessagesContextValue< * /> * ``` */ - onPressInMessage?: (payload: MessagePressableHandlerPayload) => void; + onPressInMessage?: (payload: MessagePressableHandlerPayload) => void; /** * Override onPress handler for message. You have access to payload of that handler as param: * @@ -574,7 +565,7 @@ export type MessagesContextValue< * /> * ``` */ - onPressMessage?: (payload: MessagePressableHandlerPayload) => void; + onPressMessage?: (payload: MessagePressableHandlerPayload) => void; /** * Override the entire content of the Poll component. The component has full access to the * usePollState() and usePollContext() hooks. @@ -584,7 +575,7 @@ export type MessagesContextValue< * UI component for ReactionListTop * Defaults to: [ReactionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Reaction/ReactionList.tsx) */ - ReactionListBottom?: React.ComponentType>; + ReactionListBottom?: React.ComponentType; /** * The position of the reaction list in the message */ @@ -594,16 +585,14 @@ export type MessagesContextValue< * UI component for ReactionListTop * Defaults to: [ReactionList](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Reaction/ReactionList.tsx) */ - ReactionListTop?: React.ComponentType>; + ReactionListTop?: React.ComponentType; /** * Full override of the reaction function on Message and Message Overlay * * Please check [cookbook](https://github.com/GetStream/stream-chat-react-native/wiki/Cookbook-v3.0#override-or-intercept-message-actions-edit-delete-reaction-reply-etc) for details. * */ - selectReaction?: ( - message: MessageType, - ) => (reactionType: string) => Promise; + selectReaction?: (message: LocalMessage) => (reactionType: string) => Promise; /** * Boolean to enable/disable the message underlay background when there are unread messages in the Message List. @@ -621,25 +610,19 @@ export const MessagesContext = React.createContext( DEFAULT_BASE_CONTEXT_VALUE as MessagesContextValue, ); -export const MessagesProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const MessagesProvider = ({ children, value, }: PropsWithChildren<{ - value?: MessagesContextValue; + value?: MessagesContextValue; }>) => ( {children} ); -export const useMessagesContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const contextValue = useContext( - MessagesContext, - ) as unknown as MessagesContextValue; +export const useMessagesContext = () => { + const contextValue = useContext(MessagesContext) as unknown as MessagesContextValue; if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( diff --git a/package/src/contexts/overlayContext/OverlayContext.tsx b/package/src/contexts/overlayContext/OverlayContext.tsx index 7f1b25213b..6cc559388d 100644 --- a/package/src/contexts/overlayContext/OverlayContext.tsx +++ b/package/src/contexts/overlayContext/OverlayContext.tsx @@ -6,7 +6,6 @@ import type { Attachment } from 'stream-chat'; import type { AttachmentPickerProps } from '../../components/AttachmentPicker/AttachmentPicker'; import type { ImageGalleryCustomComponents } from '../../components/ImageGallery/ImageGallery'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import type { Streami18n } from '../../utils/i18n/Streami18n'; import type { AttachmentPickerContextValue } from '../attachmentPickerContext/AttachmentPickerContext'; import type { DeepPartial } from '../themeContext/ThemeContext'; @@ -27,9 +26,7 @@ export const OverlayContext = React.createContext( DEFAULT_BASE_CONTEXT_VALUE as OverlayContextValue, ); -export type OverlayProviderProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Partial & +export type OverlayProviderProps = Partial & Partial< Pick< AttachmentPickerContextValue, @@ -47,7 +44,7 @@ export type OverlayProviderProps< | 'VideoRecorderSelectorIcon' > > & - ImageGalleryCustomComponents & { + ImageGalleryCustomComponents & { autoPlayVideo?: boolean; /** * The giphy version to render - check the keys of the [Image Object](https://developers.giphy.com/docs/api/schema#image-object) for possible values. Uses 'fixed_height' by default diff --git a/package/src/contexts/overlayContext/OverlayProvider.tsx b/package/src/contexts/overlayContext/OverlayProvider.tsx index 34c9788d74..cf64e2edbf 100644 --- a/package/src/contexts/overlayContext/OverlayProvider.tsx +++ b/package/src/contexts/overlayContext/OverlayProvider.tsx @@ -26,7 +26,7 @@ import { useStreami18n } from '../../hooks/useStreami18n'; import { useViewport } from '../../hooks/useViewport'; import { isImageMediaLibraryAvailable } from '../../native'; -import type { DefaultStreamChatGenerics } from '../../types/types'; + import { AttachmentPickerProvider } from '../attachmentPickerContext/AttachmentPickerContext'; import { ImageGalleryProvider } from '../imageGalleryContext/ImageGalleryContext'; import { ThemeProvider } from '../themeContext/ThemeContext'; @@ -55,11 +55,7 @@ import { * * @example ./OverlayProvider.md */ -export const OverlayProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - props: PropsWithChildren>, -) => { +export const OverlayProvider = (props: PropsWithChildren) => { const { vh } = useViewport(); const bottomSheetCloseTimeoutRef = useRef>(undefined); const { @@ -211,7 +207,7 @@ export const OverlayProvider = < {children} {overlay === 'gallery' && ( - + = { +export type PaginatedMessageListContextValue = { /** * Load latest messages * @returns Promise @@ -26,7 +23,7 @@ export type PaginatedMessageListContextValue< /** * Messages from client state */ - messages: ChannelState['messages']; + messages: ChannelState['messages']; /** * Has more messages to load */ @@ -53,13 +50,11 @@ export const PaginatedMessageListContext = React.createContext( DEFAULT_BASE_CONTEXT_VALUE as PaginatedMessageListContextValue, ); -export const PaginatedMessageListProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const PaginatedMessageListProvider = ({ children, value, }: PropsWithChildren<{ - value?: PaginatedMessageListContextValue; + value?: PaginatedMessageListContextValue; }>) => ( ); -export const usePaginatedMessageListContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { +export const usePaginatedMessageListContext = () => { const contextValue = useContext( PaginatedMessageListContext, - ) as unknown as PaginatedMessageListContextValue; + ) as unknown as PaginatedMessageListContextValue; if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( diff --git a/package/src/contexts/pollContext/pollContext.tsx b/package/src/contexts/pollContext/pollContext.tsx index c891ed54b1..ec0ef46e8d 100644 --- a/package/src/contexts/pollContext/pollContext.tsx +++ b/package/src/contexts/pollContext/pollContext.tsx @@ -1,29 +1,23 @@ import React, { PropsWithChildren, useContext } from 'react'; -import { Poll } from 'stream-chat'; +import { LocalMessage, Poll } from 'stream-chat'; -import { MessageType } from '../../components'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; -export type PollContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - message: MessageType; - poll: Poll; +export type PollContextValue = { + message: LocalMessage; + poll: Poll; }; export const PollContext = React.createContext(DEFAULT_BASE_CONTEXT_VALUE as PollContextValue); -export const PollContextProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const PollContextProvider = ({ children, value, }: PropsWithChildren<{ - value: PollContextValue; + value: PollContextValue; }>) => ( {children} diff --git a/package/src/contexts/suggestionsContext/SuggestionsContext.tsx b/package/src/contexts/suggestionsContext/SuggestionsContext.tsx index 42794e6607..685be195f1 100644 --- a/package/src/contexts/suggestionsContext/SuggestionsContext.tsx +++ b/package/src/contexts/suggestionsContext/SuggestionsContext.tsx @@ -6,60 +6,37 @@ import type { AutoCompleteSuggestionHeaderProps } from '../../components/AutoCom import type { AutoCompleteSuggestionItemProps } from '../../components/AutoCompleteInput/AutoCompleteSuggestionItem'; import type { AutoCompleteSuggestionListProps } from '../../components/AutoCompleteInput/AutoCompleteSuggestionList'; import type { Emoji } from '../../emoji-data'; -import type { DefaultStreamChatGenerics } from '../../types/types'; + import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; export type SuggestionComponentType = 'command' | 'emoji' | 'mention'; -export const isSuggestionCommand = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - suggestion: Suggestion, -): suggestion is SuggestionCommand => 'args' in suggestion; - -export const isSuggestionEmoji = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - suggestion: Suggestion, -): suggestion is Emoji => 'unicode' in suggestion; - -export const isSuggestionUser = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - suggestion: Suggestion, -): suggestion is SuggestionUser => 'id' in suggestion; - -export type Suggestion< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Emoji | SuggestionCommand | SuggestionUser; - -export type SuggestionCommand< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = CommandResponse; -export type SuggestionUser< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = UserResponse; - -export type Suggestions< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - data: Suggestion[]; - onSelect: (item: Suggestion) => void; +export const isSuggestionCommand = (suggestion: Suggestion): suggestion is SuggestionCommand => + 'args' in suggestion; + +export const isSuggestionEmoji = (suggestion: Suggestion): suggestion is Emoji => + 'unicode' in suggestion; + +export const isSuggestionUser = (suggestion: Suggestion): suggestion is SuggestionUser => + 'id' in suggestion; + +export type Suggestion = Emoji | SuggestionCommand | SuggestionUser; + +export type SuggestionCommand = CommandResponse; +export type SuggestionUser = UserResponse; + +export type Suggestions = { + data: Suggestion[]; + onSelect: (item: Suggestion) => void; queryText?: string; }; -export type SuggestionsContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type SuggestionsContextValue = { AutoCompleteSuggestionHeader: React.ComponentType; - AutoCompleteSuggestionItem: React.ComponentType< - AutoCompleteSuggestionItemProps - >; - AutoCompleteSuggestionList: React.ComponentType< - AutoCompleteSuggestionListProps - >; + AutoCompleteSuggestionItem: React.ComponentType; + AutoCompleteSuggestionList: React.ComponentType; /** Override handler for closing suggestions (mentions, command autocomplete etc) */ closeSuggestions: () => void; /** @@ -69,7 +46,7 @@ export type SuggestionsContextValue< * @overrideType Function */ openSuggestions: (component: SuggestionComponentType) => Promise; - suggestions: Suggestions; + suggestions: Suggestions; triggerType: SuggestionComponentType; /** * Override handler for updating suggestions (mentions, command autocomplete etc) @@ -77,7 +54,7 @@ export type SuggestionsContextValue< * @param newSuggestions {Component|element} UI Component for suggestion item. * @overrideType Function */ - updateSuggestions: (newSuggestions: Suggestions) => void; + updateSuggestions: (newSuggestions: Suggestions) => void; queryText?: string; suggestionsViewActive?: boolean; }; @@ -89,16 +66,14 @@ export const SuggestionsContext = React.createContext( /** * This provider component exposes the properties stored within the SuggestionsContext. */ -export const SuggestionsProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const SuggestionsProvider = ({ children, value, -}: PropsWithChildren<{ value?: Partial> }>) => { +}: PropsWithChildren<{ value?: Partial }>) => { const { AutoCompleteSuggestionHeader, AutoCompleteSuggestionItem, AutoCompleteSuggestionList } = value ?? {}; const [triggerType, setTriggerType] = useState(null); - const [suggestions, setSuggestions] = useState>(); + const [suggestions, setSuggestions] = useState(); const [suggestionsViewActive, setSuggestionsViewActive] = useState(false); const openSuggestions = useCallback((component: SuggestionComponentType) => { @@ -107,7 +82,7 @@ export const SuggestionsProvider = < }, []); const updateSuggestions = useCallback( - (newSuggestions: Suggestions) => { + (newSuggestions: Suggestions) => { setSuggestions(newSuggestions); setSuggestionsViewActive(!!triggerType); }, @@ -151,12 +126,8 @@ export const SuggestionsProvider = < ); }; -export const useSuggestionsContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const contextValue = useContext( - SuggestionsContext, - ) as unknown as SuggestionsContextValue; +export const useSuggestionsContext = () => { + const contextValue = useContext(SuggestionsContext) as unknown as SuggestionsContextValue; if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( diff --git a/package/src/contexts/threadContext/ThreadContext.tsx b/package/src/contexts/threadContext/ThreadContext.tsx index 5ae41f4d89..d60d6b58b1 100644 --- a/package/src/contexts/threadContext/ThreadContext.tsx +++ b/package/src/contexts/threadContext/ThreadContext.tsx @@ -1,29 +1,23 @@ import React, { PropsWithChildren, useContext } from 'react'; -import { ChannelState, Thread } from 'stream-chat'; +import { ChannelState, LocalMessage, Thread } from 'stream-chat'; -import type { MessageType } from '../../components/MessageList/hooks/useMessageList'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; -export type ThreadType< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { thread: MessageType; threadInstance: Thread }; +export type ThreadType = { thread: LocalMessage; threadInstance: Thread }; -export type ThreadContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type ThreadContextValue = { allowThreadMessagesInChannel: boolean; closeThread: () => void; loadMoreThread: () => Promise; - openThread: (message: MessageType) => void; + openThread: (message: LocalMessage) => void; reloadThread: () => void; setThreadLoadingMore: React.Dispatch>; - thread: MessageType | null; + thread: LocalMessage | null; threadHasMore: boolean; - threadMessages: ChannelState['threads'][string]; + threadMessages: ChannelState['threads'][string]; loadMoreRecentThread?: (opts: { limit?: number }) => Promise; /** * Boolean to enable/disable parent message press @@ -36,25 +30,19 @@ export type ThreadContextValue< export const ThreadContext = React.createContext(DEFAULT_BASE_CONTEXT_VALUE as ThreadContextValue); -export const ThreadProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const ThreadProvider = ({ children, value, }: PropsWithChildren<{ - value: ThreadContextValue; + value: ThreadContextValue; }>) => ( {children} ); -export const useThreadContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const contextValue = useContext( - ThreadContext, - ) as unknown as ThreadContextValue; +export const useThreadContext = () => { + const contextValue = useContext(ThreadContext) as unknown as ThreadContextValue; if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( diff --git a/package/src/contexts/threadsContext/ThreadListItemContext.tsx b/package/src/contexts/threadsContext/ThreadListItemContext.tsx index a25bd6fbf0..6a1bcd1e62 100644 --- a/package/src/contexts/threadsContext/ThreadListItemContext.tsx +++ b/package/src/contexts/threadsContext/ThreadListItemContext.tsx @@ -1,41 +1,33 @@ import React, { PropsWithChildren, useContext } from 'react'; -import { Channel, Thread } from 'stream-chat'; +import { Channel, LocalMessage, Thread } from 'stream-chat'; -import { MessageType } from '../../components'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; -export type ThreadListItemContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - channel: Channel; +export type ThreadListItemContextValue = { + channel: Channel; dateString: string | number | undefined; deletedAtDateString: string | number | undefined; - lastReply: MessageType | undefined; + lastReply: LocalMessage | undefined; ownUnreadMessageCount: number; - parentMessage: MessageType | undefined; - thread: Thread; + parentMessage: LocalMessage | undefined; + thread: Thread; }; export const ThreadListItemContext = React.createContext( DEFAULT_BASE_CONTEXT_VALUE as ThreadListItemContextValue, ); -export const ThreadListItemProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const ThreadListItemProvider = ({ children, value, }: PropsWithChildren<{ - value: ThreadListItemContextValue; + value: ThreadListItemContextValue; }>) => ( {children} ); -export const useThreadListItemContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => - useContext(ThreadListItemContext) as unknown as ThreadListItemContextValue; +export const useThreadListItemContext = () => + useContext(ThreadListItemContext) as unknown as ThreadListItemContextValue; diff --git a/package/src/contexts/threadsContext/ThreadsContext.tsx b/package/src/contexts/threadsContext/ThreadsContext.tsx index 16dc2dc5ee..e93c8f30c7 100644 --- a/package/src/contexts/threadsContext/ThreadsContext.tsx +++ b/package/src/contexts/threadsContext/ThreadsContext.tsx @@ -4,19 +4,16 @@ import { FlatListProps } from 'react-native'; import { Channel, Thread } from 'stream-chat'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { ThreadType } from '../threadContext/ThreadContext'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; -export type ThreadsContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type ThreadsContextValue = { isFocused: boolean; isLoading: boolean; isLoadingNext: boolean; - threads: Thread[]; + threads: Thread[]; additionalFlatListProps?: Partial>; loadMore?: () => Promise; onThreadSelect?: (thread: ThreadType, channel: Channel) => void; @@ -31,30 +28,23 @@ export const ThreadsContext = React.createContext( DEFAULT_BASE_CONTEXT_VALUE as ThreadsContextValue, ); -export const ThreadsProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const ThreadsProvider = ({ children, value, }: PropsWithChildren<{ - value: ThreadsContextValue; + value: ThreadsContextValue; }>) => ( {children} ); -export const useThreadsContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const contextValue = useContext( - ThreadsContext, - ) as unknown as ThreadsContextValue; +export const useThreadsContext = () => { + const contextValue = useContext(ThreadsContext) as unknown as ThreadsContextValue; if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( - // TODO: Set correct link to docs page, should be the new ThreadList instead of Channel - 'The useThreadsContext hook was called outside of the ThreadsContext provider. Make sure you have configured the ThreadList component correctly - https://getstream.io/chat/docs/sdk/reactnative/basics/hello_stream_chat/#channel', + 'The useThreadsContext hook was called outside of the ThreadsContext provider. Make sure you have configured the ThreadList component correctly - https://getstream.io/chat/docs/sdk/react-native/ui-components/thread-list/', ); } diff --git a/package/src/contexts/typingContext/TypingContext.tsx b/package/src/contexts/typingContext/TypingContext.tsx index 62ca993063..86f70c5560 100644 --- a/package/src/contexts/typingContext/TypingContext.tsx +++ b/package/src/contexts/typingContext/TypingContext.tsx @@ -2,38 +2,29 @@ import React, { PropsWithChildren, useContext } from 'react'; import type { ChannelState } from 'stream-chat'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; -export type TypingContextValue< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - typing: ChannelState['typing']; +export type TypingContextValue = { + typing: ChannelState['typing']; }; export const TypingContext = React.createContext(DEFAULT_BASE_CONTEXT_VALUE as TypingContextValue); -export const TypingProvider = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const TypingProvider = ({ children, value, }: PropsWithChildren<{ - value: TypingContextValue; + value: TypingContextValue; }>) => ( {children} ); -export const useTypingContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->() => { - const contextValue = useContext( - TypingContext, - ) as unknown as TypingContextValue; +export const useTypingContext = () => { + const contextValue = useContext(TypingContext) as unknown as TypingContextValue; if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( diff --git a/package/src/hooks/useSelectedChannelState.ts b/package/src/hooks/useSelectedChannelState.ts index 2c7b5c2e03..78b400c20a 100644 --- a/package/src/hooks/useSelectedChannelState.ts +++ b/package/src/hooks/useSelectedChannelState.ts @@ -3,33 +3,25 @@ import { useCallback } from 'react'; import type { Channel, EventTypes } from 'stream-chat'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; -import { DefaultStreamChatGenerics } from '../types/types'; - const noop = () => {}; -export function useSelectedChannelState< - StreamChatGenerics extends DefaultStreamChatGenerics, - O, ->(_: { - channel: Channel; - selector: (channel: Channel) => O; +export function useSelectedChannelState(_: { + channel: Channel; + selector: (channel: Channel) => O; stateChangeEventKeys?: EventTypes[]; }): O; -export function useSelectedChannelState< - StreamChatGenerics extends DefaultStreamChatGenerics, - O, ->(_: { - selector: (channel: Channel) => O; - channel?: Channel | undefined; +export function useSelectedChannelState(_: { + selector: (channel: Channel) => O; + channel?: Channel | undefined; stateChangeEventKeys?: EventTypes[]; }): O | undefined; -export function useSelectedChannelState({ +export function useSelectedChannelState({ channel, selector, stateChangeEventKeys = ['all'], }: { - selector: (channel: Channel) => O; - channel?: Channel; + selector: (channel: Channel) => O; + channel?: Channel; stateChangeEventKeys?: EventTypes[]; }): O | undefined { const subscribe = useCallback( diff --git a/package/src/hooks/useSyncClientEvents.ts b/package/src/hooks/useSyncClientEvents.ts index 82d66207c3..7c3bcca5bf 100644 --- a/package/src/hooks/useSyncClientEvents.ts +++ b/package/src/hooks/useSyncClientEvents.ts @@ -3,40 +3,29 @@ import { useCallback } from 'react'; import type { Channel, EventTypes, StreamChat } from 'stream-chat'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; -import { DefaultStreamChatGenerics } from '../types/types'; - const noop = () => {}; -export function useSyncClientEventsToChannel< - StreamChatGenerics extends DefaultStreamChatGenerics, - O, ->(_: { - channel: Channel; - client: StreamChat; - selector: (channel: Channel, client: StreamChat) => O; +export function useSyncClientEventsToChannel(_: { + channel: Channel; + client: StreamChat; + selector: (channel: Channel, client: StreamChat) => O; stateChangeEventKeys?: EventTypes[]; }): O; -export function useSyncClientEventsToChannel< - StreamChatGenerics extends DefaultStreamChatGenerics, - O, ->(_: { - selector: (channel: Channel, client: StreamChat) => O; - channel?: Channel | undefined; - client?: StreamChat | undefined; +export function useSyncClientEventsToChannel(_: { + selector: (channel: Channel, client: StreamChat) => O; + channel?: Channel | undefined; + client?: StreamChat | undefined; stateChangeEventKeys?: EventTypes[]; }): O | undefined; -export function useSyncClientEventsToChannel< - StreamChatGenerics extends DefaultStreamChatGenerics, - O, ->({ +export function useSyncClientEventsToChannel({ channel, client, selector, stateChangeEventKeys = ['all'], }: { - selector: (channel: Channel, client: StreamChat) => O; - channel?: Channel | undefined; - client?: StreamChat; + selector: (channel: Channel, client: StreamChat) => O; + channel?: Channel | undefined; + client?: StreamChat; stateChangeEventKeys?: EventTypes[]; }): O | undefined { const subscribe = useCallback( diff --git a/package/src/hooks/useTranslatedMessage.ts b/package/src/hooks/useTranslatedMessage.ts index 98df5520e7..628c6ef99a 100644 --- a/package/src/hooks/useTranslatedMessage.ts +++ b/package/src/hooks/useTranslatedMessage.ts @@ -1,15 +1,10 @@ -import type { FormatMessageResponse, MessageResponse, TranslationLanguages } from 'stream-chat'; +import type { LocalMessage, MessageResponse, TranslationLanguages } from 'stream-chat'; import { useTranslationContext } from '../contexts/translationContext/TranslationContext'; -import type { DefaultStreamChatGenerics } from '../types/types'; type TranslationKey = `${TranslationLanguages}_text`; -export const useTranslatedMessage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - message?: MessageResponse | FormatMessageResponse, -) => { +export const useTranslatedMessage = (message?: MessageResponse | LocalMessage) => { const { userLanguage } = useTranslationContext(); const translationKey: TranslationKey = `${userLanguage}_text`; diff --git a/package/src/mock-builders/api/channelMocks.tsx b/package/src/mock-builders/api/channelMocks.tsx index 50b31bacd9..35839a1e70 100644 --- a/package/src/mock-builders/api/channelMocks.tsx +++ b/package/src/mock-builders/api/channelMocks.tsx @@ -1,23 +1,15 @@ -import type { - Attachment, - Channel, - FormatMessageResponse, - MessageResponse, - UserResponse, -} from 'stream-chat'; +import type { Attachment, Channel, LocalMessage, MessageResponse, UserResponse } from 'stream-chat'; import { GROUP_CHANNEL_MEMBERS_MOCK, ONE_MEMBER_WITH_EMPTY_USER_MOCK, } from '../../mock-builders/api/queryMembers'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - const channelName = 'okechukwu'; const CHANNEL = { data: { name: channelName }, state: { messages: [] }, -} as unknown as Channel; +} as unknown as Channel; const CHANNEL_WITH_MESSAGES_TEXT = { data: { name: channelName }, @@ -36,8 +28,8 @@ const CHANNEL_WITH_MESSAGES_TEXT = { id: 'ljkblk', text: 'jkbkbiubicbi', type: 'MessageLabel', - user: { id: 'okechukwu' } as unknown as UserResponse, - } as unknown as MessageResponse, + user: { id: 'okechukwu' } as unknown as UserResponse, + } as unknown as MessageResponse, { args: 'string', attachments: [], @@ -50,11 +42,11 @@ const CHANNEL_WITH_MESSAGES_TEXT = { id: 'jbkjb', text: 'jkbkbiubicbi', type: 'MessageLabel', - user: { id: 'okechukwu' } as unknown as UserResponse, - } as unknown as MessageResponse, + user: { id: 'okechukwu' } as unknown as UserResponse, + } as unknown as MessageResponse, ], }, -} as unknown as Channel; +} as unknown as Channel; const CHANNEL_WITH_DELETED_MESSAGES = { data: { name: channelName }, @@ -63,13 +55,13 @@ const CHANNEL_WITH_DELETED_MESSAGES = { messages: [ { type: 'deleted', - } as unknown as MessageResponse, + } as unknown as MessageResponse, { type: 'deleted', - } as unknown as MessageResponse, + } as unknown as MessageResponse, ], }, -} as unknown as Channel; +} as unknown as Channel; const CHANNEL_WITH_NO_MESSAGES = { data: { name: channelName }, @@ -77,7 +69,7 @@ const CHANNEL_WITH_NO_MESSAGES = { members: GROUP_CHANNEL_MEMBERS_MOCK, messages: [], }, -} as unknown as Channel; +} as unknown as Channel; const CHANNEL_WITH_MESSAGE_COMMAND = { data: { name: channelName }, @@ -94,8 +86,8 @@ const CHANNEL_WITH_MESSAGE_COMMAND = { created_at: new Date('2021-02-12T12:12:35.862Z'), deleted_at: new Date('2021-02-12T12:12:35.862Z'), id: 'ljkblk', - user: { id: 'okechukwu' } as unknown as UserResponse, - } as unknown as MessageResponse, + user: { id: 'okechukwu' } as unknown as UserResponse, + } as unknown as MessageResponse, { args: 'string', attachments: [], @@ -106,11 +98,11 @@ const CHANNEL_WITH_MESSAGE_COMMAND = { created_at: new Date('2021-02-12T12:12:35.862Z'), deleted_at: new Date('2021-02-12T12:12:35.862Z'), id: 'jbkjb', - user: { id: 'okechukwu' } as unknown as UserResponse, - } as unknown as MessageResponse, + user: { id: 'okechukwu' } as unknown as UserResponse, + } as unknown as MessageResponse, ], }, -} as unknown as Channel; +} as unknown as Channel; const CHANNEL_WITH_MESSAGES_ATTACHMENTS = { data: { name: channelName }, @@ -143,17 +135,17 @@ const CHANNEL_WITH_MESSAGES_ATTACHMENTS = { title: 'string', title_link: 'string', type: 'string', - } as Attachment, + } as Attachment, ], channel: CHANNEL, created_at: new Date('2021-02-12T12:12:35.862Z'), deleted_at: new Date('2021-02-12T12:12:35.862Z'), id: 'ljkblk', - user: { id: 'okechukwu' } as unknown as UserResponse, - } as unknown as MessageResponse, + user: { id: 'okechukwu' } as unknown as UserResponse, + } as unknown as MessageResponse, ], }, -} as unknown as Channel; +} as unknown as Channel; const LATEST_MESSAGE = { args: 'string', @@ -167,13 +159,13 @@ const LATEST_MESSAGE = { id: 'string', text: 'jkbkbiubicbi', type: 'MessageLabel', - user: { id: 'okechukwu' } as unknown as UserResponse, -} as unknown as MessageResponse; + user: { id: 'okechukwu' } as unknown as UserResponse, +} as unknown as MessageResponse; -const FORMATTED_MESSAGE: FormatMessageResponse = { +const FORMATTED_MESSAGE: LocalMessage = { created_at: new Date('2021-02-12T12:12:35.862282Z'), id: '', - message: {} as unknown as MessageResponse, + message: {} as unknown as MessageResponse, pinned_at: new Date('2021-02-12T12:12:35.862282Z'), status: 'received', type: 'regular', @@ -195,9 +187,9 @@ const CHANNEL_WITH_MENTIONED_USERS = { { id: 'Max', name: 'Max' }, { id: 'Ada', name: 'Ada' }, { id: 'Enzo', name: 'Enzo' }, - ] as UserResponse[], + ] as UserResponse[], text: 'Max', - } as unknown as MessageResponse, + } as unknown as MessageResponse, { args: 'string', attachments: [], @@ -209,12 +201,12 @@ const CHANNEL_WITH_MENTIONED_USERS = { { id: 'Max', name: 'Max' }, { id: 'Ada', name: 'Ada' }, { id: 'Enzo', name: 'Enzo' }, - ] as UserResponse[], + ] as UserResponse[], text: 'Max', - } as unknown as MessageResponse, + } as unknown as MessageResponse, ], }, -} as unknown as Channel; +} as unknown as Channel; const CHANNEL_WITH_EMPTY_MESSAGE = { state: { @@ -231,8 +223,8 @@ const CHANNEL_WITH_EMPTY_MESSAGE = { { id: 'Max', name: 'Max' }, { id: 'Ada', name: 'Ada' }, { id: 'Enzo', name: 'Enzo' }, - ] as UserResponse[], - } as unknown as MessageResponse, + ] as UserResponse[], + } as unknown as MessageResponse, { args: 'string', attachments: [], @@ -244,11 +236,11 @@ const CHANNEL_WITH_EMPTY_MESSAGE = { { id: 'Max', name: 'Max' }, { id: 'Ada', name: 'Ada' }, { id: 'Enzo', name: 'Enzo' }, - ] as UserResponse[], - } as unknown as MessageResponse, + ] as UserResponse[], + } as unknown as MessageResponse, ], }, -} as unknown as Channel; +} as unknown as Channel; const CHANNEL_WITH_MESSAGES = { data: { name: channelName }, @@ -256,7 +248,7 @@ const CHANNEL_WITH_MESSAGES = { members: GROUP_CHANNEL_MEMBERS_MOCK, messages: [FORMATTED_MESSAGE, FORMATTED_MESSAGE], }, -} as unknown as Channel; +} as unknown as Channel; export { CHANNEL, diff --git a/package/src/native.ts b/package/src/native.ts index a06670dc7c..7debba4ecc 100644 --- a/package/src/native.ts +++ b/package/src/native.ts @@ -1,8 +1,7 @@ import type React from 'react'; import { FlatList as DefaultFlatList, StyleProp, ViewStyle } from 'react-native'; -import type { Asset, File } from './types/types'; - +import type { File } from './types/types'; const fail = () => { throw Error( 'Native handler was not registered, you should import stream-chat-expo or stream-chat-react-native', @@ -31,7 +30,7 @@ type iOS14RefreshGallerySelection = () => Promise; type GetPhotos = ({ after, first }: { first: number; after?: string }) => | Promise<{ - assets: Array & { source: 'picker' }>; + assets: File[]; endCursor: string; hasNextPage: boolean; iOSLimited: boolean; @@ -47,7 +46,7 @@ type PickDocument = ({ maxNumberOfFiles }: { maxNumberOfFiles?: number }) => type PickImageAssetType = { askToOpenSettings?: boolean; - assets?: Array & { source: 'picker' }>; + assets?: File[]; cancelled?: boolean; }; @@ -67,8 +66,7 @@ type ShareOptions = { }; type ShareImage = (options: ShareOptions) => Promise | never; -type Photo = Omit & { - source: 'camera'; +type TakePhotoFileType = File & { askToOpenSettings?: boolean; cancelled?: boolean; }; @@ -78,7 +76,7 @@ export type MediaTypes = 'image' | 'video' | 'mixed'; type TakePhoto = (options: { compressImageQuality?: number; mediaType?: MediaTypes; -}) => Promise | never; +}) => Promise | never; type HapticFeedbackMethod = | 'impactHeavy' @@ -277,6 +275,9 @@ export type VideoType = { seek?: (progress: number) => void; setPositionAsync?: (position: number) => void; style?: StyleProp; + play?: () => void; + pause?: () => void; + replay?: () => void; }; type Handlers = { diff --git a/package/src/store/apis/getChannelMessages.ts b/package/src/store/apis/getChannelMessages.ts index 6b7707bc70..d7d34b3614 100644 --- a/package/src/store/apis/getChannelMessages.ts +++ b/package/src/store/apis/getChannelMessages.ts @@ -4,16 +4,13 @@ import { selectMessagesForChannels } from './queries/selectMessagesForChannels'; 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 { TableRow, TableRowJoinedUser } from '../types'; -export const getChannelMessages = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const getChannelMessages = async ({ channelIds, currentUserId, }: { @@ -53,7 +50,7 @@ export const getChannelMessages = async < }); // Populate the messages. - const cidVsMessages: Record[]> = {}; + const cidVsMessages: Record = {}; messageRows.forEach((m) => { if (!cidVsMessages[m.cid]) { cidVsMessages[m.cid] = []; @@ -61,7 +58,7 @@ export const getChannelMessages = async < if (!isBlockedMessage(m)) { cidVsMessages[m.cid].push( - mapStorableToMessage({ + mapStorableToMessage({ currentUserId, messageRow: m, pollRow: messageIdsVsPolls[m.poll_id], diff --git a/package/src/store/apis/getChannels.ts b/package/src/store/apis/getChannels.ts index 5e6fb48503..1c06f3c453 100644 --- a/package/src/store/apis/getChannels.ts +++ b/package/src/store/apis/getChannels.ts @@ -5,7 +5,6 @@ import { getMembers } from './getMembers'; import { getReads } from './getReads'; import { selectChannels } from './queries/selectChannels'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { mapStorableToChannel } from '../mappers/mapStorableToChannel'; import { SqliteClient } from '../SqliteClient'; @@ -18,28 +17,26 @@ import { SqliteClient } from '../SqliteClient'; * * @returns {Array} Channels with enriched state. */ -export const getChannels = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const getChannels = async ({ channelIds, currentUserId, }: { channelIds: string[]; currentUserId: string; -}): Promise, 'duration'>[]> => { +}): Promise[]> => { SqliteClient.logger?.('info', 'getChannels', { channelIds, currentUserId }); const channels = await selectChannels({ channelIds }); - const cidVsMembers = await getMembers({ channelIds }); - const cidVsReads = await getReads({ channelIds }); - const cidVsMessages = await getChannelMessages({ + const cidVsMembers = await getMembers({ channelIds }); + const cidVsReads = await getReads({ channelIds }); + const cidVsMessages = await getChannelMessages({ channelIds, currentUserId, }); // Enrich the channels with state return channels.map((c) => ({ - ...mapStorableToChannel(c), + ...mapStorableToChannel(c), members: cidVsMembers[c.cid] || [], messages: cidVsMessages[c.cid] || [], pinned_messages: [], diff --git a/package/src/store/apis/getChannelsForFilterSort.ts b/package/src/store/apis/getChannelsForFilterSort.ts index 92123d62ed..aef2f692a6 100644 --- a/package/src/store/apis/getChannelsForFilterSort.ts +++ b/package/src/store/apis/getChannelsForFilterSort.ts @@ -3,8 +3,6 @@ import type { ChannelAPIResponse, ChannelFilters, ChannelSort } from 'stream-cha import { getChannels } from './getChannels'; import { selectChannelIdsForFilterSort } from './queries/selectChannelIdsForFilterSort'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - import { SqliteClient } from '../SqliteClient'; /** @@ -17,17 +15,15 @@ import { SqliteClient } from '../SqliteClient'; * * @returns Array of channels corresponding to filters & sort. Returns null if filters + sort query doesn't exist in "channelQueries" table. */ -export const getChannelsForFilterSort = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const getChannelsForFilterSort = async ({ currentUserId, filters, sort, }: { currentUserId: string; - filters?: ChannelFilters; - sort?: ChannelSort; -}): Promise, 'duration'>[] | null> => { + filters?: ChannelFilters; + sort?: ChannelSort; +}): Promise[] | null> => { if (!filters && !sort) { console.warn('Please provide the query (filters/sort) to fetch channels from DB'); return null; diff --git a/package/src/store/apis/getMembers.ts b/package/src/store/apis/getMembers.ts index b6090bb7f6..cdaf4e85d1 100644 --- a/package/src/store/apis/getMembers.ts +++ b/package/src/store/apis/getMembers.ts @@ -2,26 +2,19 @@ import type { ChannelMemberResponse } from 'stream-chat'; import { selectMembersForChannels } from './queries/selectMembersForChannels'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { mapStorableToMember } from '../mappers/mapStorableToMember'; import { SqliteClient } from '../SqliteClient'; -export const getMembers = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - channelIds, -}: { - channelIds: string[]; -}) => { +export const getMembers = async ({ channelIds }: { channelIds: string[] }) => { SqliteClient.logger?.('info', 'getMembers', { channelIds }); const memberRows = await selectMembersForChannels(channelIds); - const cidVsMembers: Record[]> = {}; + const cidVsMembers: Record = {}; memberRows.forEach((member) => { if (!cidVsMembers[member.cid]) { cidVsMembers[member.cid] = []; } - cidVsMembers[member.cid].push(mapStorableToMember(member)); + cidVsMembers[member.cid].push(mapStorableToMember(member)); }); return cidVsMembers; diff --git a/package/src/store/apis/getReactions.ts b/package/src/store/apis/getReactions.ts index 3565eedc54..e43562646e 100644 --- a/package/src/store/apis/getReactions.ts +++ b/package/src/store/apis/getReactions.ts @@ -1,21 +1,18 @@ import type { ReactionResponse } from 'stream-chat'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { mapStorableToReaction } from '../mappers/mapStorableToReaction'; import { SqliteClient } from '../SqliteClient'; import { TableRowJoinedUser } from '../types'; -export const getReactions = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const getReactions = ({ reactions, }: { reactions: TableRowJoinedUser<'reactions'>[]; -}): ReactionResponse[] => { +}): ReactionResponse[] => { SqliteClient.logger?.('info', 'getReactions', { reactions }); // Enrich the channels with state return reactions.map((reaction) => ({ - ...mapStorableToReaction(reaction), + ...mapStorableToReaction(reaction), })); }; diff --git a/package/src/store/apis/getReactionsforFilterSort.ts b/package/src/store/apis/getReactionsforFilterSort.ts index 4c9d42bab8..ef7592ce0c 100644 --- a/package/src/store/apis/getReactionsforFilterSort.ts +++ b/package/src/store/apis/getReactionsforFilterSort.ts @@ -3,8 +3,6 @@ import type { ReactionFilters, ReactionResponse, ReactionSort } from 'stream-cha import { getReactions } from './getReactions'; import { selectReactionsForMessages } from './queries/selectReactionsForMessages'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - import { SqliteClient } from '../SqliteClient'; /** @@ -13,17 +11,15 @@ import { SqliteClient } from '../SqliteClient'; * @param filters The filters to be applied while fetching reactions. * @param sort The sort to be applied while fetching reactions. */ -export const getReactionsForFilterSort = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const getReactionsForFilterSort = async ({ currentMessageId, filters, sort, }: { currentMessageId: string; - filters?: ReactionFilters; - sort?: ReactionSort; -}): Promise[] | null> => { + filters?: ReactionFilters; + sort?: ReactionSort; +}): Promise => { if (!filters && !sort) { console.warn('Please provide the query (filters/sort) to fetch channels from DB'); return null; diff --git a/package/src/store/apis/getReads.ts b/package/src/store/apis/getReads.ts index e3025e18a1..d6c8301b90 100644 --- a/package/src/store/apis/getReads.ts +++ b/package/src/store/apis/getReads.ts @@ -2,26 +2,19 @@ import type { ReadResponse } from 'stream-chat'; import { selectReadsForChannels } from './queries/selectReadsForChannels'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import { mapStorableToRead } from '../mappers/mapStorableToRead'; import { SqliteClient } from '../SqliteClient'; -export const getReads = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - channelIds, -}: { - channelIds: string[]; -}) => { +export const getReads = async ({ channelIds }: { channelIds: string[] }) => { SqliteClient.logger?.('info', 'getReads', { channelIds }); const reads = await selectReadsForChannels(channelIds); - const cidVsReads: Record[]> = {}; + const cidVsReads: Record = {}; reads.forEach((read) => { if (!cidVsReads[read.cid]) { cidVsReads[read.cid] = []; } - cidVsReads[read.cid].push(mapStorableToRead(read)); + cidVsReads[read.cid].push(mapStorableToRead(read)); }); return cidVsReads; diff --git a/package/src/store/apis/insertReaction.ts b/package/src/store/apis/insertReaction.ts index 5819090b04..1ec6eb4111 100644 --- a/package/src/store/apis/insertReaction.ts +++ b/package/src/store/apis/insertReaction.ts @@ -1,4 +1,4 @@ -import type { FormatMessageResponse, MessageResponse, ReactionResponse } from 'stream-chat'; +import type { LocalMessage, MessageResponse, ReactionResponse } from 'stream-chat'; import { mapReactionToStorable } from '../mappers/mapReactionToStorable'; import { createUpdateQuery } from '../sqlite-utils/createUpdateQuery'; @@ -11,7 +11,7 @@ export const insertReaction = async ({ message, reaction, }: { - message: MessageResponse | FormatMessageResponse; + message: MessageResponse | LocalMessage; reaction: ReactionResponse; flush?: boolean; }) => { diff --git a/package/src/store/apis/queries/selectChannelIdsForFilterSort.ts b/package/src/store/apis/queries/selectChannelIdsForFilterSort.ts index 73cebfb4bc..4a18284d73 100644 --- a/package/src/store/apis/queries/selectChannelIdsForFilterSort.ts +++ b/package/src/store/apis/queries/selectChannelIdsForFilterSort.ts @@ -1,6 +1,5 @@ import type { ChannelFilters, ChannelSort } from 'stream-chat'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; import { createSelectQuery } from '../../sqlite-utils/createSelectQuery'; import { SqliteClient } from '../../SqliteClient'; @@ -16,14 +15,12 @@ import { convertFilterSortToQuery } from '../utils/convertFilterSortToQuery'; * @returns Array of channel ids corresponding to filters & sort. Returns null if filters + sort query doesn't exist in "channelQueries" table. */ -export const selectChannelIdsForFilterSort = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const selectChannelIdsForFilterSort = async ({ filters, sort, }: { - filters?: ChannelFilters; - sort?: ChannelSort; + filters?: ChannelFilters; + sort?: ChannelSort; }): Promise => { const query = convertFilterSortToQuery({ filters, sort }); diff --git a/package/src/store/apis/updateMessage.ts b/package/src/store/apis/updateMessage.ts index 75ba87e21c..290a6fc794 100644 --- a/package/src/store/apis/updateMessage.ts +++ b/package/src/store/apis/updateMessage.ts @@ -1,4 +1,4 @@ -import type { FormatMessageResponse, MessageResponse } from 'stream-chat'; +import type { LocalMessage, MessageResponse } from 'stream-chat'; import { mapMessageToStorable } from '../mappers/mapMessageToStorable'; import { mapReactionToStorable } from '../mappers/mapReactionToStorable'; @@ -14,7 +14,7 @@ export const updateMessage = async ({ flush = true, message, }: { - message: MessageResponse | FormatMessageResponse; + message: MessageResponse | LocalMessage; flush?: boolean; }) => { const queries: PreparedQueries[] = []; diff --git a/package/src/store/apis/updatePollMessage.ts b/package/src/store/apis/updatePollMessage.ts index 7fe342b257..1263a4f9bb 100644 --- a/package/src/store/apis/updatePollMessage.ts +++ b/package/src/store/apis/updatePollMessage.ts @@ -1,6 +1,5 @@ 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'; @@ -8,9 +7,7 @@ import { createUpdateQuery } from '../sqlite-utils/createUpdateQuery'; import { SqliteClient } from '../SqliteClient'; import type { PreparedQueries, TableRow } from '../types'; -export const updatePollMessage = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const updatePollMessage = async ({ eventType, flush = true, poll, @@ -18,10 +15,10 @@ export const updatePollMessage = async < userID, }: { eventType: string; - poll: PollResponse; + poll: PollResponse; userID: string; flush?: boolean; - poll_vote?: PollVote | PollAnswer; + poll_vote?: PollVote | PollAnswer; }) => { const queries: PreparedQueries[] = []; diff --git a/package/src/store/apis/updateReaction.ts b/package/src/store/apis/updateReaction.ts index b355fbc270..ddd6ad9e50 100644 --- a/package/src/store/apis/updateReaction.ts +++ b/package/src/store/apis/updateReaction.ts @@ -1,4 +1,4 @@ -import type { FormatMessageResponse, MessageResponse, ReactionResponse } from 'stream-chat'; +import type { LocalMessage, MessageResponse, ReactionResponse } from 'stream-chat'; import { mapMessageToStorable } from '../mappers/mapMessageToStorable'; import { mapReactionToStorable } from '../mappers/mapReactionToStorable'; @@ -13,7 +13,7 @@ export const updateReaction = async ({ message, reaction, }: { - message: MessageResponse | FormatMessageResponse; + message: MessageResponse | LocalMessage; reaction: ReactionResponse; flush?: boolean; }) => { diff --git a/package/src/store/apis/upsertChannelDataFromChannel.ts b/package/src/store/apis/upsertChannelDataFromChannel.ts index b22b9ccaad..c65d1ca126 100644 --- a/package/src/store/apis/upsertChannelDataFromChannel.ts +++ b/package/src/store/apis/upsertChannelDataFromChannel.ts @@ -1,17 +1,14 @@ import type { Channel } from 'stream-chat'; -import { DefaultStreamChatGenerics } from '../../types/types'; import { mapChannelToStorable } from '../mappers/mapChannelToStorable'; import { createUpsertQuery } from '../sqlite-utils/createUpsertQuery'; import { SqliteClient } from '../SqliteClient'; -export const upsertChannelDataFromChannel = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const upsertChannelDataFromChannel = async ({ channel, flush = true, }: { - channel: Channel; + channel: Channel; flush?: boolean; }) => { const storableChannel = mapChannelToStorable(channel); diff --git a/package/src/store/apis/utils/convertFilterSortToQuery.ts b/package/src/store/apis/utils/convertFilterSortToQuery.ts index a6a944853f..0190f1bd1a 100644 --- a/package/src/store/apis/utils/convertFilterSortToQuery.ts +++ b/package/src/store/apis/utils/convertFilterSortToQuery.ts @@ -1,14 +1,10 @@ import type { ChannelFilters, ChannelSort } from 'stream-chat'; -import type { DefaultStreamChatGenerics } from '../../../types/types'; - -export const convertFilterSortToQuery = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const convertFilterSortToQuery = ({ filters, sort, }: { - filters?: ChannelFilters; - sort?: ChannelSort; + filters?: ChannelFilters; + sort?: ChannelSort; }) => JSON.stringify(`${filters ? JSON.stringify(filters) : ''}-${sort ? JSON.stringify(sort) : ''}`); diff --git a/package/src/store/mappers/mapChannelDataToStorable.ts b/package/src/store/mappers/mapChannelDataToStorable.ts index 6981917a6d..f254cc1a84 100644 --- a/package/src/store/mappers/mapChannelDataToStorable.ts +++ b/package/src/store/mappers/mapChannelDataToStorable.ts @@ -2,15 +2,9 @@ import type { ChannelResponse } from 'stream-chat'; import { mapDateTimeToStorable } from './mapDateTimeToStorable'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - import type { TableRow } from '../types'; -export const mapChannelDataToStorable = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: ChannelResponse, -): TableRow<'channels'> => { +export const mapChannelDataToStorable = (channel: ChannelResponse): TableRow<'channels'> => { const { auto_translation_enabled, auto_translation_language, diff --git a/package/src/store/mappers/mapChannelToStorable.ts b/package/src/store/mappers/mapChannelToStorable.ts index 9518149a4b..739f2ff4b0 100644 --- a/package/src/store/mappers/mapChannelToStorable.ts +++ b/package/src/store/mappers/mapChannelToStorable.ts @@ -2,15 +2,9 @@ import type { Channel, ChannelResponse } from 'stream-chat'; import { mapDateTimeToStorable } from './mapDateTimeToStorable'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - import type { TableRow } from '../types'; -export const mapChannelToStorable = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, -): TableRow<'channels'> | undefined => { +export const mapChannelToStorable = (channel: Channel): TableRow<'channels'> | undefined => { if (!channel.data) { return; } @@ -40,7 +34,7 @@ export const mapChannelToStorable = < type, updated_at, ...extraData - } = channel.data as unknown as ChannelResponse; + } = channel.data as unknown as ChannelResponse; return { autoTranslationEnabled: auto_translation_enabled, diff --git a/package/src/store/mappers/mapMemberToStorable.ts b/package/src/store/mappers/mapMemberToStorable.ts index eb3e64da76..ed1b492daf 100644 --- a/package/src/store/mappers/mapMemberToStorable.ts +++ b/package/src/store/mappers/mapMemberToStorable.ts @@ -2,17 +2,14 @@ import type { ChannelMemberResponse } from 'stream-chat'; import { mapDateTimeToStorable } from './mapDateTimeToStorable'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import type { TableRow } from '../types'; -export const mapMemberToStorable = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const mapMemberToStorable = ({ cid, member, }: { cid: string; - member: ChannelMemberResponse; + member: ChannelMemberResponse; }): TableRow<'members'> => { const { banned, diff --git a/package/src/store/mappers/mapMessageToStorable.ts b/package/src/store/mappers/mapMessageToStorable.ts index e8f4484eef..74dda7adb9 100644 --- a/package/src/store/mappers/mapMessageToStorable.ts +++ b/package/src/store/mappers/mapMessageToStorable.ts @@ -1,11 +1,11 @@ -import type { FormatMessageResponse, MessageResponse } from 'stream-chat'; +import type { LocalMessage, MessageResponse } from 'stream-chat'; import { mapDateTimeToStorable } from './mapDateTimeToStorable'; import type { TableRow } from '../types'; export const mapMessageToStorable = ( - message: MessageResponse | FormatMessageResponse, + message: MessageResponse | LocalMessage, ): TableRow<'messages'> => { const { attachments, diff --git a/package/src/store/mappers/mapReadToStorable.ts b/package/src/store/mappers/mapReadToStorable.ts index 4b989ee611..0e261be04d 100644 --- a/package/src/store/mappers/mapReadToStorable.ts +++ b/package/src/store/mappers/mapReadToStorable.ts @@ -2,17 +2,14 @@ import type { ReadResponse } from 'stream-chat'; import { mapDateTimeToStorable } from './mapDateTimeToStorable'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import type { TableRow } from '../types'; -export const mapReadToStorable = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const mapReadToStorable = ({ cid, read, }: { cid: string; - read: ReadResponse; + read: ReadResponse; }): TableRow<'reads'> => { const { last_read, unread_messages, user } = read; diff --git a/package/src/store/mappers/mapStorableToChannel.ts b/package/src/store/mappers/mapStorableToChannel.ts index 9911d23f3a..b60078fea0 100644 --- a/package/src/store/mappers/mapStorableToChannel.ts +++ b/package/src/store/mappers/mapStorableToChannel.ts @@ -1,16 +1,10 @@ import type { ChannelAPIResponse } from 'stream-chat'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import type { TableRow } from '../types'; -export const mapStorableToChannel = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( +export const mapStorableToChannel = ( channelRow: TableRow<'channels'>, -): Omit< - ChannelAPIResponse, - 'duration' | 'messages' | 'members' | 'pinned_messages' -> => { +): Omit => { const { autoTranslationEnabled, autoTranslationLanguage, diff --git a/package/src/store/mappers/mapStorableToMember.ts b/package/src/store/mappers/mapStorableToMember.ts index a093f6a3a0..ee5233547f 100644 --- a/package/src/store/mappers/mapStorableToMember.ts +++ b/package/src/store/mappers/mapStorableToMember.ts @@ -2,14 +2,11 @@ import type { ChannelMemberResponse } from 'stream-chat'; import { mapStorableToUser } from './mapStorableToUser'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import type { TableRowJoinedUser } from '../types'; -export const mapStorableToMember = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( +export const mapStorableToMember = ( memberRow: TableRowJoinedUser<'members'>, -): ChannelMemberResponse => { +): ChannelMemberResponse => { const { banned, channelRole, diff --git a/package/src/store/mappers/mapStorableToMessage.ts b/package/src/store/mappers/mapStorableToMessage.ts index 45d34d1734..f94cae17f2 100644 --- a/package/src/store/mappers/mapStorableToMessage.ts +++ b/package/src/store/mappers/mapStorableToMessage.ts @@ -5,13 +5,9 @@ import { mapStorableToReaction } from './mapStorableToReaction'; import { mapStorableToUser } from './mapStorableToUser'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - import type { TableRow, TableRowJoinedUser } from '../types'; -export const mapStorableToMessage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const mapStorableToMessage = ({ currentUserId, messageRow, pollRow, @@ -21,7 +17,7 @@ export const mapStorableToMessage = < messageRow: TableRowJoinedUser<'messages'>; pollRow: TableRow<'poll'>; reactionRows: TableRowJoinedUser<'reactions'>[]; -}): MessageResponse => { +}): MessageResponse => { const { createdAt, deletedAt, @@ -33,8 +29,7 @@ export const mapStorableToMessage = < user, ...rest } = messageRow; - const latestReactions = - reactionRows?.map((reaction) => mapStorableToReaction(reaction)) || []; + const latestReactions = reactionRows?.map((reaction) => mapStorableToReaction(reaction)) || []; const ownReactions = latestReactions.filter((reaction) => reaction.user?.id === currentUserId); diff --git a/package/src/store/mappers/mapStorableToPoll.ts b/package/src/store/mappers/mapStorableToPoll.ts index ffeb1ad2de..63a8eb7fc0 100644 --- a/package/src/store/mappers/mapStorableToPoll.ts +++ b/package/src/store/mappers/mapStorableToPoll.ts @@ -1,13 +1,8 @@ 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 => { +export const mapStorableToPoll = (pollRow: TableRow<'poll'>): PollResponse => { const { allow_answers, allow_user_suggested_options, diff --git a/package/src/store/mappers/mapStorableToReaction.ts b/package/src/store/mappers/mapStorableToReaction.ts index d0bf113176..6e3166c41a 100644 --- a/package/src/store/mappers/mapStorableToReaction.ts +++ b/package/src/store/mappers/mapStorableToReaction.ts @@ -2,15 +2,11 @@ import type { ReactionResponse } from 'stream-chat'; import { mapStorableToUser } from './mapStorableToUser'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - import type { TableRowJoinedUser } from '../types'; -export const mapStorableToReaction = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( +export const mapStorableToReaction = ( reactionRow: TableRowJoinedUser<'reactions'>, -): ReactionResponse => { +): ReactionResponse => { const { createdAt, extraData, messageId, score, type, updatedAt, user } = reactionRow; return { diff --git a/package/src/store/mappers/mapStorableToRead.ts b/package/src/store/mappers/mapStorableToRead.ts index cd336e468d..448aecea5b 100644 --- a/package/src/store/mappers/mapStorableToRead.ts +++ b/package/src/store/mappers/mapStorableToRead.ts @@ -2,15 +2,9 @@ import type { ReadResponse } from 'stream-chat'; import { mapStorableToUser } from './mapStorableToUser'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - import type { TableRowJoinedUser } from '../types'; -export const mapStorableToRead = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - row: TableRowJoinedUser<'reads'>, -): ReadResponse => { +export const mapStorableToRead = (row: TableRowJoinedUser<'reads'>): ReadResponse => { const { lastRead, unreadMessages, user } = row; return { diff --git a/package/src/store/mappers/mapStorableToUser.ts b/package/src/store/mappers/mapStorableToUser.ts index f740ce4ffc..1d09732606 100644 --- a/package/src/store/mappers/mapStorableToUser.ts +++ b/package/src/store/mappers/mapStorableToUser.ts @@ -1,13 +1,8 @@ import type { UserResponse } from 'stream-chat'; -import type { DefaultStreamChatGenerics } from '../../types/types'; import type { TableRow } from '../types'; -export const mapStorableToUser = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - userRow: TableRow<'users'>, -): UserResponse => { +export const mapStorableToUser = (userRow: TableRow<'users'>): UserResponse => { const { banned, createdAt, extraData, id, lastActive, online, role, updatedAt } = userRow; return { diff --git a/package/src/types/stream-chat-common-custom-data.d.ts b/package/src/types/stream-chat-common-custom-data.d.ts new file mode 100644 index 0000000000..fb4a24ed4e --- /dev/null +++ b/package/src/types/stream-chat-common-custom-data.d.ts @@ -0,0 +1,42 @@ +import 'stream-chat'; +import { + DefaultAttachmentData, + DefaultChannelData, + DefaultCommandData, + DefaultEventData, + DefaultMemberData, + DefaultMessageData, + DefaultPollData, + DefaultPollOptionData, + DefaultReactionData, + DefaultThreadData, + DefaultUserData, +} from './types'; + +declare module 'stream-chat' { + /* eslint-disable @typescript-eslint/no-empty-object-type */ + + interface CustomAttachmentData extends DefaultAttachmentData {} + + interface CustomChannelData extends DefaultChannelData {} + + interface CustomCommandData extends DefaultCommandData {} + + interface CustomEventData extends DefaultEventData {} + + interface CustomMemberData extends DefaultMemberData {} + + interface CustomUserData extends DefaultUserData {} + + interface CustomMessageData extends DefaultMessageData {} + + interface CustomPollOptionData extends DefaultPollOptionData {} + + interface CustomPollData extends DefaultPollData {} + + interface CustomReactionData extends DefaultReactionData {} + + interface CustomThreadData extends DefaultThreadData {} + + /* eslint-enable @typescript-eslint/no-empty-object-type */ +} diff --git a/package/src/types/types.ts b/package/src/types/types.ts index 6d59e3f8c4..9e3057ef4b 100644 --- a/package/src/types/types.ts +++ b/package/src/types/types.ts @@ -1,10 +1,4 @@ -import type { - ChannelFilters, - ChannelSort, - ChannelState, - ExtendableGenerics, - LiteralStringForUnion, -} from 'stream-chat'; +import type { ChannelFilters, ChannelSort, ChannelState, FileReference } from 'stream-chat'; import type { FileStateValue } from '../utils/utils'; @@ -18,106 +12,92 @@ export enum FileTypes { VoiceRecording = 'voiceRecording', } -export type Asset = { - duration: number; - height: number; - name: string; - source: 'camera' | 'picker'; - type: string; - uri: string; - width: number; - id?: string; - mimeType?: string; - originalUri?: string; - size?: number; -}; - -export type File = { - name: string; - duration?: number; - id?: string; - mimeType?: string; - originalUri?: string; - size?: number; - type?: FileTypes; - // The uri should be of type `string`. But is `string|undefined` because the same type is used for the response from Stream's Attachment. This shall be fixed. - uri?: string; - waveform_data?: number[]; -}; +export type File = FileReference; +/** + * This is nothing but a substitute for the attachment type prior to sending the message. + * This will change if we unify the file uploads to attachments. + */ export type FileUpload = { file: File; id: string; state: FileStateValue; - duration?: number; - paused?: boolean; - progress?: number; - thumb_url?: string; - type?: string; + + mime_type?: string; + + type?: FileTypes; url?: string; + + thumb_url?: string; + + duration?: number; waveform_data?: number[]; -}; -export type ImageUpload = { - file: Partial; - id: string; - state: FileStateValue; height?: number; - url?: string; width?: number; }; -export type DefaultAttachmentType = UnknownType & { +export type AudioUpload = FileUpload & { + progress?: number; + paused?: boolean; +}; + +export interface DefaultAttachmentData { duration?: number; file_size?: number; mime_type?: string; originalFile?: File; - originalImage?: Partial; + originalImage?: File; waveform_data?: number[]; -}; - -export type Reaction = { - id: string; - name: string; - type: string; - image?: string; -}; +} -interface DefaultUserType extends UnknownType { +export interface DefaultUserData { image?: string; } -interface DefaultChannelType extends UnknownType { - [key: string]: unknown; - +export interface DefaultChannelData { image?: string; + name?: string; } -export interface DefaultStreamChatGenerics extends ExtendableGenerics { - attachmentType: DefaultAttachmentType; - channelType: DefaultChannelType; - commandType: LiteralStringForUnion; - eventType: UnknownType; - memberType: UnknownType; - messageType: UnknownType; - reactionType: UnknownType; - userType: DefaultUserType; +export interface DefaultCommandData { + flag: unknown; + imgur: unknown; } -export type ChannelListEventListenerOptions< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - filters?: ChannelFilters; - sort?: ChannelSort; +/* eslint-disable @typescript-eslint/no-empty-object-type */ + +export interface DefaultEventData {} + +export interface DefaultMemberData {} + +export interface DefaultMessageData {} + +export interface DefaultPollOptionData {} + +export interface DefaultPollData {} + +export interface DefaultReactionData {} + +export interface DefaultThreadData {} + +export type Reaction = { + id: string; + name: string; + type: string; + image?: string; +}; + +export type ChannelListEventListenerOptions = { + filters?: ChannelFilters; + sort?: ChannelSort; }; export type UnknownType = Record; export type ValueOf = T[keyof T]; -export type ChannelUnreadState< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = Omit['read']>, 'user'>; +export type ChannelUnreadState = Omit, 'user'>; // ASYNC AUDIO EXPO: export enum AndroidOutputFormat { diff --git a/package/src/utils/ACITriggerSettings.ts b/package/src/utils/ACITriggerSettings.ts index 14ace35e3e..610a7af945 100644 --- a/package/src/utils/ACITriggerSettings.ts +++ b/package/src/utils/ACITriggerSettings.ts @@ -15,13 +15,8 @@ import type { SuggestionUser, } from '../contexts/suggestionsContext/SuggestionsContext'; import { Emoji } from '../emoji-data'; -import type { DefaultStreamChatGenerics } from '../types/types'; -const getCommands = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, -) => channel.getConfig()?.commands || []; +const getCommands = (channel: Channel) => channel.getConfig()?.commands || []; export type TriggerSettingsOutputType = { caretPosition: string; @@ -29,22 +24,17 @@ export type TriggerSettingsOutputType = { text: string; }; -export type TriggerSettings< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { +export type TriggerSettings = { '/'?: { dataProvider: ( - query: CommandResponse['name'], + query: CommandResponse['name'], text: string, - onReady?: ( - data: CommandResponse[], - q: CommandResponse['name'], - ) => void, + onReady?: (data: CommandResponse[], q: CommandResponse['name']) => void, options?: { limit?: number; }, - ) => SuggestionCommand[]; - output: (entity: CommandResponse) => TriggerSettingsOutputType; + ) => SuggestionCommand[]; + output: (entity: CommandResponse) => TriggerSettingsOutputType; type: SuggestionComponentType; }; ':'?: { @@ -57,31 +47,26 @@ export type TriggerSettings< type: SuggestionComponentType; }; '@'?: { - callback: (item: SuggestionUser) => void; + callback: (item: SuggestionUser) => void; dataProvider: ( - query: SuggestionUser['name'], + query: SuggestionUser['name'], _: string, - onReady?: ( - data: SuggestionUser[], - q: SuggestionUser['name'], - ) => void, + onReady?: (data: SuggestionUser[], q: SuggestionUser['name']) => void, options?: { limit?: number; mentionAllAppUsersEnabled?: boolean; - mentionAllAppUsersQuery?: MentionAllAppUsersQuery; + mentionAllAppUsersQuery?: MentionAllAppUsersQuery; }, - ) => SuggestionUser[] | Promise | void; - output: (entity: SuggestionUser) => TriggerSettingsOutputType; + ) => SuggestionUser[] | Promise | void; + output: (entity: SuggestionUser) => TriggerSettingsOutputType; type: SuggestionComponentType; }; }; -export type ACITriggerSettingsParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = { - channel: Channel; - client: StreamChat; - onMentionSelectItem: (item: SuggestionUser) => void; +export type ACITriggerSettingsParams = { + channel: Channel; + client: StreamChat; + onMentionSelectItem: (item: SuggestionUser) => void; emojiSearchIndex?: EmojiSearchIndex; }; @@ -104,14 +89,12 @@ export type Trigger = '/' | '@' | ':'; * previous call without waiting for a1. So in this case, we want to execute onReady, when trailing * end of debounce executes. */ -export const ACITriggerSettings = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const ACITriggerSettings = ({ channel, client, emojiSearchIndex, onMentionSelectItem, -}: ACITriggerSettingsParams): TriggerSettings => ({ +}: ACITriggerSettingsParams): TriggerSettings => ({ '/': { dataProvider: (query, text, onReady, options = {}) => { try { @@ -208,7 +191,7 @@ export const ACITriggerSettings = < return []; } if (options?.mentionAllAppUsersEnabled) { - return (queryUsersDebounced as DebouncedFunc>)( + return (queryUsersDebounced as DebouncedFunc)( client, query, (data) => { @@ -257,7 +240,7 @@ export const ACITriggerSettings = < return data; } - return (queryMembersDebounced as DebouncedFunc>)( + return (queryMembersDebounced as DebouncedFunc)( client, channel, query, diff --git a/package/src/utils/DBSyncManager.ts b/package/src/utils/DBSyncManager.ts index b159e88855..d4daffe65b 100644 --- a/package/src/utils/DBSyncManager.ts +++ b/package/src/utils/DBSyncManager.ts @@ -11,7 +11,6 @@ import { deletePendingTask } from '../store/apis/deletePendingTask'; import { getPendingTasks } from '../store/apis/getPendingTasks'; import { SqliteClient } from '../store/SqliteClient'; import type { PendingTask } from '../store/types'; -import type { DefaultStreamChatGenerics } from '../types/types'; /** * DBSyncManager has the responsibility to sync the channel states @@ -104,11 +103,7 @@ export class DBSyncManager { }; }; - static sync = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, - >( - client: StreamChat, - ) => { + static sync = async (client: StreamChat) => { if (!this.client?.user) { return; } @@ -165,20 +160,12 @@ export class DBSyncManager { await this.sync(this.client); }; - static queueTask = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, - >({ - client, - task, - }: { - client: StreamChat; - task: PendingTask; - }) => { + static queueTask = async ({ client, task }: { client: StreamChat; task: PendingTask }) => { const removeFromApi = await addPendingTask(task); let response; try { - response = await this.executeTask({ client, task }); + response = await this.executeTask({ client, task }); } catch (e) { if ((e as AxiosError)?.response?.data?.code === 4) { // Error code 16 - message already exists @@ -193,15 +180,7 @@ export class DBSyncManager { return response; }; - static executeTask = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, - >({ - client, - task, - }: { - client: StreamChat; - task: PendingTask; - }) => { + static executeTask = async ({ client, task }: { client: StreamChat; task: PendingTask }) => { const channel = client.channel(task.channelType, task.channelId); if (task.type === 'send-reaction') { @@ -219,11 +198,7 @@ export class DBSyncManager { throw new Error('Invalid task type'); }; - static executePendingTasks = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, - >( - client: StreamChat, - ) => { + static executePendingTasks = async (client: StreamChat) => { const queue = await getPendingTasks(); for (const task of queue) { if (!task.id) { @@ -231,7 +206,7 @@ export class DBSyncManager { } try { - await this.executeTask({ + await this.executeTask({ client, task, }); diff --git a/package/src/utils/addReactionToLocalState.ts b/package/src/utils/addReactionToLocalState.ts index ae4739ae53..694dc42b21 100644 --- a/package/src/utils/addReactionToLocalState.ts +++ b/package/src/utils/addReactionToLocalState.ts @@ -3,22 +3,18 @@ import type { Channel, ReactionResponse, UserResponse } from 'stream-chat'; import { updateReaction } from '../store/apis'; import { insertReaction } from '../store/apis/insertReaction'; -import type { DefaultStreamChatGenerics } from '../types/types'; - -export const addReactionToLocalState = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const addReactionToLocalState = ({ channel, enforceUniqueReaction, messageId, reactionType, user, }: { - channel: Channel; + channel: Channel; enforceUniqueReaction: boolean; messageId: string; reactionType: string; - user: UserResponse; + user: UserResponse; }) => { const message = channel.state.messages.find(({ id }) => id === messageId); diff --git a/package/src/utils/compressImage.ts b/package/src/utils/compressImage.ts index b3efe1a4ac..ff6b7a24f4 100644 --- a/package/src/utils/compressImage.ts +++ b/package/src/utils/compressImage.ts @@ -1,5 +1,5 @@ import { NativeHandlers } from '../native'; -import type { Asset } from '../types/types'; +import type { File } from '../types/types'; /** * Function to compress and Image and return the compressed Image URI @@ -7,7 +7,7 @@ import type { Asset } from '../types/types'; * @param compressImageQuality * @returns string */ -export const compressedImageURI = async (image: Partial, compressImageQuality?: number) => { +export const compressedImageURI = async (image: File, compressImageQuality?: number) => { const uri = image.uri || ''; /** * We skip compression if: @@ -15,8 +15,7 @@ export const compressedImageURI = async (image: Partial, compressImageQua * - the file has no height/width value to maintain for compression * - the compressImageQuality number is not present or is 1 (meaning no compression) */ - const compressedUri = await (image.source === 'camera' || - !image.height || + const compressedUri = await (!image.height || !image.width || typeof compressImageQuality !== 'number' || compressImageQuality === 1 diff --git a/package/src/utils/i18n/Streami18n.ts b/package/src/utils/i18n/Streami18n.ts index a9215541ca..bd43691c28 100644 --- a/package/src/utils/i18n/Streami18n.ts +++ b/package/src/utils/i18n/Streami18n.ts @@ -49,8 +49,6 @@ import 'dayjs/locale/tr'; */ import 'dayjs/locale/en'; -import type { DefaultStreamChatGenerics } from '../../types/types'; - const defaultNS = 'translation'; const defaultLng = 'en'; @@ -383,7 +381,7 @@ export class Streami18n { translations: { [key: string]: { - [key: string]: Partial | DefaultStreamChatGenerics; + [key: string]: Partial; }; } = { en: { [defaultNS]: enTranslations }, @@ -643,7 +641,7 @@ export class Streami18n { */ registerTranslation( language: string, - translation: Partial | DefaultStreamChatGenerics, + translation: Partial, customDayjsLocale?: Partial, ) { if (!translation) { diff --git a/package/src/utils/queryMembers.ts b/package/src/utils/queryMembers.ts index 7ed2418ab2..19c5723c61 100644 --- a/package/src/utils/queryMembers.ts +++ b/package/src/utils/queryMembers.ts @@ -4,31 +4,18 @@ import type { Channel, ChannelMemberAPIResponse, StreamChat, User } from 'stream import { defaultAutoCompleteSuggestionsLimit } from './constants'; import type { SuggestionUser } from '../contexts/suggestionsContext/SuggestionsContext'; -import type { DefaultStreamChatGenerics } from '../types/types'; -const getMembers = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, -) => { +const getMembers = (channel: Channel) => { const members = channel.state.members; return members ? Object.values(members).map(({ user }) => user) : []; }; -const getWatchers = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, -) => { +const getWatchers = (channel: Channel) => { const watchers = channel.state.watchers; return watchers ? Object.values(watchers) : []; }; -export const getMembersAndWatchers = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - channel: Channel, -) => { +export const getMembersAndWatchers = (channel: Channel) => { const members = getMembers(channel); const watchers = getWatchers(channel); const users = [...members, ...watchers]; @@ -47,32 +34,24 @@ export const getMembersAndWatchers = < return uniqueUsers; }; -const isUserResponse = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - user: SuggestionUser | undefined, -): user is SuggestionUser => - (user as SuggestionUser) !== undefined; +const isUserResponse = (user: SuggestionUser | undefined): user is SuggestionUser => + (user as SuggestionUser) !== undefined; -export type QueryMembersFunction< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = ( - client: StreamChat, - channel: Channel, - query: SuggestionUser['name'], - onReady?: (users: SuggestionUser[]) => void, +export type QueryMembersFunction = ( + client: StreamChat, + channel: Channel, + query: SuggestionUser['name'], + onReady?: (users: SuggestionUser[]) => void, options?: { limit?: number; }, ) => Promise; -const queryMembers = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - client: StreamChat, - channel: Channel, - query: SuggestionUser['name'], - onReady?: (users: SuggestionUser[]) => void, +const queryMembers = async ( + client: StreamChat, + channel: Channel, + query: SuggestionUser['name'], + onReady?: (users: SuggestionUser[]) => void, options: { limit?: number; } = {}, @@ -89,9 +68,9 @@ const queryMembers = async < }, {}, { limit }, - )) as ChannelMemberAPIResponse; + )) as ChannelMemberAPIResponse; - const users: SuggestionUser[] = []; + const users: SuggestionUser[] = []; members .filter((member) => member.user?.id !== client.userID) .forEach((member) => isUserResponse(member.user) && users.push(member.user)); diff --git a/package/src/utils/queryUsers.ts b/package/src/utils/queryUsers.ts index 0bab70b890..0a1048db10 100644 --- a/package/src/utils/queryUsers.ts +++ b/package/src/utils/queryUsers.ts @@ -5,29 +5,24 @@ import { defaultAutoCompleteSuggestionsLimit, defaultMentionAllAppUsersQuery } f import type { MentionAllAppUsersQuery } from '../contexts/messageInputContext/MessageInputContext'; import type { SuggestionUser } from '../contexts/suggestionsContext/SuggestionsContext'; -import type { DefaultStreamChatGenerics } from '../types/types'; -export type QueryUsersFunction< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, -> = ( - client: StreamChat, - query: SuggestionUser['name'], - onReady?: (users: SuggestionUser[]) => void, +export type QueryUsersFunction = ( + client: StreamChat, + query: SuggestionUser['name'], + onReady?: (users: SuggestionUser[]) => void, options?: { limit?: number; - mentionAllAppUsersQuery?: MentionAllAppUsersQuery; + mentionAllAppUsersQuery?: MentionAllAppUsersQuery; }, ) => Promise; -const queryUsers = async < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - client: StreamChat, - query: SuggestionUser['name'], - onReady?: (users: SuggestionUser[]) => void, +const queryUsers = async ( + client: StreamChat, + query: SuggestionUser['name'], + onReady?: (users: SuggestionUser[]) => void, options: { limit?: number; - mentionAllAppUsersQuery?: MentionAllAppUsersQuery; + mentionAllAppUsersQuery?: MentionAllAppUsersQuery; } = {}, ): Promise => { if (!query) { diff --git a/package/src/utils/removeReactionFromLocalState.ts b/package/src/utils/removeReactionFromLocalState.ts index e7ffa754f3..b5cdba3913 100644 --- a/package/src/utils/removeReactionFromLocalState.ts +++ b/package/src/utils/removeReactionFromLocalState.ts @@ -2,20 +2,16 @@ import type { Channel, UserResponse } from 'stream-chat'; import { deleteReaction } from '../store/apis/deleteReaction'; -import type { DefaultStreamChatGenerics } from '../types/types'; - -export const removeReactionFromLocalState = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ +export const removeReactionFromLocalState = ({ channel, messageId, reactionType, user, }: { - channel: Channel; + channel: Channel; messageId: string; reactionType: string; - user: UserResponse; + user: UserResponse; }) => { const message = channel.state.messages.find(({ id }) => id === messageId); if (!message || !channel?.id || !user?.id) { diff --git a/package/src/utils/removeReservedFields.ts b/package/src/utils/removeReservedFields.ts index 1e8d580fbe..f32d59a3e1 100644 --- a/package/src/utils/removeReservedFields.ts +++ b/package/src/utils/removeReservedFields.ts @@ -1,13 +1,8 @@ -import type { MessageResponse } from 'stream-chat'; +import type { LocalMessage, MessageResponse } from 'stream-chat'; -import type { MessageType } from '../components/MessageList/hooks/useMessageList'; -import type { DefaultStreamChatGenerics } from '../types/types'; - -export const removeReservedFields = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - message: MessageType | MessageResponse, -): MessageType | MessageResponse => { +export const removeReservedFields = ( + message: LocalMessage | MessageResponse, +): LocalMessage | MessageResponse => { const retryMessage = { ...message }; const reserved = [ 'cid', @@ -26,7 +21,8 @@ export const removeReservedFields = < 'type', 'updated_at', 'reply_count', - ]; + ] as (keyof typeof message)[]; + reserved.forEach((key) => { delete retryMessage[key]; }); diff --git a/package/src/utils/utils.ts b/package/src/utils/utils.ts index 2da02867d3..5ec4c605f0 100644 --- a/package/src/utils/utils.ts +++ b/package/src/utils/utils.ts @@ -2,14 +2,13 @@ import type React from 'react'; import dayjs from 'dayjs'; import EmojiRegex from 'emoji-regex'; -import type { ChannelState, FormatMessageResponse, MessageResponse } from 'stream-chat'; +import type { ChannelState, LocalMessage, MessageResponse } from 'stream-chat'; import { IconProps } from '../../src/icons/utils/base'; -import { MessageType } from '../components/MessageList/hooks/useMessageList'; import type { EmojiSearchIndex } from '../contexts/messageInputContext/MessageInputContext'; import { compiledEmojis } from '../emoji-data'; import type { TableRowJoinedUser } from '../store/types'; -import type { DefaultStreamChatGenerics, ValueOf } from '../types/types'; +import { FileTypes, ValueOf } from '../types/types'; export type ReactionData = { Icon: React.ComponentType; @@ -75,11 +74,7 @@ export const getIndicatorTypeForFileState = ( * @param message * @returns boolean */ -export const isBlockedMessage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - message: MessageType | TableRowJoinedUser<'messages'>, -) => { +export const isBlockedMessage = (message: LocalMessage | TableRowJoinedUser<'messages'>) => { // The only indicator for the blocked message is its message type is error and that the message text contains "Message was blocked by moderation policies". const pattern = /\bMessage was blocked by moderation policies\b/; return message.type === 'error' && message.text && pattern.test(message.text); @@ -90,11 +85,7 @@ export const isBlockedMessage = < * @param message * @returns boolean */ -export const isBouncedMessage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - message: MessageType, -) => +export const isBouncedMessage = (message: LocalMessage) => (message.type === 'error' && message?.moderation_details?.action === 'MESSAGE_RESPONSE_ACTION_BOUNCE') || message?.moderation?.action === 'bounce'; @@ -104,11 +95,7 @@ export const isBouncedMessage = < * @param message * @returns boolean */ -export const isEditedMessage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - message: MessageType, -) => !!message.message_text_updated_at && !message.ai_generated; +export const isEditedMessage = (message: LocalMessage) => !!message.message_text_updated_at; /** * Default emoji search index for auto complete text input @@ -196,27 +183,22 @@ export const hasOnlyEmojis = (text: string) => { /** * Stringifies a message object - * @param {FormatMessageResponse} message - the message object to be stringified + * @param {LocalMessage} message - the message object to be stringified * @returns {string} The stringified message */ -export const stringifyMessage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->({ - deleted_at, - i18n, - latest_reactions, - reaction_groups, - readBy, - reply_count, - status, - text, - type, - updated_at, -}: - | MessageResponse - | FormatMessageResponse - | MessageType): string => - `${ +export const stringifyMessage = (message: MessageResponse | LocalMessage): string => { + const { + deleted_at, + i18n, + latest_reactions, + reaction_groups, + reply_count, + status, + text, + type, + updated_at, + } = message; + return `${ latest_reactions ? latest_reactions.map(({ type, user }) => `${type}${user?.id}`).join() : '' }${ reaction_groups @@ -227,18 +209,16 @@ export const stringifyMessage = < ) .join() : '' - }${type}${deleted_at}${text}${readBy}${reply_count}${status}${updated_at}${JSON.stringify(i18n)}`; + }${type}${deleted_at}${text}${reply_count}${status}${updated_at}${JSON.stringify(i18n)}`; +}; /** * Reduces a list of messages to strings that are used in useEffect & useMemo * @param {messages} messages - the array of messages to be compared * @returns {string} The mapped message string */ -export const reduceMessagesToString = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - messages: FormatMessageResponse[], -): string => messages.map(stringifyMessage).join(); +export const reduceMessagesToString = (messages: LocalMessage[]): string => + messages.map(stringifyMessage).join(); /** * Utility to get the file name from the path using regex. @@ -254,12 +234,28 @@ export const getFileNameFromPath = (path: string) => { return match ? match[0] : ''; }; +export const getFileTypeFromMimeType = (mimeType: string) => { + const fileType = mimeType.split('/')[0]; + if (fileType === 'image') { + return FileTypes.Image; + } else if (fileType === 'video') { + return FileTypes.Video; + } else if (fileType === 'audio') { + return FileTypes.Audio; + } + return FileTypes.File; +}; + /** * Utility to get the duration label from the duration in seconds. * @param duration number * @returns string */ export const getDurationLabelFromDuration = (duration: number) => { + if (!duration) { + return '00:00'; + } + const ONE_HOUR_IN_SECONDS = 3600; const ONE_HOUR_IN_MILLISECONDS = ONE_HOUR_IN_SECONDS * 1000; let durationLabel = '00:00'; @@ -288,12 +284,7 @@ export function escapeRegExp(text: string) { * @param targetId * @returns number */ -export const findInMessagesById = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - messages: ChannelState['messages'], - targetId: string, -) => { +export const findInMessagesById = (messages: ChannelState['messages'], targetId: string) => { const idx = messages.findIndex((message) => message.id === targetId); return idx; }; @@ -304,10 +295,8 @@ export const findInMessagesById = < * @param targetDate * @returns an object with the index and the message object */ -export const findInMessagesByDate = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - messages: MessageResponse[] | ChannelState['messages'], +export const findInMessagesByDate = ( + messages: MessageResponse[] | ChannelState['messages'], targetDate: Date, ) => { // Binary search diff --git a/package/yarn.lock b/package/yarn.lock index 9b8842a73b..d52740d3f6 100644 --- a/package/yarn.lock +++ b/package/yarn.lock @@ -1056,7 +1056,7 @@ pirates "^4.0.6" source-map-support "^0.5.16" -"@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.23.2", "@babel/runtime@^7.25.0", "@babel/runtime@^7.26.9", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.17.2", "@babel/runtime@^7.23.2", "@babel/runtime@^7.25.0", "@babel/runtime@^7.26.9", "@babel/runtime@^7.8.4": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.9.tgz#aa4c6facc65b9cb3f87d75125ffd47781b475433" integrity sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg== @@ -1995,7 +1995,7 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/jsonwebtoken@~9.0.0": +"@types/jsonwebtoken@^9.0.8": version "9.0.9" resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz#a4c3a446c0ebaaf467a58398382616f416345fb3" integrity sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ== @@ -2093,10 +2093,10 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-10.0.0.tgz#e9c07fe50da0f53dc24970cca94d619ff03f6f6d" integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ== -"@types/ws@^7.4.0": - version "7.4.7" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" - integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== +"@types/ws@^8.5.14": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.0.tgz#8a2ec491d6f0685ceaab9a9b7ff44146236993b5" + integrity sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw== dependencies: "@types/node" "*" @@ -5079,10 +5079,10 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== -isomorphic-ws@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" - integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +isomorphic-ws@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" @@ -5626,7 +5626,7 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonwebtoken@~9.0.0: +jsonwebtoken@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== @@ -5727,7 +5727,7 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -linkifyjs@^4.1.1: +linkifyjs@^4.1.1, linkifyjs@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08" integrity sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw== @@ -7772,20 +7772,20 @@ 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@^8.57.6: - version "8.57.6" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.57.6.tgz#dd3a4a0a024ee44bfa6ae7ca15648e6c9f2e498f" - integrity sha512-+zkC5DtxvdkEBAv7dCzzO4WWosjaM15EjsH3q4xD1nX/yf2qT6BMhPJguM+CUWxwlFh32X0KR+ARtD0ChPqtnA== +stream-chat@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.0.0.tgz#cb22dcb8b7f070c623a13b6b75b212d560534d6c" + integrity sha512-I4+/DEp7dP3WBgRmqHaLswL+Y2fyQkUWJhYBS5zx4bpu1cYM6WEir9HYjToDNuJjltqa/FFIEF/tMPWr7iTc0A== dependencies: - "@babel/runtime" "^7.16.3" - "@types/jsonwebtoken" "~9.0.0" - "@types/ws" "^7.4.0" + "@types/jsonwebtoken" "^9.0.8" + "@types/ws" "^8.5.14" axios "^1.6.0" base64-js "^1.5.1" form-data "^4.0.0" - isomorphic-ws "^4.0.1" - jsonwebtoken "~9.0.0" - ws "^7.5.10" + isomorphic-ws "^5.0.0" + jsonwebtoken "^9.0.2" + linkifyjs "^4.2.0" + ws "^8.18.1" stream-composer@^1.0.2: version "1.0.2" @@ -8590,6 +8590,11 @@ ws@^7, ws@^7.5.10: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== +ws@^8.18.1: + version "8.18.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb" + integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w== + xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" diff --git a/release/next.js b/release/next.js index 8d7eed4a4d..55f91550e9 100644 --- a/release/next.js +++ b/release/next.js @@ -12,7 +12,7 @@ configPromise.then((config) => { name: 'develop', channel: 'beta', prerelease: 'beta', - } + }, ], }).then((result) => { // This logics avoid a overflow of next tags in github by removing the last diff --git a/release/release.config.js b/release/release.config.js index 57400db19a..d8ded1c92e 100644 --- a/release/release.config.js +++ b/release/release.config.js @@ -10,7 +10,7 @@ module.exports = Promise.resolve().then(() => { { preset: 'angular', releaseRules: [ - { type: 'chore', scope: 'deps', release: 'patch' }, + { type: 'chore', release: 'patch' }, { type: 'refactor', release: 'patch' }, ], parserOpts: {