diff --git a/README.md b/README.md index aae1b8561b..b42278100f 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-290%20KB-blue) +![JS Bundle Size](https://img.shields.io/badge/js_bundle_size-291%20KB-blue) diff --git a/examples/ExpoMessaging/app/index.tsx b/examples/ExpoMessaging/app/index.tsx index 14742ae6ea..e484cccbfa 100644 --- a/examples/ExpoMessaging/app/index.tsx +++ b/examples/ExpoMessaging/app/index.tsx @@ -1,5 +1,5 @@ import { Alert, Image, Pressable, StyleSheet, View } from 'react-native'; -import { ChannelList } from 'stream-chat-expo'; +import { ChannelList, SqliteClient } from 'stream-chat-expo'; import { useCallback, useContext, useMemo } from 'react'; import { Stack, useRouter } from 'expo-router'; import { ChannelSort } from 'stream-chat'; @@ -19,7 +19,13 @@ const LogoutButton = () => { const onLogoutHandler = useCallback(() => { Alert.alert('Logout', 'Are you sure you want to logout?', [ { text: 'Cancel', style: 'cancel' }, - { text: 'Logout', onPress: logOut }, + { + text: 'Logout', + onPress: () => { + SqliteClient.resetDB(); + logOut(); + }, + }, ]); }, [logOut]); diff --git a/examples/ExpoMessaging/components/ChatWrapper.tsx b/examples/ExpoMessaging/components/ChatWrapper.tsx index 4054f9edc6..d0bf6eff10 100644 --- a/examples/ExpoMessaging/components/ChatWrapper.tsx +++ b/examples/ExpoMessaging/components/ChatWrapper.tsx @@ -46,7 +46,7 @@ export const ChatWrapper = ({ children }: PropsWithChildren<{}>) => { return ( - + {children} diff --git a/examples/SampleApp/src/utils/messageActions.tsx b/examples/SampleApp/src/utils/messageActions.tsx index e462763e22..7dafc4ff79 100644 --- a/examples/SampleApp/src/utils/messageActions.tsx +++ b/examples/SampleApp/src/utils/messageActions.tsx @@ -2,6 +2,7 @@ import { Alert } from 'react-native'; import { StreamChat } from 'stream-chat'; import { Colors, + Delete, messageActions, MessageActionsParams, Time, @@ -20,7 +21,7 @@ export function channelMessageActions({ t: TranslationContextValue['t']; colors?: typeof Colors; }) { - const { dismissOverlay } = params; + const { dismissOverlay, deleteForMeMessage } = params; const actions = messageActions(params); // We cannot use the useMessageReminder hook here because it is a hook. @@ -88,6 +89,27 @@ export function channelMessageActions({ title: reminder ? 'Remove Reminder' : 'Remind Me', icon: , }); + actions.push({ + action: async () => { + Alert.alert('Delete for me', 'Are you sure you want to delete this message for me?', [ + { + text: 'Cancel', + style: 'cancel', + }, + { + text: 'Delete', + onPress: async () => { + await deleteForMeMessage?.action(); + dismissOverlay(); + }, + style: 'destructive', + }, + ]); + }, + actionType: 'deleteForMe', + icon: , + title: t('Delete for me'), + }); return actions; } diff --git a/examples/TypeScriptMessaging/ios/Podfile.lock b/examples/TypeScriptMessaging/ios/Podfile.lock index 622d093c3b..47c9e973e8 100644 --- a/examples/TypeScriptMessaging/ios/Podfile.lock +++ b/examples/TypeScriptMessaging/ios/Podfile.lock @@ -3248,4 +3248,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 6b7a4b74915b42bfe4ffddaf67cbf5e7a2bfeab3 -COCOAPODS: 1.16.2 +COCOAPODS: 1.14.3 diff --git a/examples/TypeScriptMessaging/yarn.lock b/examples/TypeScriptMessaging/yarn.lock index 46cd8ec3b9..5ae0cf23fe 100644 --- a/examples/TypeScriptMessaging/yarn.lock +++ b/examples/TypeScriptMessaging/yarn.lock @@ -2734,6 +2734,15 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" +axios@^1.12.2: + version "1.12.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.12.2.tgz#6c307390136cf7a2278d09cec63b136dfc6e6da7" + integrity sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.4" + proxy-from-env "^1.1.0" + axios@^1.6.0: version "1.7.9" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a" @@ -7319,14 +7328,14 @@ stream-chat-react-native-core@8.1.0: version "0.0.0" uid "" -stream-chat@^9.17.0: - version "9.17.0" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.17.0.tgz#540cf1ea03b08a394d6140696aae8528e9ba9ce2" - integrity sha512-ys6K73wIVWs5+qsfPJ9wumEUtgbMXYVbH1dhmAZ1oYtQ01dY/avsvt25PYDakVjKeyrnT+y8T/xEzfeF/WDJsg== +stream-chat@^9.23.0: + version "9.24.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.24.0.tgz#e6af5d4b0eb396e24e0ab7f852719581c39f18bc" + integrity sha512-zLtguYRqxeEc/Cjw8Zp00u/wTrqFg4gFPKdj3mvl/Jq1Pt95mY9nMc38KW0GOu/2quIAAar0NNMq8fsXl4jupQ== dependencies: "@types/jsonwebtoken" "^9.0.8" "@types/ws" "^8.5.14" - axios "^1.6.0" + axios "^1.12.2" base64-js "^1.5.1" form-data "^4.0.4" isomorphic-ws "^5.0.0" diff --git a/package/expo-package/src/optionalDependencies/deleteFile.ts b/package/expo-package/src/optionalDependencies/deleteFile.ts index 694503293a..5213f53345 100644 --- a/package/expo-package/src/optionalDependencies/deleteFile.ts +++ b/package/expo-package/src/optionalDependencies/deleteFile.ts @@ -1,7 +1,7 @@ let FileSystem; try { - FileSystem = require('expo-file-system'); + FileSystem = require('expo-file-system/legacy'); } catch (e) { // do nothing } diff --git a/package/expo-package/src/optionalDependencies/pickImage.ts b/package/expo-package/src/optionalDependencies/pickImage.ts index 25ef02f531..280710b4c2 100644 --- a/package/expo-package/src/optionalDependencies/pickImage.ts +++ b/package/expo-package/src/optionalDependencies/pickImage.ts @@ -1,4 +1,5 @@ import { Platform } from 'react-native'; +import { PickImageOptions } from 'stream-chat-react-native-core'; let ImagePicker; try { @@ -14,7 +15,7 @@ if (!ImagePicker) { } export const pickImage = ImagePicker - ? async () => { + ? async ({ maxNumberOfFiles }: PickImageOptions = {}) => { try { let permissionGranted = true; if (Platform.OS === 'ios') { @@ -35,6 +36,7 @@ export const pickImage = ImagePicker allowsMultipleSelection: true, mediaTypes: ['images', 'videos'], preferredAssetRepresentationMode: 'current', + selectionLimit: maxNumberOfFiles, }); const canceled = result.canceled; diff --git a/package/expo-package/src/optionalDependencies/saveFile.ts b/package/expo-package/src/optionalDependencies/saveFile.ts index 3e768d2be6..c4d5070a54 100644 --- a/package/expo-package/src/optionalDependencies/saveFile.ts +++ b/package/expo-package/src/optionalDependencies/saveFile.ts @@ -1,7 +1,7 @@ let FileSystem; try { - FileSystem = require('expo-file-system'); + FileSystem = require('expo-file-system/legacy'); } catch (e) { // do nothing } diff --git a/package/native-package/src/optionalDependencies/pickImage.ts b/package/native-package/src/optionalDependencies/pickImage.ts index 1d04e68753..dc7966dd99 100644 --- a/package/native-package/src/optionalDependencies/pickImage.ts +++ b/package/native-package/src/optionalDependencies/pickImage.ts @@ -1,4 +1,5 @@ import { Platform } from 'react-native'; +import { PickImageOptions } from 'stream-chat-react-native-core'; let ImagePicker; try { @@ -8,11 +9,12 @@ try { } export const pickImage = ImagePicker - ? async () => { + ? async ({ maxNumberOfFiles }: PickImageOptions = {}) => { try { const result = await ImagePicker.launchImageLibrary({ assetRepresentationMode: 'current', mediaType: 'mixed', + selectionLimit: maxNumberOfFiles, }); const canceled = result.didCancel; const errorCode = result.errorCode; diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 9617284455..b2674cdd79 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -9,6 +9,7 @@ import { Channel as ChannelClass, ChannelState, Channel as ChannelType, + DeleteMessageOptions, EventHandler, LocalMessage, localMessageToNewMessagePayload, @@ -259,7 +260,9 @@ const debounceOptions = { }; export type ChannelPropsWithContext = Pick & - Partial> & + Partial< + Pick + > & Partial< Pick< AttachmentPickerProps, @@ -324,6 +327,7 @@ export type ChannelPropsWithContext = Pick & | 'handleBan' | 'handleCopy' | 'handleDelete' + | 'handleDeleteForMe' | 'handleEdit' | 'handleFlag' | 'handleMarkUnread' @@ -556,6 +560,7 @@ const ChannelWithContext = (props: PropsWithChildren) = customMessageSwipeAction, DateHeader = DateHeaderDefault, deletedMessagesVisibilityType = 'always', + disableAttachmentPicker = !isImageMediaLibraryAvailable(), disableKeyboardCompatibleView = false, disableTypingIndicator, dismissKeyboardOnMessageTouch = true, @@ -582,6 +587,7 @@ const ChannelWithContext = (props: PropsWithChildren) = handleBan, handleCopy, handleDelete, + handleDeleteForMe, handleEdit, handleFlag, handleMarkUnread, @@ -1515,7 +1521,15 @@ const ChannelWithContext = (props: PropsWithChildren) = }); const deleteMessage: MessagesContextValue['deleteMessage'] = useStableCallback( - async (message, hardDelete = false) => { + async (message, optionsOrHardDelete = false) => { + let options: DeleteMessageOptions = {}; + if (typeof optionsOrHardDelete === 'boolean') { + options = optionsOrHardDelete ? { hardDelete: true } : {}; + } else if (optionsOrHardDelete?.deleteForMe) { + options = { deleteForMe: true }; + } else if (optionsOrHardDelete?.hardDelete) { + options = { hardDelete: true }; + } if (!channel.id) { throw new Error('Channel has not been initialized yet'); } @@ -1534,7 +1548,7 @@ const ChannelWithContext = (props: PropsWithChildren) = threadInstance?.upsertReplyLocally({ message: updatedMessage }); - const data = await client.deleteMessage(message.id, hardDelete); + const data = await client.deleteMessage(message.id, options); if (data?.message) { updateMessage({ ...data.message }); @@ -1679,10 +1693,11 @@ const ChannelWithContext = (props: PropsWithChildren) = bottomInset, bottomSheetRef, closePicker: () => closePicker(bottomSheetRef), + disableAttachmentPicker, openPicker: () => openPicker(bottomSheetRef), topInset, }), - [bottomInset, bottomSheetRef, closePicker, openPicker, topInset], + [bottomInset, bottomSheetRef, closePicker, openPicker, topInset, disableAttachmentPicker], ); const ownCapabilitiesContext = useCreateOwnCapabilitiesContext({ @@ -1845,6 +1860,7 @@ const ChannelWithContext = (props: PropsWithChildren) = handleBan, handleCopy, handleDelete, + handleDeleteForMe, handleEdit, handleFlag, handleMarkUnread, @@ -1980,7 +1996,7 @@ const ChannelWithContext = (props: PropsWithChildren) = {children} - {isImageMediaLibraryAvailable() && ( + {!disableAttachmentPicker && ( )} diff --git a/package/src/components/Channel/hooks/useCreateMessagesContext.ts b/package/src/components/Channel/hooks/useCreateMessagesContext.ts index 71f3e02795..3ac1e7236d 100644 --- a/package/src/components/Channel/hooks/useCreateMessagesContext.ts +++ b/package/src/components/Channel/hooks/useCreateMessagesContext.ts @@ -33,6 +33,7 @@ export const useCreateMessagesContext = ({ handleBan, handleCopy, handleDelete, + handleDeleteForMe, handleEdit, handleFlag, handleMarkUnread, @@ -150,6 +151,7 @@ export const useCreateMessagesContext = ({ handleBan, handleCopy, handleDelete, + handleDeleteForMe, handleEdit, handleFlag, handleMarkUnread, diff --git a/package/src/components/ImageGallery/ImageGallery.tsx b/package/src/components/ImageGallery/ImageGallery.tsx index 10b2380066..c804c0ffd9 100644 --- a/package/src/components/ImageGallery/ImageGallery.tsx +++ b/package/src/components/ImageGallery/ImageGallery.tsx @@ -378,7 +378,7 @@ export const ImageGallery = (props: Props) => { ); /** - * This transition and scaleX reverse lets use scroll left + * This transition and scaleX reverse lets use scroll right */ const pagerStyle = useAnimatedStyle( () => ({ diff --git a/package/src/components/ImageGallery/hooks/useAnimatedGalleryStyle.tsx b/package/src/components/ImageGallery/hooks/useAnimatedGalleryStyle.tsx index 76d2d683a9..ff9bfce0f9 100644 --- a/package/src/components/ImageGallery/hooks/useAnimatedGalleryStyle.tsx +++ b/package/src/components/ImageGallery/hooks/useAnimatedGalleryStyle.tsx @@ -59,17 +59,17 @@ export const useAnimatedGalleryStyle = ({ { scale: selected ? scale.value / 8 : oneEighth, }, - { scaleX: -1 }, + { scaleX: 1 }, ], }; }, [previous, selected]); const animatedStyles = useAnimatedStyle(() => { - const xScaleOffset = -7 * screenWidth * (0.5 + index); + const xScaleOffset = 7 * screenWidth * (0.5 + index); const yScaleOffset = -screenHeight * 3.5; return { transform: [ - { scaleX: -1 }, + { scaleX: 1 }, { translateY: yScaleOffset }, { translateX: -xScaleOffset, diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx index 1f10124150..6a677616ae 100644 --- a/package/src/components/Message/Message.tsx +++ b/package/src/components/Message/Message.tsx @@ -121,6 +121,7 @@ export type MessagePressableHandlerPayload = PressableHandlerPayload & { export type MessageActionHandlers = { copyMessage: () => void; deleteMessage: () => void; + deleteForMeMessage: () => void; editMessage: () => void; flagMessage: () => void; markUnread: () => Promise; @@ -155,6 +156,7 @@ export type MessagePropsWithContext = Pick< | 'handleBan' | 'handleCopy' | 'handleDelete' + | 'handleDeleteForMe' | 'handleEdit' | 'handleFlag' | 'handleMarkUnread' @@ -229,6 +231,7 @@ const MessageWithContext = (props: MessagePropsWithContext) => { handleBan, handleCopy, handleDelete, + handleDeleteForMe, handleEdit, handleFlag, handleMarkUnread, @@ -487,6 +490,7 @@ const MessageWithContext = (props: MessagePropsWithContext) => { const { handleCopyMessage, handleDeleteMessage, + handleDeleteForMeMessage, handleEditMessage, handleFlagMessage, handleMarkUnreadMessage, @@ -514,6 +518,7 @@ const MessageWithContext = (props: MessagePropsWithContext) => { banUser, copyMessage, deleteMessage, + deleteForMeMessage, editMessage, flagMessage, handleReaction, @@ -534,6 +539,7 @@ const MessageWithContext = (props: MessagePropsWithContext) => { handleBan, handleCopy, handleDelete, + handleDeleteForMe, handleEdit, handleFlag, handleMarkUnread, @@ -565,6 +571,7 @@ const MessageWithContext = (props: MessagePropsWithContext) => { : messageActionsProp({ banUser, copyMessage, + deleteForMeMessage, deleteMessage, dismissOverlay, editMessage, @@ -582,10 +589,12 @@ const MessageWithContext = (props: MessagePropsWithContext) => { showMessageReactions, threadReply, unpinMessage, + updateMessage, }); const actionHandlers: MessageActionHandlers = { copyMessage: handleCopyMessage, + deleteForMeMessage: handleDeleteForMeMessage, deleteMessage: handleDeleteMessage, editMessage: handleEditMessage, flagMessage: handleFlagMessage, diff --git a/package/src/components/Message/hooks/useMessageActionHandlers.ts b/package/src/components/Message/hooks/useMessageActionHandlers.ts index 3f835dca3c..872cd5e6eb 100644 --- a/package/src/components/Message/hooks/useMessageActionHandlers.ts +++ b/package/src/components/Message/hooks/useMessageActionHandlers.ts @@ -66,6 +66,14 @@ export const useMessageActionHandlers = ({ ); }; + const handleDeleteForMeMessage = async () => { + if (!message.id) { + return; + } + + await deleteMessage(message, { deleteForMe: true }); + }; + const handleToggleMuteUser = async () => { if (!message.user?.id) { return; @@ -182,6 +190,7 @@ export const useMessageActionHandlers = ({ return { handleCopyMessage, + handleDeleteForMeMessage, handleDeleteMessage, handleEditMessage, handleFlagMessage, diff --git a/package/src/components/Message/hooks/useMessageActions.tsx b/package/src/components/Message/hooks/useMessageActions.tsx index 7f45b451ec..9f307f56fd 100644 --- a/package/src/components/Message/hooks/useMessageActions.tsx +++ b/package/src/components/Message/hooks/useMessageActions.tsx @@ -39,6 +39,7 @@ export type MessageActionsHookProps = Pick< | 'handleBan' | 'handleCopy' | 'handleDelete' + | 'handleDeleteForMe' | 'handleEdit' | 'handleFlag' | 'handleQuotedReply' @@ -73,6 +74,7 @@ export const useMessageActions = ({ handleBan, handleCopy, handleDelete, + handleDeleteForMe, handleEdit, handleFlag, handleMarkUnread, @@ -101,6 +103,7 @@ export const useMessageActions = ({ const { handleCopyMessage, handleDeleteMessage, + handleDeleteForMeMessage, handleEditMessage, handleFlagMessage, handleMarkUnreadMessage, @@ -182,6 +185,19 @@ export const useMessageActions = ({ titleStyle: { color: accent_red }, }; + const deleteForMeMessage: MessageActionType = { + action: () => { + dismissOverlay(); + if (handleDeleteForMe) { + handleDeleteForMe(message); + } + handleDeleteForMeMessage(); + }, + actionType: 'deleteForMeMessage', + icon: , + title: t('Delete for me'), + }; + const editMessage: MessageActionType = { action: () => { dismissOverlay(); @@ -319,6 +335,7 @@ export const useMessageActions = ({ return { banUser, copyMessage, + deleteForMeMessage, deleteMessage, editMessage, flagMessage, diff --git a/package/src/components/Message/utils/messageActions.ts b/package/src/components/Message/utils/messageActions.ts index 8d019238b9..d7973cc2db 100644 --- a/package/src/components/Message/utils/messageActions.ts +++ b/package/src/components/Message/utils/messageActions.ts @@ -1,4 +1,5 @@ import type { MessageContextValue } from '../../../contexts/messageContext/MessageContext'; +import type { MessagesContextValue } from '../../../contexts/messagesContext/MessagesContext'; import type { OwnCapabilitiesContextValue } from '../../../contexts/ownCapabilitiesContext/OwnCapabilitiesContext'; import { isClipboardAvailable } from '../../../native'; @@ -25,7 +26,10 @@ export type MessageActionsParams = { showMessageReactions: boolean; threadReply: MessageActionType; unpinMessage: MessageActionType; -} & Pick; + // Optional Actions + deleteForMeMessage?: MessageActionType; +} & Pick & + Pick; export type MessageActionsProp = (param: MessageActionsParams) => MessageActionType[]; diff --git a/package/src/components/MessageInput/AttachButton.tsx b/package/src/components/MessageInput/AttachButton.tsx index a6e63e0f5c..a5a36efc96 100644 --- a/package/src/components/MessageInput/AttachButton.tsx +++ b/package/src/components/MessageInput/AttachButton.tsx @@ -4,7 +4,10 @@ import { Pressable } from 'react-native'; import { NativeAttachmentPicker } from './components/NativeAttachmentPicker'; -import { useAttachmentPickerContext } from '../../contexts/attachmentPickerContext/AttachmentPickerContext'; +import { + AttachmentPickerContextValue, + useAttachmentPickerContext, +} from '../../contexts/attachmentPickerContext/AttachmentPickerContext'; import { MessageInputContextValue, useMessageInputContext, @@ -12,22 +15,21 @@ import { import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { Attach } from '../../icons/Attach'; -import { isImageMediaLibraryAvailable } from '../../native'; - type AttachButtonPropsWithContext = Pick< MessageInputContextValue, 'handleAttachButtonPress' | 'toggleAttachmentPicker' -> & { - disabled?: boolean; - /** Function that opens attachment options bottom sheet */ - handleOnPress?: ((event: GestureResponderEvent) => void) & (() => void); - selectedPicker?: 'images'; -}; +> & + Pick & { + disabled?: boolean; + /** Function that opens attachment options bottom sheet */ + handleOnPress?: ((event: GestureResponderEvent) => void) & (() => void); + }; const AttachButtonWithContext = (props: AttachButtonPropsWithContext) => { const [showAttachButtonPicker, setShowAttachButtonPicker] = useState(false); const [attachButtonLayoutRectangle, setAttachButtonLayoutRectangle] = useState(); const { + disableAttachmentPicker, disabled = false, handleAttachButtonPress, handleOnPress, @@ -73,7 +75,7 @@ const AttachButtonWithContext = (props: AttachButtonPropsWithContext) => { handleAttachButtonPress(); return; } - if (isImageMediaLibraryAvailable()) { + if (!disableAttachmentPicker) { toggleAttachmentPicker(); } else { attachButtonHandler(); @@ -132,12 +134,17 @@ export type AttachButtonProps = Partial; * UI Component for attach button in MessageInput component. */ export const AttachButton = (props: AttachButtonProps) => { - const { selectedPicker } = useAttachmentPickerContext(); + const { disableAttachmentPicker, selectedPicker } = useAttachmentPickerContext(); const { handleAttachButtonPress, toggleAttachmentPicker } = useMessageInputContext(); return ( ); diff --git a/package/src/components/MessageInput/MessageInput.tsx b/package/src/components/MessageInput/MessageInput.tsx index 2df5981793..924828ff08 100644 --- a/package/src/components/MessageInput/MessageInput.tsx +++ b/package/src/components/MessageInput/MessageInput.tsx @@ -62,11 +62,7 @@ import { } from '../../contexts/translationContext/TranslationContext'; import { useStateStore } from '../../hooks/useStateStore'; -import { - isAudioRecorderAvailable, - isImageMediaLibraryAvailable, - NativeHandlers, -} from '../../native'; +import { isAudioRecorderAvailable, NativeHandlers } from '../../native'; import { AIStates, useAIState } from '../AITypingIndicatorView'; import { AutoCompleteInput } from '../AutoCompleteInput/AutoCompleteInput'; import { CreatePoll } from '../Poll/CreatePollContent'; @@ -112,7 +108,7 @@ const styles = StyleSheet.create({ type MessageInputPropsWithContext = Pick< AttachmentPickerContextValue, - 'bottomInset' | 'selectedPicker' + 'bottomInset' | 'disableAttachmentPicker' | 'selectedPicker' > & Pick & Pick & @@ -207,6 +203,7 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { cooldownEndsAt, CooldownTimer, CreatePollContent, + disableAttachmentPicker, editing, Input, inputBoxRef, @@ -564,7 +561,7 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { - {isImageMediaLibraryAvailable() && selectedPicker ? ( + {!disableAttachmentPicker && selectedPicker ? ( { uploadNewFile, VideoRecorderSelectorIcon, } = useMessageInputContext(); - const { bottomInset, bottomSheetRef, selectedPicker } = useAttachmentPickerContext(); + const { bottomInset, bottomSheetRef, disableAttachmentPicker, selectedPicker } = + useAttachmentPickerContext(); const messageComposer = useMessageComposer(); const editing = !!messageComposer.editedMessage; const { clearEditingState } = useMessageComposerAPIContext(); @@ -835,6 +833,7 @@ export const MessageInput = (props: MessageInputProps) => { CooldownTimer, CreatePollContent, CreatePollIcon, + disableAttachmentPicker, editing, FileSelectorIcon, ImageSelectorIcon, diff --git a/package/src/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.tsx b/package/src/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.tsx index e058876ee2..7c29cc5715 100644 --- a/package/src/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.tsx +++ b/package/src/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.tsx @@ -49,6 +49,10 @@ export const ImageAttachmentUploadPreview = ({ setLoading(false); }, []); + const onErrorHandler = useCallback(() => { + setLoading(false); + }, []); + return ( ; - deleteMessage: (message: LocalMessage, hardDelete?: boolean) => Promise; + // FIXME: Remove the signature with optionsOrHardDelete boolean with the next major release + deleteMessage: ( + message: LocalMessage, + optionsOrHardDelete?: boolean | DeleteMessageOptions, + ) => Promise; deleteReaction: (type: string, messageId: string) => Promise; /** Should keyboard be dismissed when messaged is touched */ @@ -404,6 +409,8 @@ export type MessagesContextValue = Pick Promise; /** Handler to access when a copy message action is invoked */ handleCopy?: (message: LocalMessage) => Promise; + /** Handler to access when a delete for me message action is invoked */ + handleDeleteForMe?: (message: LocalMessage) => Promise; /** Handler to access when a delete message action is invoked */ handleDelete?: (message: LocalMessage) => Promise; /** Handler to access when an edit message action is invoked */ diff --git a/package/src/i18n/en.json b/package/src/i18n/en.json index 3e9465eda0..9837a87b65 100644 --- a/package/src/i18n/en.json +++ b/package/src/i18n/en.json @@ -23,6 +23,7 @@ "Create Poll": "Create Poll", "Delete": "Delete", "Delete Message": "Delete Message", + "Delete for me": "Delete for me", "Device camera is used to take photos or videos.": "Device camera is used to take photos or videos.", "Device gallery permissions is used to take photos or videos.": "Device gallery permissions is used to take photos or videos.", "Do you want to send a copy of this message to a moderator for further investigation?": "Do you want to send a copy of this message to a moderator for further investigation?", diff --git a/package/src/i18n/es.json b/package/src/i18n/es.json index 74657b6a1b..4daf4147d7 100644 --- a/package/src/i18n/es.json +++ b/package/src/i18n/es.json @@ -23,6 +23,7 @@ "Create Poll": "Crear encuesta", "Delete": "Eliminar", "Delete Message": "Eliminar mensaje", + "Delete for me": "Eliminar para mí", "Device camera is used to take photos or videos.": "La cámara del dispositivo se utiliza para tomar fotografías o vídeos.", "Device gallery permissions is used to take photos or videos.": "Los permisos de la galería del dispositivo se utilizan para tomar fotos o videos.", "Do you want to send a copy of this message to a moderator for further investigation?": "¿Deseas enviar una copia de este mensaje a un moderador para una investigación adicional?", diff --git a/package/src/i18n/fr.json b/package/src/i18n/fr.json index 7ac6cfe1ea..3227ab9290 100644 --- a/package/src/i18n/fr.json +++ b/package/src/i18n/fr.json @@ -23,6 +23,7 @@ "Create Poll": "Créer un sondage", "Delete": "Supprimer", "Delete Message": "Supprimer un message", + "Delete for me": "Supprimer pour moi", "Device camera is used to take photos or videos.": "L'appareil photo de l'appareil est utilisé pour prendre des photos ou des vidéos.", "Device gallery permissions is used to take photos or videos.": "Les autorisations de la galerie de l'appareil sont utilisées pour prendre des photos ou des vidéos.", "Do you want to send a copy of this message to a moderator for further investigation?": "Voulez-vous envoyer une copie de ce message à un modérateur pour une enquête plus approfondie?", diff --git a/package/src/i18n/he.json b/package/src/i18n/he.json index 2e913471ee..376f6294eb 100644 --- a/package/src/i18n/he.json +++ b/package/src/i18n/he.json @@ -23,6 +23,7 @@ "Create Poll": "צור סקר", "Delete": "מחק", "Delete Message": "מחק/י הודעה", + "Delete for me": "מחק עבורי", "Device camera is used to take photos or videos.": "מצלמת המכשיר משמשת לצילום תמונות או סרטונים.", "Device gallery permissions is used to take photos or videos.": "הרשאות גלריית המכשיר משמשות לצילום תמונות או סרטונים.", "Do you want to send a copy of this message to a moderator for further investigation?": "האם את/ה רוצה לשלוח עותק של הודעה זו למנחה להמשך חקירה?", diff --git a/package/src/i18n/hi.json b/package/src/i18n/hi.json index 5f2b5bcb4a..2ea6b2a80d 100644 --- a/package/src/i18n/hi.json +++ b/package/src/i18n/hi.json @@ -23,6 +23,7 @@ "Create Poll": "सर्वेक्षण बनाएं", "Delete": "हटाएं", "Delete Message": "मैसेज को डिलीट करे", + "Delete for me": "मुझे हटाएं", "Device camera is used to take photos or videos.": "डिवाइस कैमरे का उपयोग फ़ोटो या वीडियो लेने के लिए किया जाता है।", "Device gallery permissions is used to take photos or videos.": "डिवाइस गैलरी की अनुमतियों का उपयोग फोटो या वीडियो लेने के लिए किया जाता है।", "Do you want to send a copy of this message to a moderator for further investigation?": "क्या आप इस संदेश की एक प्रति आगे की जाँच के लिए किसी मॉडरेटर को भेजना चाहते हैं?", diff --git a/package/src/i18n/it.json b/package/src/i18n/it.json index 728d8a44d5..e89c76d4ba 100644 --- a/package/src/i18n/it.json +++ b/package/src/i18n/it.json @@ -23,6 +23,7 @@ "Create Poll": "Crea sondaggio", "Delete": "Elimina", "Delete Message": "Cancella il Messaggio", + "Delete for me": "Elimina per me", "Device camera is used to take photos or videos.": "La fotocamera del dispositivo viene utilizzata per scattare foto o video.", "Device gallery permissions is used to take photos or videos.": "Le autorizzazioni della galleria del dispositivo vengono utilizzate per scattare foto o video.", "Do you want to send a copy of this message to a moderator for further investigation?": "Vuoi inviare una copia di questo messaggio a un moderatore per ulteriori indagini?", diff --git a/package/src/i18n/ja.json b/package/src/i18n/ja.json index 099a53591d..4394088f31 100644 --- a/package/src/i18n/ja.json +++ b/package/src/i18n/ja.json @@ -23,6 +23,7 @@ "Create Poll": "アンケートを作成", "Delete": "消去", "Delete Message": "メッセージを削除", + "Delete for me": "自分で削除", "Device camera is used to take photos or videos.": "デバイスのカメラは写真やビデオの撮影に使用されます。", "Device gallery permissions is used to take photos or videos.": "デバイスギャラリーの権限は写真やビデオを撮るために使用されます。", "Do you want to send a copy of this message to a moderator for further investigation?": "このメッセージのコピーをモデレーターに送信して、さらに調査しますか?", diff --git a/package/src/i18n/ko.json b/package/src/i18n/ko.json index c297cbd67b..a9d61ef72a 100644 --- a/package/src/i18n/ko.json +++ b/package/src/i18n/ko.json @@ -23,6 +23,7 @@ "Create Poll": "투표 생성", "Delete": "삭제", "Delete Message": "메시지 삭제", + "Delete for me": "나 삭제", "Device camera is used to take photos or videos.": "기기 카메라는 사진이나 동영상을 촬영하는 데 사용됩니다.", "Device gallery permissions is used to take photos or videos.": "장치 갤러리 권한은 사진 또는 비디오를 촬영하는 데 사용됩니다.", "Do you want to send a copy of this message to a moderator for further investigation?": "이 메시지의 복사본을 운영자에게 보내 추가 조사를합니까?", diff --git a/package/src/i18n/nl.json b/package/src/i18n/nl.json index 0c68500e11..eada070bd6 100644 --- a/package/src/i18n/nl.json +++ b/package/src/i18n/nl.json @@ -23,6 +23,7 @@ "Create Poll": "Peiling aanmaken", "Delete": "Verwijderen", "Delete Message": "Verwijder bericht", + "Delete for me": "Verwijder voor mij", "Device camera is used to take photos or videos.": "De camera van het apparaat wordt gebruikt om foto's of video's te maken.", "Device gallery permissions is used to take photos or videos.": "Apparaatgallerijmachtigingen worden gebruikt om foto’s of video’s te maken.", "Do you want to send a copy of this message to a moderator for further investigation?": "Wil je een kopie van dit bericht naar een moderator sturen voor verder onderzoek?", diff --git a/package/src/i18n/pt-br.json b/package/src/i18n/pt-br.json index d8e975f378..4fb3d21991 100644 --- a/package/src/i18n/pt-br.json +++ b/package/src/i18n/pt-br.json @@ -23,6 +23,7 @@ "Create Poll": "Criar enquete", "Delete": "Excluir", "Delete Message": "Excluir Mensagem", + "Delete for me": "Excluir para mim", "Device camera is used to take photos or videos.": "A câmera do dispositivo é usada para tirar fotos ou vídeos.", "Device gallery permissions is used to take photos or videos.": "As permissões da galeria do dispositivo são usadas para tirar fotos ou vídeos.", "Do you want to send a copy of this message to a moderator for further investigation?": "Deseja enviar uma cópia desta mensagem para um moderador para investigação adicional?", diff --git a/package/src/i18n/ru.json b/package/src/i18n/ru.json index ea29cf9806..5f4a81f7e5 100644 --- a/package/src/i18n/ru.json +++ b/package/src/i18n/ru.json @@ -24,6 +24,7 @@ "Create Poll": "Создать опрос", "Delete": "удалять", "Delete Message": "Удалить сообщение", + "Delete for me": "Удалить для себя", "Device camera is used to take photos or videos.": "Камера устройства используется для съемки фотографий или видео.", "Device gallery permissions is used to take photos or videos.": "Разрешения галереи устройства используются для съемки фото или видео.", "Do you want to send a copy of this message to a moderator for further investigation?": "Вы хотите отправить копию этого сообщения модератору для дальнейшего изучения?", diff --git a/package/src/i18n/tr.json b/package/src/i18n/tr.json index 3305b84b6a..6f2b955938 100644 --- a/package/src/i18n/tr.json +++ b/package/src/i18n/tr.json @@ -23,6 +23,7 @@ "Create Poll": "Anket oluştur", "Delete": "Sil", "Delete Message": "Mesajı Sil", + "Delete for me": "Benim için sil", "Device camera is used to take photos or videos.": "Cihaz kamerası fotoğraf veya video çekmek için kullanılır.", "Device gallery permissions is used to take photos or videos.": "Cihaz galerisi izinleri fotoğraf veya video çekmek için kullanılır.", "Do you want to send a copy of this message to a moderator for further investigation?": "Detaylı inceleme için bu mesajın kopyasını moderatöre göndermek istiyor musunuz?", diff --git a/package/src/native.ts b/package/src/native.ts index 7debba4ecc..f20d52b09b 100644 --- a/package/src/native.ts +++ b/package/src/native.ts @@ -50,7 +50,11 @@ type PickImageAssetType = { cancelled?: boolean; }; -type PickImage = () => Promise | never; +export type PickImageOptions = { + maxNumberOfFiles?: number; +}; + +type PickImage = (options?: PickImageOptions) => Promise | never; type SaveFileOptions = { fileName: string; diff --git a/package/src/store/SqliteClient.ts b/package/src/store/SqliteClient.ts index 74380960c6..103eaa25e0 100644 --- a/package/src/store/SqliteClient.ts +++ b/package/src/store/SqliteClient.ts @@ -28,7 +28,7 @@ import type { PreparedBatchQueries, PreparedQueries, Scalar, Table } from './typ * This way usage @op-engineering/op-sqlite package is scoped to a single class/file. */ export class SqliteClient { - static dbVersion = 13; + static dbVersion = 14; static dbName = DB_NAME; static dbLocation = DB_LOCATION; diff --git a/package/src/store/apis/softDeleteMessage.ts b/package/src/store/apis/softDeleteMessage.ts index e88964f358..ce9c99a104 100644 --- a/package/src/store/apis/softDeleteMessage.ts +++ b/package/src/store/apis/softDeleteMessage.ts @@ -1,25 +1,25 @@ -import { MessageLabel } from 'stream-chat'; +import { DBDeleteMessageType, MessageLabel } from 'stream-chat'; import { createUpdateQuery } from '../sqlite-utils/createUpdateQuery'; import { SqliteClient } from '../SqliteClient'; export const softDeleteMessage = async ({ + deleteForMe = false, execute = true, id, -}: { - id: string; - execute?: boolean; -}) => { +}: DBDeleteMessageType) => { const query = createUpdateQuery( 'messages', { - deletedAt: new Date().toISOString(), + deletedAt: deleteForMe ? undefined : new Date().toISOString(), + deletedForMe: deleteForMe, type: 'deleted' as MessageLabel, }, { id }, ); SqliteClient.logger?.('info', 'softDeleteMessage', { + deleteForMe, execute, id, }); diff --git a/package/src/store/mappers/mapMessageToStorable.ts b/package/src/store/mappers/mapMessageToStorable.ts index f307355af2..ac166d7c98 100644 --- a/package/src/store/mappers/mapMessageToStorable.ts +++ b/package/src/store/mappers/mapMessageToStorable.ts @@ -12,6 +12,7 @@ export const mapMessageToStorable = ( cid, created_at, deleted_at, + deleted_for_me, id, // eslint-disable-next-line @typescript-eslint/no-unused-vars latest_reactions, @@ -37,6 +38,7 @@ export const mapMessageToStorable = ( cid: cid || '', createdAt: mapDateTimeToStorable(created_at), deletedAt: mapDateTimeToStorable(deleted_at), + deletedForMe: deleted_for_me, extraData: JSON.stringify(extraData), id, messageTextUpdatedAt: mapDateTimeToStorable(message_text_updated_at), diff --git a/package/src/store/mappers/mapStorableToMessage.ts b/package/src/store/mappers/mapStorableToMessage.ts index 30ca649ee5..e4b6459466 100644 --- a/package/src/store/mappers/mapStorableToMessage.ts +++ b/package/src/store/mappers/mapStorableToMessage.ts @@ -24,6 +24,7 @@ export const mapStorableToMessage = ({ const { createdAt, deletedAt, + deletedForMe, extraData, messageTextUpdatedAt, poll_id, @@ -42,6 +43,7 @@ export const mapStorableToMessage = ({ attachments: messageRow.attachments ? JSON.parse(messageRow.attachments) : [], created_at: createdAt, deleted_at: deletedAt, + deleted_for_me: deletedForMe, latest_reactions: latestReactions, message_text_updated_at: messageTextUpdatedAt, own_reactions: ownReactions, diff --git a/package/src/store/schema.ts b/package/src/store/schema.ts index 50ec7ee2aa..21de2afed4 100644 --- a/package/src/store/schema.ts +++ b/package/src/store/schema.ts @@ -170,6 +170,7 @@ export const tables: Tables = { cid: 'TEXT NOT NULL', createdAt: 'TEXT', deletedAt: 'TEXT', + deletedForMe: 'BOOLEAN DEFAULT FALSE', extraData: 'TEXT', id: 'TEXT', messageTextUpdatedAt: 'TEXT', @@ -408,6 +409,7 @@ export type Schema = { cid: string; createdAt: string; deletedAt: string; + deletedForMe?: boolean; extraData: string; id: string; messageTextUpdatedAt: string;