From 871cdd2be5b7b7790d8dcebbbcd46ef2082def13 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 1 May 2024 15:20:38 +0530 Subject: [PATCH] feat: async audio (#2501) * feat: async audio initial steps for Expo CLI * feat: add async audio for native cli * feat: add recording to message input context * feat: add playback support for native CLI for recordings * feat: audio playback support for expo cli * feat: async audio play in message list and improvements * feat: add audio speed change button * chore: upgrade rn-videi * feat: native android permissions * fix: permissions android * fix: improve async audio * fix: remove redundant console logs * fix: improve normalization of waveform * fix: add prop description * fix: improve theme * fix: add improvements to file upload preview * fix: translation issue * feat: add comments * feat: improve state of recording * feat: props improvement * docs: message input props * docs: lint fix * feat: add audio support guide * fix: delete voice recording * feat: enable async audio in TS app and Expo app * fix: normalization fix * test: fix tests --- .../audio-attachment-upload-preview.jpg | Bin 0 -> 23266 bytes .../guides/audio-support/audio-attachment.jpg | Bin 0 -> 17768 bytes .../audio-recorder-lock-button.png | Bin 0 -> 94612 bytes .../audio-recording-in-progress.png | Bin 0 -> 93382 bytes .../audio-support/audio-recording-preview.jpg | Bin 0 -> 17470 bytes .../guides/audio-support/start-recording.png | Bin 0 -> 39020 bytes .../docs/reactnative/basics/installation.mdx | 7 +- .../docs/reactnative/basics/overview.mdx | 8 +- .../props/async_messages_lock_distance.mdx | 5 + .../async_messages_minimum_press_duration.mdx | 5 + .../async_messages_multi_send_enabled.mdx | 5 + ...sync_messages_slide_to_cancel_distance.mdx | 5 + .../props/audio_attachment_upload_preview.mdx | 5 + .../channel/props/audio_recorder.mdx | 5 + .../channel/props/audio_recording_enabled.mdx | 5 + .../props/audio_recording_in_progress.mdx | 5 + .../props/audio_recording_lock_indicator.mdx | 5 + .../channel/props/audio_recording_preview.mdx | 5 + .../props/audio_recording_waveform.mdx | 5 + .../props/start_audio_recording_button.mdx | 5 + .../contexts/message-input-context.mdx | 60 +++ .../reactnative/core-components/channel.mdx | 78 ++- .../guides/audio-messages-support.mdx | 141 +++++ .../ui-components/message-input.mdx | 55 ++ docusaurus/sidebars-react-native.json | 1 + .../ExpoMessaging/app/channel/[cid]/index.tsx | 2 +- .../app/channel/[cid]/thread/[cid]/index.tsx | 2 +- examples/SampleApp/ios/Podfile.lock | 12 +- examples/SampleApp/package.json | 2 +- examples/SampleApp/yarn.lock | 149 +----- examples/TypeScriptMessaging/App.tsx | 15 +- .../android/app/src/main/AndroidManifest.xml | 75 +-- .../TypeScriptMessaging/android/build.gradle | 7 +- examples/TypeScriptMessaging/ios/Podfile.lock | 482 +++++++++--------- .../ios/TypeScriptMessaging/Info.plist | 3 + examples/TypeScriptMessaging/package.json | 3 +- .../TypeScriptMessaging/useStreamChatTheme.ts | 2 + examples/TypeScriptMessaging/yarn.lock | 203 +++----- package/expo-package/src/handlers/Audio.ts | 270 ++++++++++ package/expo-package/src/handlers/index.ts | 1 + package/expo-package/src/index.js | 2 + package/jest-setup.js | 4 + package/native-package/package.json | 4 + package/native-package/src/handlers/Sound.tsx | 3 +- package/native-package/src/index.js | 2 + .../src/optionalDependencies/Audio.ts | 243 +++++++++ .../src/optionalDependencies/index.ts | 1 + .../src/components/Attachment/Attachment.tsx | 9 +- .../components/Attachment/AudioAttachment.tsx | 342 ++++++++----- .../Attachment/FileAttachmentGroup.tsx | 90 ++-- .../components/Attachment/VideoThumbnail.tsx | 2 +- package/src/components/Channel/Channel.tsx | 32 ++ .../useCreateInputMessageInputContext.ts | 24 + .../__tests__/ImageGallery.test.tsx | 2 +- .../ImageGalleryVideoControl.test.tsx | 4 +- .../components/ImageGalleryVideoControl.tsx | 9 +- package/src/components/Message/Message.tsx | 2 +- .../Message/MessageSimple/MessageContent.tsx | 3 +- .../Message/MessageSimple/MessageSimple.tsx | 3 +- .../Message/hooks/useMessageActions.tsx | 4 +- .../MessageInput/FileUploadPreview.tsx | 165 +++--- .../components/MessageInput/InputButtons.tsx | 3 +- .../components/MessageInput/MessageInput.tsx | 369 ++++++++++++-- .../components/MessageInput/SendButton.tsx | 10 +- .../__tests__/MessageInput.test.js | 1 - .../__snapshots__/SendButton.test.js.snap | 180 +++++-- .../AudioRecorder/AudioRecorder.tsx | 313 ++++++++++++ .../AudioRecorder/AudioRecordingButton.tsx | 192 +++++++ .../AudioRecordingInProgress.tsx | 114 +++++ .../AudioRecordingLockIndicator.tsx | 85 +++ .../AudioRecorder/AudioRecordingPreview.tsx | 102 ++++ .../AudioRecorder/AudioRecordingWaveform.tsx | 62 +++ .../MessageInput/hooks/useAudioController.tsx | 288 +++++++++++ .../MessageInput/utils/audioSampling.ts | 108 ++++ .../MessageInput/utils/normalizeAudioLevel.ts | 20 + .../ProgressControl/ProgressControl.tsx | 109 ++-- .../ProgressControl/WaveProgressBar.tsx | 180 +++++++ package/src/components/Reply/Reply.tsx | 145 ++++-- .../__snapshots__/Thread.test.js.snap | 58 ++- package/src/components/index.ts | 6 + .../MessageInputContext.tsx | 105 +++- .../hooks/useCreateMessageInputContext.ts | 25 + .../hooks/useMessageDetailsForState.ts | 107 +++- .../src/contexts/themeContext/utils/theme.ts | 125 ++++- package/src/i18n/en.json | 2 + package/src/i18n/es.json | 2 + package/src/i18n/fr.json | 2 + package/src/i18n/he.json | 2 + package/src/i18n/hi.json | 2 + package/src/i18n/it.json | 2 + package/src/i18n/ja.json | 2 + package/src/i18n/ko.json | 2 + package/src/i18n/nl.json | 2 + package/src/i18n/pt-BR.json | 2 + package/src/i18n/ru.json | 2 + package/src/i18n/tr.json | 2 + package/src/icons/ArrowLeft.tsx | 18 + package/src/icons/ArrowUp.tsx | 19 + package/src/icons/Audio.tsx | 48 +- package/src/icons/CircleStop.tsx | 18 + package/src/icons/Delete.tsx | 22 +- package/src/icons/Lock.tsx | 22 + package/src/icons/Mic.tsx | 20 + package/src/icons/Pause.tsx | 23 +- package/src/icons/Play.tsx | 22 +- package/src/icons/SendCheck.tsx | 18 + package/src/icons/SendRight.tsx | 23 +- package/src/icons/SendUp.tsx | 23 +- package/src/icons/Stop.tsx | 12 + package/src/icons/index.ts | 7 + package/src/native.ts | 69 +++ package/src/types/types.ts | 6 + .../src/utils/getTrimmedAttachmentTitle.ts | 5 + 113 files changed, 4504 insertions(+), 1190 deletions(-) create mode 100644 docusaurus/docs/reactnative/assets/guides/audio-support/audio-attachment-upload-preview.jpg create mode 100644 docusaurus/docs/reactnative/assets/guides/audio-support/audio-attachment.jpg create mode 100644 docusaurus/docs/reactnative/assets/guides/audio-support/audio-recorder-lock-button.png create mode 100644 docusaurus/docs/reactnative/assets/guides/audio-support/audio-recording-in-progress.png create mode 100644 docusaurus/docs/reactnative/assets/guides/audio-support/audio-recording-preview.jpg create mode 100644 docusaurus/docs/reactnative/assets/guides/audio-support/start-recording.png create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_lock_distance.mdx create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_minimum_press_duration.mdx create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_multi_send_enabled.mdx create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_slide_to_cancel_distance.mdx create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_attachment_upload_preview.mdx create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recorder.mdx create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_enabled.mdx create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_in_progress.mdx create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_lock_indicator.mdx create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_preview.mdx create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_waveform.mdx create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/start_audio_recording_button.mdx create mode 100644 docusaurus/docs/reactnative/guides/audio-messages-support.mdx create mode 100644 package/expo-package/src/handlers/Audio.ts create mode 100644 package/native-package/src/optionalDependencies/Audio.ts create mode 100644 package/src/components/MessageInput/components/AudioRecorder/AudioRecorder.tsx create mode 100644 package/src/components/MessageInput/components/AudioRecorder/AudioRecordingButton.tsx create mode 100644 package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx create mode 100644 package/src/components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator.tsx create mode 100644 package/src/components/MessageInput/components/AudioRecorder/AudioRecordingPreview.tsx create mode 100644 package/src/components/MessageInput/components/AudioRecorder/AudioRecordingWaveform.tsx create mode 100644 package/src/components/MessageInput/hooks/useAudioController.tsx create mode 100644 package/src/components/MessageInput/utils/audioSampling.ts create mode 100644 package/src/components/MessageInput/utils/normalizeAudioLevel.ts create mode 100644 package/src/components/ProgressControl/WaveProgressBar.tsx create mode 100644 package/src/icons/ArrowLeft.tsx create mode 100644 package/src/icons/ArrowUp.tsx create mode 100644 package/src/icons/CircleStop.tsx create mode 100644 package/src/icons/Lock.tsx create mode 100644 package/src/icons/Mic.tsx create mode 100644 package/src/icons/SendCheck.tsx create mode 100644 package/src/icons/Stop.tsx create mode 100644 package/src/utils/getTrimmedAttachmentTitle.ts diff --git a/docusaurus/docs/reactnative/assets/guides/audio-support/audio-attachment-upload-preview.jpg b/docusaurus/docs/reactnative/assets/guides/audio-support/audio-attachment-upload-preview.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e413b986d2f8a17d075bd65c2ba94260fc586e56 GIT binary patch literal 23266 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 literal 0 HcmV?d00001 diff --git a/docusaurus/docs/reactnative/assets/guides/audio-support/audio-attachment.jpg b/docusaurus/docs/reactnative/assets/guides/audio-support/audio-attachment.jpg new file mode 100644 index 0000000000000000000000000000000000000000..241a7f67e3dc56ad99280fec9c9775b6734ed734 GIT binary patch literal 17768 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໏#^$LR9_hF3;JY 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 z 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 WFWpY`=CnhA|K7@}$<|1l1^o}-XCZ(9 literal 0 HcmV?d00001 diff --git a/docusaurus/docs/reactnative/assets/guides/audio-support/audio-recording-in-progress.png b/docusaurus/docs/reactnative/assets/guides/audio-support/audio-recording-in-progress.png new file mode 100644 index 0000000000000000000000000000000000000000..74b0528128e1620fe101b472c683dc689e4a6e8f GIT binary patch literal 93382 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 literal 0 HcmV?d00001 diff --git a/docusaurus/docs/reactnative/assets/guides/audio-support/audio-recording-preview.jpg b/docusaurus/docs/reactnative/assets/guides/audio-support/audio-recording-preview.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8cdcbf458aab4e4501c94019a5be15033114c4e6 GIT binary patch literal 17470 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 literal 0 HcmV?d00001 diff --git a/docusaurus/docs/reactnative/basics/installation.mdx b/docusaurus/docs/reactnative/basics/installation.mdx index 403c17a5c6..68a1756164 100644 --- a/docusaurus/docs/reactnative/basics/installation.mdx +++ b/docusaurus/docs/reactnative/basics/installation.mdx @@ -123,6 +123,7 @@ values={[ - [`react-native-video`](https://github.com/react-native-video/react-native-video) for Video and Audio playback support. +- [`react-native-audio-recorder-player`](https://github.com/hyochan/react-native-audio-recorder-player) for Audio recording and async audio messages support. - [`react-native-share`](https://github.com/react-native-share/react-native-share) for Attachment sharing support. - [`react-native-haptic-feedback`](https://github.com/junina-de/react-native-haptic-feedback) for user haptics feedback. - [`@react-native-clipboard/clipboard`](https://github.com/react-native-clipboard/clipboard) for Copy message support. @@ -132,7 +133,7 @@ values={[ -- [`expo-av`](https://docs.expo.dev/versions/latest/sdk/av/) for Video and Audio playback support. +- [`expo-av`](https://docs.expo.dev/versions/latest/sdk/av/) for Video and Audio playback, recording and async audio messages support. - [`expo-sharing`](https://docs.expo.dev/versions/latest/sdk/sharing/) for Attachments sharing support. - [`expo-haptics`](https://docs.expo.dev/versions/latest/sdk/haptics/) for user haptics support. - [`expo-clipboard`](https://docs.expo.dev/versions/latest/sdk/clipboard/) for Copy message support. @@ -142,6 +143,10 @@ values={[ +:::note +Please follow along the linked documentation of each optional dependencies so as to support them correctly in your application. +::: + ### Additional Steps Some dependencies require us to make changes to our application for all functionalities to be available. diff --git a/docusaurus/docs/reactnative/basics/overview.mdx b/docusaurus/docs/reactnative/basics/overview.mdx index b8e35c3a40..f9c5d14c51 100644 --- a/docusaurus/docs/reactnative/basics/overview.mdx +++ b/docusaurus/docs/reactnative/basics/overview.mdx @@ -103,6 +103,7 @@ There are a few optional dependencies that can be added by our users to have mor - [`react-native-video`](https://github.com/react-native-video/react-native-video) for Video and Audio playback support. +- [`react-native-audio-recorder-player`](https://github.com/hyochan/react-native-audio-recorder-player) for Audio recording and async audio messages support. - [`react-native-share`](https://github.com/react-native-share/react-native-share) for Attachment sharing support. - [`react-native-haptic-feedback`](https://github.com/junina-de/react-native-haptic-feedback) for user haptics feedback. - [`@react-native-clipboard/clipboard`](https://github.com/react-native-clipboard/clipboard) for Copy message support. @@ -112,15 +113,20 @@ There are a few optional dependencies that can be added by our users to have mor -- [`expo-av`](https://docs.expo.dev/versions/latest/sdk/av/) for Video and Audio playback support. +- [`expo-av`](https://docs.expo.dev/versions/latest/sdk/av/) for Video and Audio playback, recording and async audio messages support. - [`expo-sharing`](https://docs.expo.dev/versions/latest/sdk/sharing/) for Attachments sharing support. - [`expo-haptics`](https://docs.expo.dev/versions/latest/sdk/haptics/) for user haptics support. - [`expo-clipboard`](https://docs.expo.dev/versions/latest/sdk/clipboard/) for Copy message support. - [`expo-document-picker`](https://docs.expo.dev/versions/latest/sdk/document-picker/) to access device media files. +- [`react-native-quick-sqlite`](https://github.com/margelo/react-native-quick-sqlite) to enable Offline support in the app. +:::note +Please follow along the linked documentation of each optional dependencies so as to support them correctly in your application. +::: + ## Choosing the Right SDK When integrating with our chat platform, you get to choose which SDK you would like to integrate with. diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_lock_distance.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_lock_distance.mdx new file mode 100644 index 0000000000..5ce1279176 --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_lock_distance.mdx @@ -0,0 +1,5 @@ +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. + +| Type | Default | +| ------ | ------- | +| Number | 50 | diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_minimum_press_duration.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_minimum_press_duration.mdx new file mode 100644 index 0000000000..f67cdf74a8 --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_minimum_press_duration.mdx @@ -0,0 +1,5 @@ +Controls the minimum duration(in milliseconds) that the user has to press on the record button in the composer, in order to start recording a new voice message. + +| Type | Default | +| ------ | ------- | +| Number | 500 | diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_multi_send_enabled.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_multi_send_enabled.mdx new file mode 100644 index 0000000000..b186fb078c --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_multi_send_enabled.mdx @@ -0,0 +1,5 @@ +When it’s enabled, recorded messages won’t be sent immediately. Instead they will “stack up” in the composer allowing the user to send multiple voice recording as part of the same message. + +| Type | Default | +| ------- | ------- | +| Boolean | true | diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_slide_to_cancel_distance.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_slide_to_cancel_distance.mdx new file mode 100644 index 0000000000..1092d3c8e1 --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/async_messages_slide_to_cancel_distance.mdx @@ -0,0 +1,5 @@ +Controls how many pixels to the leading side the user has to scroll in order to cancel the recording of a voice message. + +| Type | Default | +| ------ | ------- | +| Number | 100 | diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_attachment_upload_preview.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_attachment_upload_preview.mdx new file mode 100644 index 0000000000..6e143f9baa --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_attachment_upload_preview.mdx @@ -0,0 +1,5 @@ +Component prop used to customize the audio attachment upload preview when its uploading/uploaded in the `MessageInput`. + +| Type | Default | +| ------------- | ------------------------------------------------------------------- | +| ComponentType | [`AudioAttachment`](../../../../ui-components/audio-attachment.mdx) | diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recorder.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recorder.mdx new file mode 100644 index 0000000000..52ea24ee5c --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recorder.mdx @@ -0,0 +1,5 @@ +Custom UI component to render audio recorder controls in [MessageInput](../../../../ui-components/message-input.mdx). + +| Type | Default | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| ComponentType | [`AudioRecorder`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/AudioRecorder.tsx) | diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_enabled.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_enabled.mdx new file mode 100644 index 0000000000..0f2d7e3ee5 --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_enabled.mdx @@ -0,0 +1,5 @@ +Controls whether the feature is enabled. + +| Type | Default | +| ------- | ------- | +| Boolean | false | diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_in_progress.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_in_progress.mdx new file mode 100644 index 0000000000..e4450a5022 --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_in_progress.mdx @@ -0,0 +1,5 @@ +Custom UI component to render audio recording in progress in [MessageInput](../../../../ui-components/message-input.mdx). It renders the waveform, duration etc, for the recording. + +| Type | Default | +| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ComponentType | [`AudioRecordingInProgress`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx) | diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_lock_indicator.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_lock_indicator.mdx new file mode 100644 index 0000000000..4b43d3b0ec --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_lock_indicator.mdx @@ -0,0 +1,5 @@ +Custom UI component to render audio recording lock indicator in [MessageInput](../../../../ui-components/message-input.mdx) that can be dragged up to lock the recording start. + +| Type | Default | +| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ComponentType | [`AudioRecordingLockIndicator`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator.tsx) | diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_preview.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_preview.mdx new file mode 100644 index 0000000000..6234dd3dca --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_preview.mdx @@ -0,0 +1,5 @@ +Custom UI component to render audio recording preview in [MessageInput](../../../../ui-components/message-input.mdx) that allows playing the recording. + +| Type | Default | +| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ComponentType | [`AudioRecordingPreview`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingPreview.tsx) | diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_waveform.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_waveform.mdx new file mode 100644 index 0000000000..2f8058b38c --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/audio_recording_waveform.mdx @@ -0,0 +1,5 @@ +Custom UI component to render audio recording waveform in [MessageInput](../../../../ui-components/message-input.mdx). + +| Type | Default | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ComponentType | [`AudioRecordingWaveform`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingWaveform.tsx) | diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/start_audio_recording_button.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/start_audio_recording_button.mdx new file mode 100644 index 0000000000..b8dbd604ed --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/start_audio_recording_button.mdx @@ -0,0 +1,5 @@ +Custom UI component for audio recording mic button in [MessageInput](../../../../ui-components/message-input.mdx). + +| Type | Default | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ComponentType | [`StartAudioRecordingButton`](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingButton.tsx) | diff --git a/docusaurus/docs/reactnative/contexts/message-input-context.mdx b/docusaurus/docs/reactnative/contexts/message-input-context.mdx index 87cde018ac..dba4f6f94d 100644 --- a/docusaurus/docs/reactnative/contexts/message-input-context.mdx +++ b/docusaurus/docs/reactnative/contexts/message-input-context.mdx @@ -6,7 +6,18 @@ title: MessageInputContext import SelectedPicker from '../common-content/contexts/attachment-picker-context/selected_picker.mdx'; import AdditionalTextInputProps from '../common-content/ui-components/channel/props/additional_text_input_props.mdx'; +import AsyncMessagesLockDistance from '../common-content/ui-components/channel/props/async_messages_lock_distance.mdx'; +import AsyncMessagesMinimumPressDuration from '../common-content/ui-components/channel/props/async_messages_minimum_press_duration.mdx'; +import AsyncMessagesMultiSendEnabled from '../common-content/ui-components/channel/props/async_messages_multi_send_enabled.mdx'; +import AsyncMessagesSlideToCancelDistance from '../common-content/ui-components/channel/props/async_messages_slide_to_cancel_distance.mdx'; import AttachButton from '../common-content/ui-components/channel/props/attach_button.mdx'; +import AudioAttachmentUploadPreview from '../common-content/ui-components/channel/props/audio_attachment_upload_preview.mdx'; +import AudioRecorder from '../common-content/ui-components/channel/props/audio_recorder.mdx'; +import AudioRecordingEnabled from '../common-content/ui-components/channel/props/audio_recording_enabled.mdx'; +import AudioRecordingInProgress from '../common-content/ui-components/channel/props/audio_recording_in_progress.mdx'; +import AudioRecordingLockIndicator from '../common-content/ui-components/channel/props/audio_recording_lock_indicator.mdx'; +import AudioRecordingPreview from '../common-content/ui-components/channel/props/audio_recording_preview.mdx'; +import AudioRecordingWaveform from '../common-content/ui-components/channel/props/audio_recording_waveform.mdx'; import AutoCompleteSuggestionsLimit from '../common-content/ui-components/channel/props/auto_complete_suggestions_limit.mdx'; import AutoCompleteTriggerSettings from '../common-content/ui-components/channel/props/auto_complete_trigger_settings.mdx'; import CommandsButton from '../common-content/ui-components/channel/props/commands_button.mdx'; @@ -33,6 +44,7 @@ import SendButton from '../common-content/ui-components/channel/props/send_butto import SendMessageDisallowedIndicator from '../common-content/ui-components/channel/props/send_message_disallowed_indicator.mdx'; import SendImageAsync from '../common-content/ui-components/channel/props/send_image_async.mdx'; import ShowThreadMessageInChannelButton from '../common-content/ui-components/channel/props/show_thread_message_in_channel_button.mdx'; +import StartAudioRecordingButton from '../common-content/ui-components/channel/props/start_audio_recording_button.mdx'; import Thread from '../common-content/ui-components/channel/props/thread.mdx'; import UploadProgressIndicator from '../common-content/ui-components/channel/props/upload_progress_indicator.mdx'; @@ -106,6 +118,26 @@ const { sendMessage, toggleAttachmentPicker } = useMessageInputContext(); +### ayncMessagesLockDistance + + + +### ayncMessagesMinimumPressDuration + + + +### ayncMessagesMultiSendEnabled + + + +### ayncMessagesSlideToCancelDistance + + + +### audioRecordingEnabled + + + ###

_forwarded from [Channel](../../core-components/channel#autocompletesuggestionslimit)_ props
autoCompleteSuggestionsLimit {#autocompletesuggestionslimit} @@ -376,6 +408,30 @@ const { sendMessage, toggleAttachmentPicker } = useMessageInputContext(); +###
_forwarded from [Channel](../../core-components/channel#audioattachmentuploadpreview)_ props
AudioAttachmentUploadPreview {#audioattachmentuploadpreview} + + + +###
_forwarded from [Channel](../../core-components/channel#audiorecorder)_ props
AudioRecorder {#audiorecorder} + + + +###
_forwarded from [Channel](../../core-components/channel#audiorecordinginprogress)_ props
AudioRecordingInProgress {#audiorecordinginprogress} + + + +###
_forwarded from [Channel](../../core-components/channel#audiorecordinglockindicator)_ props
AudioRecordingLockIndicator {#audiorecordinglockindicator} + + + +###
_forwarded from [Channel](../../core-components/channel#audiorecordingpreview)_ props
AudioRecordingPreview {#audiorecordingpreview} + + + +###
_forwarded from [Channel](../../core-components/channel#audiorecordingwaveform)_ props
AudioRecordingWaveform {#audiorecordingwaveform} + + + ###
_forwarded from [Channel](../../core-components/channel#commandsbutton)_ props
CommandsButton {#commandsbutton} @@ -412,6 +468,10 @@ const { sendMessage, toggleAttachmentPicker } = useMessageInputContext(); +###
_forwarded from [Channel](../../core-components/channel#startaudiorecordingbutton)_ props
StartAudioRecordingButton {#startaudiorecordingbutton} + + + ###
_forwarded from [Channel](../../core-components/channel#uploadprogressindicator)_ props
UploadProgressIndicator {#uploadprogressindicator} diff --git a/docusaurus/docs/reactnative/core-components/channel.mdx b/docusaurus/docs/reactnative/core-components/channel.mdx index d6b043944c..32542a36a7 100644 --- a/docusaurus/docs/reactnative/core-components/channel.mdx +++ b/docusaurus/docs/reactnative/core-components/channel.mdx @@ -16,6 +16,10 @@ import NetworkDownIndicator from '../common-content/ui-components/channel/props/ import AdditionalKeyboardAvoidingViewProps from '../common-content/ui-components/channel/props/additional_keyboard_avoiding_view_props.mdx'; import AdditionalTextInputProps from '../common-content/ui-components/channel/props/additional_text_input_props.mdx'; import AllowThreadMessagesInChannel from '../common-content/ui-components/channel/props/allow_thread_messages_in_channel.mdx'; +import AsyncMessagesLockDistance from '../common-content/ui-components/channel/props/async_messages_lock_distance.mdx'; +import AsyncMessagesMinimumPressDuration from '../common-content/ui-components/channel/props/async_messages_minimum_press_duration.mdx'; +import AsyncMessagesMultiSendEnabled from '../common-content/ui-components/channel/props/async_messages_multi_send_enabled.mdx'; +import AsyncMessagesSlideToCancelDistance from '../common-content/ui-components/channel/props/async_messages_slide_to_cancel_distance.mdx'; import AutoCompleteSuggestionHeader from '../common-content/ui-components/channel/props/autocomplete_suggestion_header.mdx'; import AutoCompleteSuggestionItem from '../common-content/ui-components/channel/props/autocomplete_suggestion_item.mdx'; import AutoCompleteSuggestionList from '../common-content/ui-components/channel/props/autocomplete_suggestion_list.mdx'; @@ -26,6 +30,13 @@ import AttachButton from '../common-content/ui-components/channel/props/attach_b import Attachment from '../common-content/ui-components/channel/props/attachment.mdx'; import AttachmentActions from '../common-content/ui-components/channel/props/attachment_actions.mdx'; import AudioAttachment from '../common-content/ui-components/channel/props/audio_attachment.mdx'; +import AudioAttachmentUploadPreview from '../common-content/ui-components/channel/props/audio_attachment_upload_preview.mdx'; +import AudioRecorder from '../common-content/ui-components/channel/props/audio_recorder.mdx'; +import AudioRecordingEnabled from '../common-content/ui-components/channel/props/audio_recording_enabled.mdx'; +import AudioRecordingInProgress from '../common-content/ui-components/channel/props/audio_recording_in_progress.mdx'; +import AudioRecordingLockIndicator from '../common-content/ui-components/channel/props/audio_recording_lock_indicator.mdx'; +import AudioRecordingPreview from '../common-content/ui-components/channel/props/audio_recording_preview.mdx'; +import AudioRecordingWaveform from '../common-content/ui-components/channel/props/audio_recording_waveform.mdx'; import Card from '../common-content/ui-components/channel/props/card.mdx'; import CardCover from '../common-content/ui-components/channel/props/card_cover.mdx'; import CardFooter from '../common-content/ui-components/channel/props/card_footer.mdx'; @@ -120,6 +131,7 @@ import SelectReaction from '../common-content/ui-components/channel/props/select import SendButton from '../common-content/ui-components/channel/props/send_button.mdx'; import SendMessageDisallowedIndicator from '../common-content/ui-components/channel/props/send_message_disallowed_indicator.mdx'; import ShowThreadMessageInChannelButton from '../common-content/ui-components/channel/props/show_thread_message_in_channel_button.mdx'; +import StartAudioRecordingButton from '../common-content/ui-components/channel/props/start_audio_recording_button.mdx'; import StateUpdateThrottleInterval from '../common-content/ui-components/channel/props/state_update_throttle_interval.mdx'; import SupportedReactions from '../common-content/ui-components/channel/props/supported_reactions.mdx'; import Thread from '../common-content/ui-components/channel/props/thread.mdx'; @@ -268,21 +280,29 @@ This is often the header height. -### autoCompleteSuggestionsLimit +### ayncMessagesLockDistance - + -### AutoCompleteSuggestionHeader +### ayncMessagesMinimumPressDuration - + -### AutoCompleteSuggestionItem +### ayncMessagesMultiSendEnabled - + -### AutoCompleteSuggestionList +### ayncMessagesSlideToCancelDistance - + + +### audioRecordingEnabled + + + +### autoCompleteSuggestionsLimit + + ### autoCompleteTriggerSettings @@ -688,6 +708,8 @@ Callback function to set the [ref](https://reactjs.org/docs/refs-and-the-dom.htm +## UI Components Props + ### AttachButton @@ -704,6 +726,42 @@ Callback function to set the [ref](https://reactjs.org/docs/refs-and-the-dom.htm +### AudioAttachmentUploadPreview + + + +### AudioRecorder + + + +### AudioRecordingInProgress + + + +### AudioRecordingLockIndicator + + + +### AudioRecordingPreview + + + +### AudioRecordingWaveform + + + +### AutoCompleteSuggestionHeader + + + +### AutoCompleteSuggestionItem + + + +### AutoCompleteSuggestionList + + + ### Card @@ -920,6 +978,10 @@ Component to render full screen error indicator, when channel fails to load. +### StartAudioRecordingButton + + + ### TypingIndicator diff --git a/docusaurus/docs/reactnative/guides/audio-messages-support.mdx b/docusaurus/docs/reactnative/guides/audio-messages-support.mdx new file mode 100644 index 0000000000..b1777295c5 --- /dev/null +++ b/docusaurus/docs/reactnative/guides/audio-messages-support.mdx @@ -0,0 +1,141 @@ +--- +id: audio-messages-support +title: Audio Messages Support +--- + +import ImageShowcase from '@site/src/components/ImageShowcase'; +import AudioAttachmentUploadPreview from '../assets/guides/audio-support/audio-attachment-upload-preview.jpg'; +import AudioAttachment from '../assets/guides/audio-support/audio-attachment.jpg'; + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The Stream Chat React Native SDK has the support for recording and playing the audio. This can be sent as a message. + +## Installation + +To support audio playing and recording, install the following package: + + + + +- [`react-native-video`](https://github.com/react-native-video/react-native-video) for Audio playback support. +- [`react-native-audio-recorder-player`](https://github.com/hyochan/react-native-audio-recorder-player) for Audio recording and preview. + + + + + +- [`expo-av`](https://docs.expo.dev/versions/latest/sdk/av/) for Audio playback, recording and async audio messages support. + + + + +:::note +Please follow along the linked documentation of each optional dependencies so as to support them correctly in your application. + +Also, make sure that the `minSdkVersion` is >=24, for [`react-native-audio-recorder-player`](https://github.com/hyochan/react-native-audio-recorder-player) to work and the `kotlinVersion` should be `1.6.10`. +::: + +## Enable Async Audio + +Recording voice messages is possible by enabling audio recording on the [`Channel`](../core-components/channel.mdx#audiorecordingenabled) or the [`MessageInput`](../ui-components/message-input.mdx#audiorecordingenabled) component. + +```tsx + +``` + +Once enabled and once the necessary packages are installed, the `MessageInput` UI will render a `StartRecordingAudioButton`. + +![Start Recording](../assets/guides/audio-support/start-recording.png) + +On long pressing the `StartRecordingAudioButton`, the recording UI renders and the recording starts, showing the `AudioRecorder` UI and a `AudioRecordingLockIndicator` on top. + +![Audio Recorder](../assets/guides/audio-support/audio-recorder-lock-button.png) + +You can slide the `StartRecordingAudioButton` to the top or to the left. + +- On sliding the button to the left, the recording is stopped and deleted and the `MessageInput` state is reset. +- On sliding the button to the top, the mic is locked and you don't have to press the button anymore. + +When the mic is locked, the `AudioRecordingInProgress` component is rendered on top, and the `AudioRecorder` component now shows the stop recording and the send recording button. + +![Audio Recording in Progress](../assets/guides/audio-support/audio-recording-in-progress.png) + +When the recording is stopped, the `AudioRecordingPreview` component is rendered which allows you to preview the recording. It has play and pause button to control the preview. Moreover, the `AudioRecorder` component now renders delete recording and the confirm button to send the recording. + +![Audio Recording Preview](../assets/guides/audio-support/audio-recording-preview.jpg) + +The audio attachment has different preview in the `MessageInput` and the `MessageList` component. The `MessageInput` renders `AudioAttachmentUploadPreview` and the `MessageList` renders the `AudioAttachment` component. + + + +## Message Send Behaviour + +The resulting recording is always uploaded on the recording completion. The recording is completed when user stops the recording and confirms the completion with a send button. + +The behavior, when a message with the given recording attachment is sent, however, can be controlled through the asyncMessagesMultiSendEnabled configuration prop on `Channel` or `MessageInput`. + +```tsx + +``` + +And so the message is sent depending on asyncMessagesMultiSendEnabled value as follows: + +| `asyncMessagesMultiSendEnabled` value | Impact | +| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `false` (default behavior) | immediately after a successful upload at one step on completion. In that case as a single attachment (voice recording only), no-text message is submitted | +| `true` | upon clicking the `SendMessage` button if `asyncMessagesMultiSendEnabled` is enabled | + +:::note +Enabling `asyncMessagesMultiSendEnabled` would allow users to record multiple voice messages or accompany the voice recording with text or other types of attachments. +::: + +## Customization + +We allow different types of customization with the behaviour of async audio messages. + +### UI Customization + +The components of the async audio can be customized by passing your custom component to the props in the table in the [`Channel`](../core-components/channel.mdx) component. + +| Component | Description | +| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| [`AudioAttachmentUploadPreview`](../core-components/channel.mdx#audioattachmentuploadpreview) | Component prop used to customize the audio attachment upload preview in the `MessageInput`. | +| [`AudioRecorder`](../core-components/channel.mdx#audiorecorder) | Component prop used to customize the audio recording controls and UI that replaces the `MessageInput`. | +| [`AudioRecordingInProgress`](../core-components/channel.mdx#audiorecordinginprogress) | Component prop used to customize the audio recording UI when its in recording state. | +| [`AudioRecordingPreview`](../core-components/channel.mdx#audiorecordingpreview) | Component prop used to customize the audio recording preview UI. | +| [`AudioRecordingLockIndicator`](../core-components/channel.mdx#audiorecordinglockindicator) | Component prop used to customize the mic lock indicator on top of the `MessageInput`. | +| [`AudioRecordingWaveform`](../core-components/channel.mdx#audiorecorindwaveform) | Component prop used to customize the audio recording waveform component that is shown in the `AudioRecordingInProgress` component. | +| [`StartRecordingAudioButton`](../core-components/channel.mdx#startaudiorecordingbutton) | Component prop used to customize the start audio recording button in the `MessageInput`. | +| [`AudioAttachment`](../core-components/channel.mdx#audioattachment) | Component prop used to customize the audio attachment in the `MessageList`. | + +### Function customization + +| Component | Description | +| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`audioRecordingEnabled`](../core-components/channel.mdx#audioattachmentuploadpreview) | Controls whether the async audio feature(sending voice recording) is enabled. | +| [`asyncMessagesMultiSendEnabled`](../core-components/channel.mdx#audiorecorder) | When it’s enabled, recorded messages won’t be sent immediately. Instead they will “stack up” in the composer allowing the user to send multiple voice recording as part of the same message. | +| [`asyncMessagesLockDistance`](../core-components/channel.mdx#audiorecordinginprogress) | 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. | +| [`asyncMessagesMinimumPressDuration`](../core-components/channel.mdx#audiorecordingpreview) | Controls the minimum duration that the user has to press on the record button in the composer, in order to start recording a new voice message. | +| [`asyncMessagesSlideToCancelDistance`](../core-components/channel.mdx#audiorecordinglockindicator) | Controls how many pixels to the leading side the user has to scroll in order to cancel the recording of a voice message. | diff --git a/docusaurus/docs/reactnative/ui-components/message-input.mdx b/docusaurus/docs/reactnative/ui-components/message-input.mdx index 9ca7ad2ee1..ed2bc94986 100644 --- a/docusaurus/docs/reactnative/ui-components/message-input.mdx +++ b/docusaurus/docs/reactnative/ui-components/message-input.mdx @@ -20,10 +20,21 @@ import InputButtons from '../common-content/ui-components/channel/props/input_bu import MaxNumberOfFiles from '../common-content/ui-components/channel/props/max_number_of_files.mdx'; import SendButton from '../common-content/ui-components/channel/props/send_button.mdx'; import ShowThreadMessageInChannelButton from '../common-content/ui-components/channel/props/show_thread_message_in_channel_button.mdx'; +import StartAudioRecordingButton from '../common-content/ui-components/channel/props/start_audio_recording_button.mdx'; import Thread from '../common-content/ui-components/channel/props/thread.mdx'; import AsyncIds from '../common-content/contexts/message-input-context/async_ids.mdx'; +import AsyncMessagesLockDistance from '../common-content/ui-components/channel/props/async_messages_lock_distance.mdx'; +import AsyncMessagesMinimumPressDuration from '../common-content/ui-components/channel/props/async_messages_minimum_press_duration.mdx'; +import AsyncMessagesMultiSendEnabled from '../common-content/ui-components/channel/props/async_messages_multi_send_enabled.mdx'; +import AsyncMessagesSlideToCancelDistance from '../common-content/ui-components/channel/props/async_messages_slide_to_cancel_distance.mdx'; import AsyncUploads from '../common-content/contexts/message-input-context/async_uploads.mdx'; +import AudioRecorder from '../common-content/ui-components/channel/props/audio_recorder.mdx'; +import AudioRecordingEnabled from '../common-content/ui-components/channel/props/audio_recording_enabled.mdx'; +import AudioRecordingInProgress from '../common-content/ui-components/channel/props/audio_recording_in_progress.mdx'; +import AudioRecordingLockIndicator from '../common-content/ui-components/channel/props/audio_recording_lock_indicator.mdx'; +import AudioRecordingPreview from '../common-content/ui-components/channel/props/audio_recording_preview.mdx'; +import AudioRecordingWaveform from '../common-content/ui-components/channel/props/audio_recording_waveform.mdx'; import ClearEditingState from '../common-content/contexts/message-input-context/clear_editing_state.mdx'; import ClearQuotedMessageState from '../common-content/contexts/message-input-context/clear_quoted_message_state.mdx'; import CloseAttachmentPicker from '../common-content/contexts/message-input-context/close_attachment_picker.mdx'; @@ -74,6 +85,26 @@ All the configuration for `MessageInput` can be done on [`Channel`](../core-comp Additionally please take a look at our [Guide Section](../guides/custom-message-input.mdx) on how to customize MessageInput UI ::: +###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#asyncmessageslockdistance)_
`asyncMessagesLockDistance` {#asyncmessageslockdistance} + + + +###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#asyncmessagesminimumpressduration)_
`asyncMessagesMinimumPressDuration` {#asyncmessagesminimumpressduration} + + + +###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#asyncmessagesmultisendenabled)_
`AsyncMessagesMultiSendEnabled` {#asyncmessagesmultisendenabled} + + + +###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#cleareditingstate)_
`asyncMessagesSlideToCancelDistance` {#asyncmessagesslideTocanceldistance} + + + +###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#audiorecordingenabled)_
`audioRecordingEnabled` {#audiorecordingenabled} + + + ###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#cleareditingstate)_
`clearEditingState` {#cleareditingstate} @@ -196,6 +227,26 @@ Additionally please take a look at our [Guide Section](../guides/custom-message- ## UI Component Props +###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#audiorecorder)_
`AudioRecorder` {#audiorecorder} + + + +###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#audiorecordinginprogress)_
`AudioRecordingInProgress` {#audiorecordinginprogress} + + + +###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#audiorecordinglockindicator)_
`AudioRecordingLockIndicator` {#audiorecordinglockindicator} + + + +###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#audiorecordingpreview)_
`AudioRecordingPreview` {#audiorecordingpreview} + + + +###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#audiorecordingwaveform)_
`AudioRecordingWaveform` {#audiorecordingwaveform} + + + ###
_overrides the value from [ChannelContext](../contexts/messages-context.mdx#reply)_
`Reply` {#reply} @@ -223,3 +274,7 @@ Additionally please take a look at our [Guide Section](../guides/custom-message- ###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#showthreadmessageinchannelbutton)_
`ShowThreadMessageInChannelButton` {#showthreadmessageinchannelbutton} + +###
_overrides the value from [MessageInputContext](../contexts/message-input-context.mdx#startaudiorecordingbutton)_
`StartAudioRecordingButton` {#startaudiorecordingbutton} + + diff --git a/docusaurus/sidebars-react-native.json b/docusaurus/sidebars-react-native.json index dd78a3108b..206f24fad5 100644 --- a/docusaurus/sidebars-react-native.json +++ b/docusaurus/sidebars-react-native.json @@ -127,6 +127,7 @@ "basics/offline-support" ], "Advanced Guides": [ + "guides/audio-messages-support", "customization/typescript", "basics/troubleshooting", "basics/stream_chat_with_navigation", diff --git a/examples/ExpoMessaging/app/channel/[cid]/index.tsx b/examples/ExpoMessaging/app/channel/[cid]/index.tsx index ac4438f9c9..175da7bfa3 100644 --- a/examples/ExpoMessaging/app/channel/[cid]/index.tsx +++ b/examples/ExpoMessaging/app/channel/[cid]/index.tsx @@ -25,7 +25,7 @@ export default function ChannelScreen() { {channel && ( - + { diff --git a/examples/ExpoMessaging/app/channel/[cid]/thread/[cid]/index.tsx b/examples/ExpoMessaging/app/channel/[cid]/thread/[cid]/index.tsx index 249c29b658..7aad7b79a9 100644 --- a/examples/ExpoMessaging/app/channel/[cid]/thread/[cid]/index.tsx +++ b/examples/ExpoMessaging/app/channel/[cid]/thread/[cid]/index.tsx @@ -15,7 +15,7 @@ export default function ThreadScreen() { - + = ({ navigation }) => { return ( - + onThreadSelect={(selectedThread) => { @@ -171,7 +176,13 @@ const ThreadScreen: React.FC = ({ navigation }) => { return ( - + - - - - - - + + + + + + + - - - - - - - - - - + + + + + + + + + + - + - - - - - - - - + android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" + android:launchMode="singleTask" + android:windowSoftInputMode="adjustResize" + android:exported="true"> + + + + + + + \ No newline at end of file diff --git a/examples/TypeScriptMessaging/android/build.gradle b/examples/TypeScriptMessaging/android/build.gradle index 77dc961d36..5495aac781 100644 --- a/examples/TypeScriptMessaging/android/build.gradle +++ b/examples/TypeScriptMessaging/android/build.gradle @@ -1,11 +1,14 @@ buildscript { ext { buildToolsVersion = "34.0.0" - minSdkVersion = 21 + // Added 24 for react-native-audio-recorder-player(Value - 24) + minSdkVersion = 24 compileSdkVersion = 34 targetSdkVersion = 34 + androidXCore = "1.0.2" + // Added for react-native-audio-recorder-player(Value - 1.6.10) + kotlinVersion = "1.6.10" ndkVersion = "25.1.8937393" - kotlinVersion = "1.8.0" } repositories { google() diff --git a/examples/TypeScriptMessaging/ios/Podfile.lock b/examples/TypeScriptMessaging/ios/Podfile.lock index a6a39a7b33..b4dbb50a44 100644 --- a/examples/TypeScriptMessaging/ios/Podfile.lock +++ b/examples/TypeScriptMessaging/ios/Podfile.lock @@ -2,14 +2,14 @@ PODS: - boost (1.83.0) - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) - - FBLazyVector (0.73.4) - - FBReactNativeSpec (0.73.4): + - FBLazyVector (0.73.6) + - FBReactNativeSpec (0.73.6): - RCT-Folly (= 2022.05.16.00) - - RCTRequired (= 0.73.4) - - RCTTypeSafety (= 0.73.4) - - React-Core (= 0.73.4) - - React-jsi (= 0.73.4) - - ReactCommon/turbomodule/core (= 0.73.4) + - RCTRequired (= 0.73.6) + - RCTTypeSafety (= 0.73.6) + - React-Core (= 0.73.6) + - React-jsi (= 0.73.6) + - ReactCommon/turbomodule/core (= 0.73.6) - Flipper (0.201.0): - Flipper-Folly (~> 2.6) - Flipper-Boost-iOSX (1.76.0.1.11) @@ -68,9 +68,9 @@ PODS: - FlipperKit/FlipperKitNetworkPlugin - fmt (6.2.1) - glog (0.3.5) - - hermes-engine (0.73.4): - - hermes-engine/Pre-built (= 0.73.4) - - hermes-engine/Pre-built (0.73.4) + - hermes-engine (0.73.6): + - hermes-engine/Pre-built (= 0.73.6) + - hermes-engine/Pre-built (0.73.6) - libevent (2.1.12) - OpenSSL-Universal (1.1.1100) - PromisesObjC (2.4.0) @@ -98,26 +98,26 @@ PODS: - fmt (~> 6.2.1) - glog - libevent - - RCTRequired (0.73.4) - - RCTTypeSafety (0.73.4): - - FBLazyVector (= 0.73.4) - - RCTRequired (= 0.73.4) - - React-Core (= 0.73.4) - - React (0.73.4): - - React-Core (= 0.73.4) - - React-Core/DevSupport (= 0.73.4) - - React-Core/RCTWebSocket (= 0.73.4) - - React-RCTActionSheet (= 0.73.4) - - React-RCTAnimation (= 0.73.4) - - React-RCTBlob (= 0.73.4) - - React-RCTImage (= 0.73.4) - - React-RCTLinking (= 0.73.4) - - React-RCTNetwork (= 0.73.4) - - React-RCTSettings (= 0.73.4) - - React-RCTText (= 0.73.4) - - React-RCTVibration (= 0.73.4) - - React-callinvoker (0.73.4) - - React-Codegen (0.73.4): + - RCTRequired (0.73.6) + - RCTTypeSafety (0.73.6): + - FBLazyVector (= 0.73.6) + - RCTRequired (= 0.73.6) + - React-Core (= 0.73.6) + - React (0.73.6): + - React-Core (= 0.73.6) + - React-Core/DevSupport (= 0.73.6) + - React-Core/RCTWebSocket (= 0.73.6) + - React-RCTActionSheet (= 0.73.6) + - React-RCTAnimation (= 0.73.6) + - React-RCTBlob (= 0.73.6) + - React-RCTImage (= 0.73.6) + - React-RCTLinking (= 0.73.6) + - React-RCTNetwork (= 0.73.6) + - React-RCTSettings (= 0.73.6) + - React-RCTText (= 0.73.6) + - React-RCTVibration (= 0.73.6) + - React-callinvoker (0.73.6) + - React-Codegen (0.73.6): - DoubleConversion - FBReactNativeSpec - glog @@ -132,11 +132,11 @@ PODS: - React-rncore - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - React-Core (0.73.4): + - React-Core (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) - - React-Core/Default (= 0.73.4) + - React-Core/Default (= 0.73.6) - React-cxxreact - React-hermes - React-jsi @@ -146,7 +146,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/CoreModulesHeaders (0.73.4): + - React-Core/CoreModulesHeaders (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -160,7 +160,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/Default (0.73.4): + - React-Core/Default (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -173,23 +173,23 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/DevSupport (0.73.4): + - React-Core/DevSupport (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) - - React-Core/Default (= 0.73.4) - - React-Core/RCTWebSocket (= 0.73.4) + - React-Core/Default (= 0.73.6) + - React-Core/RCTWebSocket (= 0.73.6) - React-cxxreact - React-hermes - React-jsi - React-jsiexecutor - - React-jsinspector (= 0.73.4) + - React-jsinspector (= 0.73.6) - React-perflogger - React-runtimescheduler - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTActionSheetHeaders (0.73.4): + - React-Core/RCTActionSheetHeaders (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -203,7 +203,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTAnimationHeaders (0.73.4): + - React-Core/RCTAnimationHeaders (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -217,7 +217,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTBlobHeaders (0.73.4): + - React-Core/RCTBlobHeaders (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -231,7 +231,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTImageHeaders (0.73.4): + - React-Core/RCTImageHeaders (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -245,7 +245,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTLinkingHeaders (0.73.4): + - React-Core/RCTLinkingHeaders (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -259,7 +259,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTNetworkHeaders (0.73.4): + - React-Core/RCTNetworkHeaders (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -273,7 +273,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTSettingsHeaders (0.73.4): + - React-Core/RCTSettingsHeaders (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -287,7 +287,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTTextHeaders (0.73.4): + - React-Core/RCTTextHeaders (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -301,7 +301,7 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTVibrationHeaders (0.73.4): + - React-Core/RCTVibrationHeaders (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -315,11 +315,11 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-Core/RCTWebSocket (0.73.4): + - React-Core/RCTWebSocket (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) - - React-Core/Default (= 0.73.4) + - React-Core/Default (= 0.73.6) - React-cxxreact - React-hermes - React-jsi @@ -329,33 +329,33 @@ PODS: - React-utils - SocketRocket (= 0.6.1) - Yoga - - React-CoreModules (0.73.4): + - React-CoreModules (0.73.6): - RCT-Folly (= 2022.05.16.00) - - RCTTypeSafety (= 0.73.4) + - RCTTypeSafety (= 0.73.6) - React-Codegen - - React-Core/CoreModulesHeaders (= 0.73.4) - - React-jsi (= 0.73.4) + - React-Core/CoreModulesHeaders (= 0.73.6) + - React-jsi (= 0.73.6) - React-NativeModulesApple - React-RCTBlob - - React-RCTImage (= 0.73.4) + - React-RCTImage (= 0.73.6) - ReactCommon - SocketRocket (= 0.6.1) - - React-cxxreact (0.73.4): + - React-cxxreact (0.73.6): - boost (= 1.83.0) - DoubleConversion - fmt (~> 6.2.1) - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) - - React-callinvoker (= 0.73.4) - - React-debug (= 0.73.4) - - React-jsi (= 0.73.4) - - React-jsinspector (= 0.73.4) - - React-logger (= 0.73.4) - - React-perflogger (= 0.73.4) - - React-runtimeexecutor (= 0.73.4) - - React-debug (0.73.4) - - React-Fabric (0.73.4): + - React-callinvoker (= 0.73.6) + - React-debug (= 0.73.6) + - React-jsi (= 0.73.6) + - React-jsinspector (= 0.73.6) + - React-logger (= 0.73.6) + - React-perflogger (= 0.73.6) + - React-runtimeexecutor (= 0.73.6) + - React-debug (0.73.6) + - React-Fabric (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -366,20 +366,20 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/animations (= 0.73.4) - - React-Fabric/attributedstring (= 0.73.4) - - React-Fabric/componentregistry (= 0.73.4) - - React-Fabric/componentregistrynative (= 0.73.4) - - React-Fabric/components (= 0.73.4) - - React-Fabric/core (= 0.73.4) - - React-Fabric/imagemanager (= 0.73.4) - - React-Fabric/leakchecker (= 0.73.4) - - React-Fabric/mounting (= 0.73.4) - - React-Fabric/scheduler (= 0.73.4) - - React-Fabric/telemetry (= 0.73.4) - - React-Fabric/templateprocessor (= 0.73.4) - - React-Fabric/textlayoutmanager (= 0.73.4) - - React-Fabric/uimanager (= 0.73.4) + - React-Fabric/animations (= 0.73.6) + - React-Fabric/attributedstring (= 0.73.6) + - React-Fabric/componentregistry (= 0.73.6) + - React-Fabric/componentregistrynative (= 0.73.6) + - React-Fabric/components (= 0.73.6) + - React-Fabric/core (= 0.73.6) + - React-Fabric/imagemanager (= 0.73.6) + - React-Fabric/leakchecker (= 0.73.6) + - React-Fabric/mounting (= 0.73.6) + - React-Fabric/scheduler (= 0.73.6) + - React-Fabric/telemetry (= 0.73.6) + - React-Fabric/templateprocessor (= 0.73.6) + - React-Fabric/textlayoutmanager (= 0.73.6) + - React-Fabric/uimanager (= 0.73.6) - React-graphics - React-jsi - React-jsiexecutor @@ -388,7 +388,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/animations (0.73.4): + - React-Fabric/animations (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -407,7 +407,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/attributedstring (0.73.4): + - React-Fabric/attributedstring (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -426,7 +426,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/componentregistry (0.73.4): + - React-Fabric/componentregistry (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -445,7 +445,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/componentregistrynative (0.73.4): + - React-Fabric/componentregistrynative (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -464,7 +464,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components (0.73.4): + - React-Fabric/components (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -475,17 +475,17 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/components/inputaccessory (= 0.73.4) - - React-Fabric/components/legacyviewmanagerinterop (= 0.73.4) - - React-Fabric/components/modal (= 0.73.4) - - React-Fabric/components/rncore (= 0.73.4) - - React-Fabric/components/root (= 0.73.4) - - React-Fabric/components/safeareaview (= 0.73.4) - - React-Fabric/components/scrollview (= 0.73.4) - - React-Fabric/components/text (= 0.73.4) - - React-Fabric/components/textinput (= 0.73.4) - - React-Fabric/components/unimplementedview (= 0.73.4) - - React-Fabric/components/view (= 0.73.4) + - React-Fabric/components/inputaccessory (= 0.73.6) + - React-Fabric/components/legacyviewmanagerinterop (= 0.73.6) + - React-Fabric/components/modal (= 0.73.6) + - React-Fabric/components/rncore (= 0.73.6) + - React-Fabric/components/root (= 0.73.6) + - React-Fabric/components/safeareaview (= 0.73.6) + - React-Fabric/components/scrollview (= 0.73.6) + - React-Fabric/components/text (= 0.73.6) + - React-Fabric/components/textinput (= 0.73.6) + - React-Fabric/components/unimplementedview (= 0.73.6) + - React-Fabric/components/view (= 0.73.6) - React-graphics - React-jsi - React-jsiexecutor @@ -494,7 +494,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/inputaccessory (0.73.4): + - React-Fabric/components/inputaccessory (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -513,7 +513,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/legacyviewmanagerinterop (0.73.4): + - React-Fabric/components/legacyviewmanagerinterop (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -532,7 +532,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/modal (0.73.4): + - React-Fabric/components/modal (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -551,7 +551,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/rncore (0.73.4): + - React-Fabric/components/rncore (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -570,7 +570,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/root (0.73.4): + - React-Fabric/components/root (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -589,7 +589,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/safeareaview (0.73.4): + - React-Fabric/components/safeareaview (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -608,7 +608,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/scrollview (0.73.4): + - React-Fabric/components/scrollview (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -627,7 +627,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/text (0.73.4): + - React-Fabric/components/text (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -646,7 +646,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/textinput (0.73.4): + - React-Fabric/components/textinput (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -665,7 +665,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/unimplementedview (0.73.4): + - React-Fabric/components/unimplementedview (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -684,7 +684,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/components/view (0.73.4): + - React-Fabric/components/view (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -704,7 +704,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - Yoga - - React-Fabric/core (0.73.4): + - React-Fabric/core (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -723,7 +723,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/imagemanager (0.73.4): + - React-Fabric/imagemanager (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -742,7 +742,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/leakchecker (0.73.4): + - React-Fabric/leakchecker (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -761,7 +761,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/mounting (0.73.4): + - React-Fabric/mounting (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -780,7 +780,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/scheduler (0.73.4): + - React-Fabric/scheduler (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -799,7 +799,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/telemetry (0.73.4): + - React-Fabric/telemetry (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -818,7 +818,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/templateprocessor (0.73.4): + - React-Fabric/templateprocessor (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -837,7 +837,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/textlayoutmanager (0.73.4): + - React-Fabric/textlayoutmanager (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -857,7 +857,7 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-Fabric/uimanager (0.73.4): + - React-Fabric/uimanager (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog @@ -876,42 +876,42 @@ PODS: - React-runtimescheduler - React-utils - ReactCommon/turbomodule/core - - React-FabricImage (0.73.4): + - React-FabricImage (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog - hermes-engine - RCT-Folly/Fabric (= 2022.05.16.00) - - RCTRequired (= 0.73.4) - - RCTTypeSafety (= 0.73.4) + - RCTRequired (= 0.73.6) + - RCTTypeSafety (= 0.73.6) - React-Fabric - React-graphics - React-ImageManager - React-jsi - - React-jsiexecutor (= 0.73.4) + - React-jsiexecutor (= 0.73.6) - React-logger - React-rendererdebug - React-utils - ReactCommon - Yoga - - React-graphics (0.73.4): + - React-graphics (0.73.6): - glog - RCT-Folly/Fabric (= 2022.05.16.00) - - React-Core/Default (= 0.73.4) + - React-Core/Default (= 0.73.6) - React-utils - - React-hermes (0.73.4): + - React-hermes (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) - RCT-Folly/Futures (= 2022.05.16.00) - - React-cxxreact (= 0.73.4) + - React-cxxreact (= 0.73.6) - React-jsi - - React-jsiexecutor (= 0.73.4) - - React-jsinspector (= 0.73.4) - - React-perflogger (= 0.73.4) - - React-ImageManager (0.73.4): + - React-jsiexecutor (= 0.73.6) + - React-jsinspector (= 0.73.6) + - React-perflogger (= 0.73.6) + - React-ImageManager (0.73.6): - glog - RCT-Folly/Fabric - React-Core/Default @@ -920,31 +920,31 @@ PODS: - React-graphics - React-rendererdebug - React-utils - - React-jserrorhandler (0.73.4): + - React-jserrorhandler (0.73.6): - RCT-Folly/Fabric (= 2022.05.16.00) - React-debug - React-jsi - React-Mapbuffer - - React-jsi (0.73.4): + - React-jsi (0.73.6): - boost (= 1.83.0) - DoubleConversion - fmt (~> 6.2.1) - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) - - React-jsiexecutor (0.73.4): + - React-jsiexecutor (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) - - React-cxxreact (= 0.73.4) - - React-jsi (= 0.73.4) - - React-perflogger (= 0.73.4) - - React-jsinspector (0.73.4) - - React-logger (0.73.4): + - React-cxxreact (= 0.73.6) + - React-jsi (= 0.73.6) + - React-perflogger (= 0.73.6) + - React-jsinspector (0.73.6) + - React-logger (0.73.6): - glog - - React-Mapbuffer (0.73.4): + - React-Mapbuffer (0.73.6): - glog - React-debug - react-native-cameraroll (5.6.0): @@ -963,14 +963,18 @@ PODS: - React-Core - react-native-safe-area-context (4.9.0): - React-Core - - react-native-video (6.0.0-beta.5): + - react-native-video (6.0.0-beta.8): + - glog + - RCT-Folly (= 2022.05.16.00) - React-Core - - react-native-video/Video (= 6.0.0-beta.5) - - react-native-video/Video (6.0.0-beta.5): + - react-native-video/Video (= 6.0.0-beta.8) + - react-native-video/Video (6.0.0-beta.8): + - glog - PromisesSwift + - RCT-Folly (= 2022.05.16.00) - React-Core - - React-nativeconfig (0.73.4) - - React-NativeModulesApple (0.73.4): + - React-nativeconfig (0.73.6) + - React-NativeModulesApple (0.73.6): - glog - hermes-engine - React-callinvoker @@ -980,10 +984,10 @@ PODS: - React-runtimeexecutor - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - React-perflogger (0.73.4) - - React-RCTActionSheet (0.73.4): - - React-Core/RCTActionSheetHeaders (= 0.73.4) - - React-RCTAnimation (0.73.4): + - React-perflogger (0.73.6) + - React-RCTActionSheet (0.73.6): + - React-Core/RCTActionSheetHeaders (= 0.73.6) + - React-RCTAnimation (0.73.6): - RCT-Folly (= 2022.05.16.00) - RCTTypeSafety - React-Codegen @@ -991,7 +995,7 @@ PODS: - React-jsi - React-NativeModulesApple - ReactCommon - - React-RCTAppDelegate (0.73.4): + - React-RCTAppDelegate (0.73.6): - RCT-Folly - RCTRequired - RCTTypeSafety @@ -1005,7 +1009,7 @@ PODS: - React-RCTNetwork - React-runtimescheduler - ReactCommon - - React-RCTBlob (0.73.4): + - React-RCTBlob (0.73.6): - hermes-engine - RCT-Folly (= 2022.05.16.00) - React-Codegen @@ -1015,7 +1019,7 @@ PODS: - React-NativeModulesApple - React-RCTNetwork - ReactCommon - - React-RCTFabric (0.73.4): + - React-RCTFabric (0.73.6): - glog - hermes-engine - RCT-Folly/Fabric (= 2022.05.16.00) @@ -1033,7 +1037,7 @@ PODS: - React-runtimescheduler - React-utils - Yoga - - React-RCTImage (0.73.4): + - React-RCTImage (0.73.6): - RCT-Folly (= 2022.05.16.00) - RCTTypeSafety - React-Codegen @@ -1042,14 +1046,14 @@ PODS: - React-NativeModulesApple - React-RCTNetwork - ReactCommon - - React-RCTLinking (0.73.4): + - React-RCTLinking (0.73.6): - React-Codegen - - React-Core/RCTLinkingHeaders (= 0.73.4) - - React-jsi (= 0.73.4) + - React-Core/RCTLinkingHeaders (= 0.73.6) + - React-jsi (= 0.73.6) - React-NativeModulesApple - ReactCommon - - ReactCommon/turbomodule/core (= 0.73.4) - - React-RCTNetwork (0.73.4): + - ReactCommon/turbomodule/core (= 0.73.6) + - React-RCTNetwork (0.73.6): - RCT-Folly (= 2022.05.16.00) - RCTTypeSafety - React-Codegen @@ -1057,7 +1061,7 @@ PODS: - React-jsi - React-NativeModulesApple - ReactCommon - - React-RCTSettings (0.73.4): + - React-RCTSettings (0.73.6): - RCT-Folly (= 2022.05.16.00) - RCTTypeSafety - React-Codegen @@ -1065,25 +1069,25 @@ PODS: - React-jsi - React-NativeModulesApple - ReactCommon - - React-RCTText (0.73.4): - - React-Core/RCTTextHeaders (= 0.73.4) + - React-RCTText (0.73.6): + - React-Core/RCTTextHeaders (= 0.73.6) - Yoga - - React-RCTVibration (0.73.4): + - React-RCTVibration (0.73.6): - RCT-Folly (= 2022.05.16.00) - React-Codegen - React-Core/RCTVibrationHeaders - React-jsi - React-NativeModulesApple - ReactCommon - - React-rendererdebug (0.73.4): + - React-rendererdebug (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - RCT-Folly (= 2022.05.16.00) - React-debug - - React-rncore (0.73.4) - - React-runtimeexecutor (0.73.4): - - React-jsi (= 0.73.4) - - React-runtimescheduler (0.73.4): + - React-rncore (0.73.6) + - React-runtimeexecutor (0.73.6): + - React-jsi (= 0.73.6) + - React-runtimescheduler (0.73.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1094,48 +1098,50 @@ PODS: - React-rendererdebug - React-runtimeexecutor - React-utils - - React-utils (0.73.4): + - React-utils (0.73.6): - glog - RCT-Folly (= 2022.05.16.00) - React-debug - - ReactCommon (0.73.4): - - React-logger (= 0.73.4) - - ReactCommon/turbomodule (= 0.73.4) - - ReactCommon/turbomodule (0.73.4): + - ReactCommon (0.73.6): + - React-logger (= 0.73.6) + - ReactCommon/turbomodule (= 0.73.6) + - ReactCommon/turbomodule (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) - - React-callinvoker (= 0.73.4) - - React-cxxreact (= 0.73.4) - - React-jsi (= 0.73.4) - - React-logger (= 0.73.4) - - React-perflogger (= 0.73.4) - - ReactCommon/turbomodule/bridging (= 0.73.4) - - ReactCommon/turbomodule/core (= 0.73.4) - - ReactCommon/turbomodule/bridging (0.73.4): + - React-callinvoker (= 0.73.6) + - React-cxxreact (= 0.73.6) + - React-jsi (= 0.73.6) + - React-logger (= 0.73.6) + - React-perflogger (= 0.73.6) + - ReactCommon/turbomodule/bridging (= 0.73.6) + - ReactCommon/turbomodule/core (= 0.73.6) + - ReactCommon/turbomodule/bridging (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) - - React-callinvoker (= 0.73.4) - - React-cxxreact (= 0.73.4) - - React-jsi (= 0.73.4) - - React-logger (= 0.73.4) - - React-perflogger (= 0.73.4) - - ReactCommon/turbomodule/core (0.73.4): + - React-callinvoker (= 0.73.6) + - React-cxxreact (= 0.73.6) + - React-jsi (= 0.73.6) + - React-logger (= 0.73.6) + - React-perflogger (= 0.73.6) + - ReactCommon/turbomodule/core (0.73.6): - DoubleConversion - fmt (~> 6.2.1) - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) - - React-callinvoker (= 0.73.4) - - React-cxxreact (= 0.73.4) - - React-jsi (= 0.73.4) - - React-logger (= 0.73.4) - - React-perflogger (= 0.73.4) + - React-callinvoker (= 0.73.6) + - React-cxxreact (= 0.73.6) + - React-jsi (= 0.73.6) + - React-logger (= 0.73.6) + - React-perflogger (= 0.73.6) + - RNAudioRecorderPlayer (3.6.6): + - React-Core - RNCClipboard (1.13.2): - React-Core - RNCMaskedView (0.1.11): @@ -1255,6 +1261,7 @@ DEPENDENCIES: - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - RNAudioRecorderPlayer (from `../node_modules/react-native-audio-recorder-player`) - "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)" - "RNCMaskedView (from `../node_modules/@react-native-community/masked-view`)" - RNFS (from `../node_modules/react-native-fs`) @@ -1299,7 +1306,7 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" - :tag: hermes-2023-11-17-RNv0.73.0-21043a3fc062be445e56a2c10ecd8be028dd9cc5 + :tag: hermes-2024-02-20-RNv0.73.5-18f99ace4213052c5e7cdbcd39ee9766cd5df7e4 RCT-Folly: :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" RCTRequired: @@ -1398,6 +1405,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/utils" ReactCommon: :path: "../node_modules/react-native/ReactCommon" + RNAudioRecorderPlayer: + :path: "../node_modules/react-native-audio-recorder-player" RNCClipboard: :path: "../node_modules/@react-native-clipboard/clipboard" RNCMaskedView: @@ -1425,8 +1434,8 @@ SPEC CHECKSUMS: boost: d3f49c53809116a5d38da093a8aa78bf551aed09 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 - FBLazyVector: 84f6edbe225f38aebd9deaf1540a4160b1f087d7 - FBReactNativeSpec: d0086a479be91c44ce4687a962956a352d2dc697 + FBLazyVector: f64d1e2ea739b4d8f7e4740cde18089cd97fe864 + FBReactNativeSpec: 9f2b8b243131565335437dba74923a8d3015e780 Flipper: c7a0093234c4bdd456e363f2f19b2e4b27652d44 Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30 @@ -1437,32 +1446,32 @@ SPEC CHECKSUMS: FlipperKit: 37525a5d056ef9b93d1578e04bc3ea1de940094f fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 - hermes-engine: b2669ce35fc4ac14f523b307aff8896799829fe2 + hermes-engine: 9cecf9953a681df7556b8cc9c74905de8f3293c0 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0 - RCTRequired: ab7f915c15569f04a49669e573e6e319a53f9faa - RCTTypeSafety: 63b97ced7b766865057e7154db0e81ce4ee6cf1e - React: 1c87497e50fa40ba9c54e5ea5e53483a0f8eecc0 - React-callinvoker: e3a52a9a93e3eb004d7282c26a4fb27003273fe6 - React-Codegen: 50c0f8f073e71b929b057b68bf31be604f1dccc8 - React-Core: d0ecde72894b792cb8922efaa0990199cbe85169 - React-CoreModules: 2ff1684dd517f0c441495d90a704d499f05e9d0a - React-cxxreact: d9be2fac926741052395da0a6d0bab8d71e2f297 - React-debug: 4678e73a37cb501d784e99ff0f219b4940362a3b - React-Fabric: 460ee9d4b8b9de3382504a711430bfead1d5be1e - React-FabricImage: d0a0631bc8ad9143f42bfccf9d3d533a144cc3d6 - React-graphics: f0d5040263a9649e2a70ebe27b3120c49411afef - React-hermes: b9ac2f7b0c1eeb206eb883583cab7a973d570a6e - React-ImageManager: 6c4bf9d5ed363ead7b5aaf820a3feab221b7063e - React-jserrorhandler: 6e7a7e187583e14dc7a0053a2bdd66c252ea3b21 - React-jsi: 380cd24dd81a705dd042c18989fb10b07182210c - React-jsiexecutor: 8ed7a18b9f119440efdcd424c8257dc7e18067e2 - React-jsinspector: 9ac353eccf6ab54d1e0a33862ba91221d1e88460 - React-logger: 0a57b68dd2aec7ff738195f081f0520724b35dab - React-Mapbuffer: 63913773ed7f96b814a2521e13e6d010282096ad + RCTRequired: ca1d7414aba0b27efcfa2ccd37637edb1ab77d96 + RCTTypeSafety: 678e344fb976ff98343ca61dc62e151f3a042292 + React: e296bcebb489deaad87326067204eb74145934ab + React-callinvoker: d0b7015973fa6ccb592bb0363f6bc2164238ab8c + React-Codegen: f034a5de6f28e15e8d95d171df17e581d5309268 + React-Core: 44c936d0ab879e9c32e5381bd7596a677c59c974 + React-CoreModules: 558228e12cddb9ca00ff7937894cc5104a21be6b + React-cxxreact: 1fcf565012c203655b3638f35aa03c13c2ed7e9e + React-debug: d444db402065cca460d9c5b072caab802a04f729 + React-Fabric: 7d11905695e42f8bdaedddcf294959b43b290ab8 + React-FabricImage: 6e06a512d2fb5f55669c721578736785d915d4f5 + React-graphics: 5500206f7c9a481456365403c9fcf1638de108b7 + React-hermes: 783023e43af9d6be4fbaeeb96b5beee00649a5f7 + React-ImageManager: df193215ff3cf1a8dad297e554c89c632e42436c + React-jserrorhandler: a4d0f541c5852cf031db2f82f51de90be55b1334 + React-jsi: ae102ccb38d2e4d0f512b7074d0c9b4e1851f402 + React-jsiexecutor: bd12ec75873d3ef0a755c11f878f2c420430f5a9 + React-jsinspector: 85583ef014ce53d731a98c66a0e24496f7a83066 + React-logger: 3eb80a977f0d9669468ef641a5e1fabbc50a09ec + React-Mapbuffer: 84ea43c6c6232049135b1550b8c60b2faac19fab react-native-cameraroll: 755bcc628148a90a7c9cf3f817a252be3a601bc5 react-native-document-picker: 2b8f18667caee73a96708a82b284a4f40b30a156 react-native-flipper: 9c1957af24b76493ba74f46d000a5c1d485e7731 @@ -1470,41 +1479,42 @@ SPEC CHECKSUMS: react-native-netinfo: 299dad906cdbf3b67bcc6f693c807f98bdd127cc react-native-quick-sqlite: 2b225dadc63b670f027111e58f6f169773f6d755 react-native-safe-area-context: b97eb6f9e3b7f437806c2ce5983f479f8eb5de4b - react-native-video: df7d8a4c8568ed4a31b28e6cd2bfa4a98b186e36 - React-nativeconfig: d7af5bae6da70fa15ce44f045621cf99ed24087c - React-NativeModulesApple: 0123905d5699853ac68519607555a9a4f5c7b3ac - React-perflogger: 8a1e1af5733004bdd91258dcefbde21e0d1faccd - React-RCTActionSheet: 64bbff3a3963664c2d0146f870fe8e0264aee4c4 - React-RCTAnimation: b698168a7269265a4694727196484342d695f0c1 - React-RCTAppDelegate: dcd8e955116eb1d1908dfaf08b4c970812e6a1e6 - React-RCTBlob: 47f8c3b2b4b7fa2c5f19c43f0b7f77f57fb9d953 - React-RCTFabric: 6067a32d683d0c2b84d444548bc15a263c64abed - React-RCTImage: ac0e77a44c290b20db783649b2b9cddc93e3eb99 - React-RCTLinking: e626fd2900913fe5d25922ea1be394b7aafa09c9 - React-RCTNetwork: d3114bce3977dafe8bd06421b29812f5a8527ba0 - React-RCTSettings: a53511f90d8df637a1a11ac729179a4d2f734481 - React-RCTText: f0176f5f5952f9a4a2c7354f5ae71f7c420aaf34 - React-RCTVibration: 8160223c6eda5b187079fec204f80eca8b8f3177 - React-rendererdebug: ed286b4da8648c27d6ed3ae1410d4b21ba890d5a - React-rncore: 43f133b89ac10c4b6ab43702a541dee1c292a3bf - React-runtimeexecutor: e6ab6bb083dbdbdd489cff426ed0bce0652e6edf - React-runtimescheduler: ed48e5faac6751e66ee1261c4bd01643b436f112 - React-utils: 6e5ad394416482ae21831050928ae27348f83487 - ReactCommon: 840a955d37b7f3358554d819446bffcf624b2522 + react-native-video: d440605e68cf173e70f0b25112455e3d86890663 + React-nativeconfig: b4d4e9901d4cabb57be63053fd2aa6086eb3c85f + React-NativeModulesApple: cd26e56d56350e123da0c1e3e4c76cb58a05e1ee + React-perflogger: 5f49905de275bac07ac7ea7f575a70611fa988f2 + React-RCTActionSheet: 37edf35aeb8e4f30e76c82aab61f12d1b75c04ec + React-RCTAnimation: a69de7f3daa8462743094f4736c455e844ea63f7 + React-RCTAppDelegate: 51fb96b554a6acd0cd7818acecd5aa5ca2f3ab9f + React-RCTBlob: d91771caebf2d015005d750cd1dc2b433ad07c99 + React-RCTFabric: c5b9451d1f2b546119b7a0353226a8a26247d4a9 + React-RCTImage: a0bfe87b6908c7b76bd7d74520f40660bd0ad881 + React-RCTLinking: 5f10be1647952cceddfa1970fdb374087582fc34 + React-RCTNetwork: a0bc3dd45a2dc7c879c80cebb6f9707b2c8bbed6 + React-RCTSettings: 28c202b68afa59afb4067510f2c69c5a530fb9e3 + React-RCTText: 4119d9e53ca5db9502b916e1b146e99798986d21 + React-RCTVibration: 55bd7c48487eb9a2562f2bd3fdc833274f5b0636 + React-rendererdebug: 5fa97ba664806cee4700e95aec42dff1b6f8ea36 + React-rncore: b0a8e1d14dabb7115c7a5b4ec8b9b74d1727d382 + React-runtimeexecutor: bb328dbe2865f3a550df0240df8e2d8c3aaa4c57 + React-runtimescheduler: 9636eee762c699ca7c85751a359101797e4c8b3b + React-utils: d16c1d2251c088ad817996621947d0ac8167b46c + ReactCommon: 2aa35648354bd4c4665b9a5084a7d37097b89c10 + RNAudioRecorderPlayer: f790fc1afb118552ae6285d60adde52ee6b5d9ef RNCClipboard: 60fed4b71560d7bfe40e9d35dea9762b024da86d RNCMaskedView: 0e1bc4bfa8365eba5fbbb71e07fbdc0555249489 RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 - RNGestureHandler: deda62b8339496ba721a45e0f3e2d7a319932cee + RNGestureHandler: 67fb54b3e6ca338a8044e85cd6f340265aa41091 RNImageCropPicker: 14fe1c29298fb4018f3186f455c475ab107da332 RNReactNativeHapticFeedback: afa5bf2794aecbb2dba2525329253da0d66656df - RNReanimated: beb07f7f900543928467da8107c175d1e57a1049 - RNScreens: b582cb834dc4133307562e930e8fa914b8c04ef2 + RNReanimated: 15a855719335a6b655a214531e86d806edfd49da + RNScreens: 17e2f657f1b09a71ec3c821368a04acbb7ebcb46 RNShare: d82e10f6b7677f4b0048c23709bd04098d5aee6c RNSVG: ba3e7232f45e34b7b47e74472386cf4e1a676d0a SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 - Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 + Yoga: d17d2cc8105eed528474683b42e2ea310e1daf61 PODFILE CHECKSUM: 90406e1e85c82b37484f5d746afa45c0637bb4b3 -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/examples/TypeScriptMessaging/ios/TypeScriptMessaging/Info.plist b/examples/TypeScriptMessaging/ios/TypeScriptMessaging/Info.plist index dcb3449a24..73a387d92e 100644 --- a/examples/TypeScriptMessaging/ios/TypeScriptMessaging/Info.plist +++ b/examples/TypeScriptMessaging/ios/TypeScriptMessaging/Info.plist @@ -56,5 +56,8 @@ $(PRODUCT_NAME) would like to save photos to your photo gallery NSMicrophoneUsageDescription $(PRODUCT_NAME) would like to use your microphone (for videos) + NSMicrophoneUsageDescription + Give $(PRODUCT_NAME) permission to use your microphone. Your record wont be shared + without your permission. \ No newline at end of file diff --git a/examples/TypeScriptMessaging/package.json b/examples/TypeScriptMessaging/package.json index bd371ad125..20646e82c9 100644 --- a/examples/TypeScriptMessaging/package.json +++ b/examples/TypeScriptMessaging/package.json @@ -20,6 +20,7 @@ "@react-navigation/stack": "^6.2.0", "@stream-io/flat-list-mvcp": "0.10.3", "react": "18.2.0", + "react-native-audio-recorder-player": "3.6.6", "react-native": "^0.73.6", "react-native-document-picker": "^9.0.1", "react-native-fs": "^2.18.0", @@ -33,7 +34,7 @@ "react-native-screens": "^3.28.0", "react-native-share": "^8.2.2", "react-native-svg": "^14.0.0", - "react-native-video": "6.0.0-beta.5", + "react-native-video": "6.0.0-beta.8", "stream-chat-react-native": "link:../../package/native-package", "stream-chat-react-native-core": "link:../../package" }, diff --git a/examples/TypeScriptMessaging/useStreamChatTheme.ts b/examples/TypeScriptMessaging/useStreamChatTheme.ts index 6766cb785e..88dc6442b1 100644 --- a/examples/TypeScriptMessaging/useStreamChatTheme.ts +++ b/examples/TypeScriptMessaging/useStreamChatTheme.ts @@ -25,6 +25,7 @@ const getChatStyle = (colorScheme: string): DeepPartial => ({ grey_gainsboro: '#2D2F2F', grey_whisper: '#1C1E22', icon_background: '#FFFFFF', + light_gray: '#272A30', modal_shadow: '#000000', overlay: '#FFFFFFCC', // CC = 80% opacity shadow_icon: '#00000080', // 80 = 50% opacity @@ -48,6 +49,7 @@ const getChatStyle = (colorScheme: string): DeepPartial => ({ grey_gainsboro: '#DBDBDB', grey_whisper: '#ECEBEB', icon_background: '#FFFFFF', + light_gray: '#DBDDE1', modal_shadow: '#00000099', // 99 = 60% opacity; x=0, y= 1, radius=4 overlay: '#00000099', // 99 = 60% opacity shadow_icon: '#00000040', // 40 = 25% opacity; x=0, y=0, radius=4 diff --git a/examples/TypeScriptMessaging/yarn.lock b/examples/TypeScriptMessaging/yarn.lock index 293c185dac..9525a2e15b 100644 --- a/examples/TypeScriptMessaging/yarn.lock +++ b/examples/TypeScriptMessaging/yarn.lock @@ -40,7 +40,7 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.14.0", "@babel/core@^7.20.0": +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.20.0": version "7.22.1" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.1.tgz#5de51c5206f4c6f5533562838337a603c1033cfd" integrity sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA== @@ -525,7 +525,7 @@ "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.13.0", "@babel/plugin-proposal-class-properties@^7.18.0": +"@babel/plugin-proposal-class-properties@^7.13.0", "@babel/plugin-proposal-class-properties@^7.18.0": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== @@ -541,7 +541,7 @@ "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-syntax-export-default-from" "^7.18.6" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.0.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8", "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.0": +"@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8", "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.0": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== @@ -557,7 +557,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.0.0", "@babel/plugin-proposal-object-rest-spread@^7.20.0": +"@babel/plugin-proposal-object-rest-spread@^7.20.0": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== @@ -576,7 +576,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.0.0", "@babel/plugin-proposal-optional-chaining@^7.13.12", "@babel/plugin-proposal-optional-chaining@^7.20.0": +"@babel/plugin-proposal-optional-chaining@^7.13.12", "@babel/plugin-proposal-optional-chaining@^7.20.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== @@ -631,7 +631,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-dynamic-import@^7.0.0", "@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3": +"@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== @@ -659,7 +659,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-syntax-flow@^7.18.0", "@babel/plugin-syntax-flow@^7.18.6", "@babel/plugin-syntax-flow@^7.2.0": +"@babel/plugin-syntax-flow@^7.18.0", "@babel/plugin-syntax-flow@^7.18.6": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.21.4.tgz#3e37fca4f06d93567c1cd9b75156422e90a67107" integrity sha512-l9xd3N+XG4fZRxEP3vXdK6RW7vN1Uf5dxzRC/09wV86wqZ/YYQooBIGNsiRdfNR3/q2/5pPzV4B54J/9ctX5jw== @@ -789,15 +789,6 @@ "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-transform-async-to-generator@^7.0.0", "@babel/plugin-transform-async-to-generator@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354" - integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q== - dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-remap-async-to-generator" "^7.18.9" - "@babel/plugin-transform-async-to-generator@^7.20.0": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz#d1f513c7a8a506d43f47df2bf25f9254b0b051fa" @@ -807,6 +798,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-remap-async-to-generator" "^7.22.20" +"@babel/plugin-transform-async-to-generator@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354" + integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-transform-block-scoped-functions@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" @@ -861,13 +861,6 @@ "@babel/helper-plugin-utils" "^7.21.5" "@babel/template" "^7.20.7" -"@babel/plugin-transform-destructuring@^7.0.0", "@babel/plugin-transform-destructuring@^7.21.3": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz#73b46d0fd11cd6ef57dea8a381b1215f4959d401" - integrity sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-transform-destructuring@^7.20.0": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz#8c9ee68228b12ae3dff986e56ed1ba4f3c446311" @@ -875,6 +868,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-destructuring@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz#73b46d0fd11cd6ef57dea8a381b1215f4959d401" + integrity sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" @@ -898,7 +898,7 @@ "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-transform-exponentiation-operator@^7.0.0", "@babel/plugin-transform-exponentiation-operator@^7.18.6": +"@babel/plugin-transform-exponentiation-operator@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== @@ -914,14 +914,6 @@ "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-transform-flow-strip-types@^7.0.0", "@babel/plugin-transform-flow-strip-types@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.21.0.tgz#6aeca0adcb81dc627c8986e770bfaa4d9812aff5" - integrity sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-flow" "^7.18.6" - "@babel/plugin-transform-flow-strip-types@^7.20.0": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz#cfa7ca159cc3306fab526fc67091556b51af26ff" @@ -930,7 +922,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-flow" "^7.23.3" -"@babel/plugin-transform-for-of@^7.0.0", "@babel/plugin-transform-for-of@^7.21.5": +"@babel/plugin-transform-flow-strip-types@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.21.0.tgz#6aeca0adcb81dc627c8986e770bfaa4d9812aff5" + integrity sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-flow" "^7.18.6" + +"@babel/plugin-transform-for-of@^7.21.5": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc" integrity sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ== @@ -1042,7 +1042,7 @@ "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-transform-object-assign@^7.0.0", "@babel/plugin-transform-object-assign@^7.16.7": +"@babel/plugin-transform-object-assign@^7.16.7": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.18.6.tgz#7830b4b6f83e1374a5afb9f6111bcfaea872cdd2" integrity sha512-mQisZ3JfqWh2gVXvfqYCAAyRs6+7oev+myBsTwW5RnPhYXOTuCEw2oe3YgxlXMViXUS53lG8koulI7mJ+8JE+A== @@ -1167,7 +1167,7 @@ "@babel/plugin-syntax-jsx" "^7.21.4" "@babel/types" "^7.22.3" -"@babel/plugin-transform-regenerator@^7.0.0", "@babel/plugin-transform-regenerator@^7.21.5": +"@babel/plugin-transform-regenerator@^7.21.5": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz#576c62f9923f94bcb1c855adc53561fd7913724e" integrity sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w== @@ -1216,7 +1216,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-template-literals@^7.0.0", "@babel/plugin-transform-template-literals@^7.18.9": +"@babel/plugin-transform-template-literals@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== @@ -1404,7 +1404,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.0", "@babel/runtime@^7.16.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.8.4": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb" integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ== @@ -2433,7 +2433,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.9": version "7.0.12" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== @@ -2695,12 +2695,7 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2995,11 +2990,6 @@ base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -3596,6 +3586,11 @@ domutils@^3.0.1: domelementtype "^2.3.0" domhandler "^5.0.3" +dooboolab-welcome@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dooboolab-welcome/-/dooboolab-welcome-1.3.2.tgz#4928595312f0429b4ea1b485ba8767bae6acdab7" + integrity sha512-2NbMaIIURElxEf/UAoVUFlXrO+7n/FRhLCiQlk4fkbGRh9cJ3/f8VEMPveR9m4Ug2l2Zey+UCXjd6EcBqHJ5bw== + ecdsa-sig-formatter@1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" @@ -3633,11 +3628,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -4108,14 +4098,6 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-loader@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" - integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -5290,7 +5272,7 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json5@^2.1.2, json5@^2.2.2, json5@^2.2.3: +json5@^2.2.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -5378,15 +5360,6 @@ linkifyjs@^4.1.1: resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.1.tgz#73d427e3bbaaf4ca8e71c589ad4ffda11a9a5fde" integrity sha512-zFN/CTVmbcVef+WaDXT63dNzzkfRBKT1j464NJQkV7iSgJU0sLBus9W0HBwnXK13/hf168pbrx/V/bjEHOXNHA== -loader-utils@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" - integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -5587,52 +5560,6 @@ metro-minify-terser@0.80.6: dependencies: terser "^5.15.0" -metro-react-native-babel-preset@0.66.2: - version "0.66.2" - resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.66.2.tgz#fddebcf413ad4ea617d4f47f7c1da401052de734" - integrity sha512-H/nLBAz0MgfDloSe1FjyH4EnbokHFdncyERvLPXDACY3ROVRCeUyFNo70ywRGXW2NMbrV4H7KUyU4zkfWhC2HQ== - dependencies: - "@babel/core" "^7.14.0" - "@babel/plugin-proposal-class-properties" "^7.0.0" - "@babel/plugin-proposal-export-default-from" "^7.0.0" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.0.0" - "@babel/plugin-proposal-object-rest-spread" "^7.0.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" - "@babel/plugin-proposal-optional-chaining" "^7.0.0" - "@babel/plugin-syntax-dynamic-import" "^7.0.0" - "@babel/plugin-syntax-export-default-from" "^7.0.0" - "@babel/plugin-syntax-flow" "^7.2.0" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0" - "@babel/plugin-syntax-optional-chaining" "^7.0.0" - "@babel/plugin-transform-arrow-functions" "^7.0.0" - "@babel/plugin-transform-async-to-generator" "^7.0.0" - "@babel/plugin-transform-block-scoping" "^7.0.0" - "@babel/plugin-transform-classes" "^7.0.0" - "@babel/plugin-transform-computed-properties" "^7.0.0" - "@babel/plugin-transform-destructuring" "^7.0.0" - "@babel/plugin-transform-exponentiation-operator" "^7.0.0" - "@babel/plugin-transform-flow-strip-types" "^7.0.0" - "@babel/plugin-transform-for-of" "^7.0.0" - "@babel/plugin-transform-function-name" "^7.0.0" - "@babel/plugin-transform-literals" "^7.0.0" - "@babel/plugin-transform-modules-commonjs" "^7.0.0" - "@babel/plugin-transform-object-assign" "^7.0.0" - "@babel/plugin-transform-parameters" "^7.0.0" - "@babel/plugin-transform-react-display-name" "^7.0.0" - "@babel/plugin-transform-react-jsx" "^7.0.0" - "@babel/plugin-transform-react-jsx-self" "^7.0.0" - "@babel/plugin-transform-react-jsx-source" "^7.0.0" - "@babel/plugin-transform-regenerator" "^7.0.0" - "@babel/plugin-transform-runtime" "^7.0.0" - "@babel/plugin-transform-shorthand-properties" "^7.0.0" - "@babel/plugin-transform-spread" "^7.0.0" - "@babel/plugin-transform-sticky-regex" "^7.0.0" - "@babel/plugin-transform-template-literals" "^7.0.0" - "@babel/plugin-transform-typescript" "^7.5.0" - "@babel/plugin-transform-unicode-regex" "^7.0.0" - "@babel/template" "^7.0.0" - react-refresh "^0.4.0" - metro-resolver@0.80.6: version "0.80.6" resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.80.6.tgz#b648b8c661bc4cf091efd11affa010dd11f58bec" @@ -6343,6 +6270,13 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-native-audio-recorder-player@3.6.6: + version "3.6.6" + resolved "https://registry.yarnpkg.com/react-native-audio-recorder-player/-/react-native-audio-recorder-player-3.6.6.tgz#76a4a62bbc2eb28a04bc6ce1beb802ec546810a5" + integrity sha512-Obw5c1uBZy+yHle/ms9zUdBjknS0Zhtqq7BXejZwbM4a1no2T0J0zQkmS5oIpmZ5mTslj7PaNKvTgM3hkNgjdA== + dependencies: + dooboolab-welcome "^1.3.2" + react-native-document-picker@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/react-native-document-picker/-/react-native-document-picker-9.0.1.tgz#a5ceec157f84dbadb85fe717c657569755f4c6ca" @@ -6453,10 +6387,10 @@ react-native-url-polyfill@^1.3.0: dependencies: whatwg-url-without-unicode "8.0.0-3" -react-native-video@6.0.0-beta.5: - version "6.0.0-beta.5" - resolved "https://registry.yarnpkg.com/react-native-video/-/react-native-video-6.0.0-beta.5.tgz#9559d7a6c43c0cf68ef8d8780b599460ec7a44b8" - integrity sha512-dAfIXvtxsMI8TE3Q+1MHTP1brq3/V2VsPKVDtU8E+JcF963y5upnBb8JFiG8Yl4s4qAoQum2P02fZE30stQOHg== +react-native-video@6.0.0-beta.8: + version "6.0.0-beta.8" + resolved "https://registry.yarnpkg.com/react-native-video/-/react-native-video-6.0.0-beta.8.tgz#76597ea61d3791beb14731e9e469ddb86e88adf9" + integrity sha512-pWAJKhP6yt7sIe/u4vi292sWaHNgqSwZbKzauRKBHHEJr2pY/EG7Lis5hDI+rOJlQ0NmsPEurbUPH5TGQsFJFw== react-native@*, react-native@^0.73.6: version "0.73.6" @@ -6507,11 +6441,6 @@ react-refresh@^0.14.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== -react-refresh@^0.4.0: - version "0.4.3" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.4.3.tgz#966f1750c191672e76e16c2efa569150cc73ab53" - integrity sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA== - react-shallow-renderer@^16.15.0: version "16.15.0" resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz#48fb2cf9b23d23cde96708fe5273a7d3446f4457" @@ -6739,15 +6668,6 @@ scheduler@0.24.0-canary-efb381bbf-20230505: dependencies: loose-envify "^1.1.0" -schema-utils@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.2.tgz#36c10abca6f7577aeae136c804b0c741edeadc99" - integrity sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -6962,20 +6882,17 @@ statuses@~1.5.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -stream-chat-react-native-core@5.27.0: - version "5.27.0" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.27.0.tgz#1cead940ca3a26555a97039e2e893e9698cff7b1" - integrity sha512-vIfq2pGMwDwYjnxgBfAOfVLo3nvVc+GBCFgJHtsEPm9o3HXZQNoxrTg5adFgKPQ7A6cC2MEs1uluWkmdRdFhpQ== +stream-chat-react-native-core@5.28.0: + version "5.28.0" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.28.0.tgz#66449c014e034bf6041f364ccf8f25302b8ff580" + integrity sha512-G+NmlCYPO84OE2p1soNoKRi/RlfM0ICnw3gm5wq8bPWaBvt1XozZuX6usO2+nEGVcdhaKTvXqX5i4+6FEGDPBw== dependencies: - "@babel/runtime" "^7.12.5" "@gorhom/bottom-sheet" "4.4.8" dayjs "1.10.5" emoji-regex "^10.3.0" - file-loader "6.2.0" i18next "20.2.4" linkifyjs "^4.1.1" lodash-es "4.17.21" - metro-react-native-babel-preset "0.66.2" mime-types "^2.1.34" path "0.12.7" react-native-markdown-package "1.8.2" diff --git a/package/expo-package/src/handlers/Audio.ts b/package/expo-package/src/handlers/Audio.ts new file mode 100644 index 0000000000..f67c3fcb94 --- /dev/null +++ b/package/expo-package/src/handlers/Audio.ts @@ -0,0 +1,270 @@ +import { AudioComponent } from '../optionalDependencies/Video'; + +export enum AndroidOutputFormat { + DEFAULT = 0, + THREE_GPP = 1, + MPEG_4 = 2, + AMR_NB = 3, + AMR_WB = 4, + AAC_ADIF = 5, + AAC_ADTS = 6, + RTP_AVP = 7, + MPEG2TS = 8, + WEBM = 9, +} + +// @docsMissing +export enum AndroidAudioEncoder { + DEFAULT = 0, + AMR_NB = 1, + AMR_WB = 2, + AAC = 3, + HE_AAC = 4, + AAC_ELD = 5, +} + +export enum IOSOutputFormat { + LINEARPCM = 'lpcm', + AC3 = 'ac-3', + '60958AC3' = 'cac3', + APPLEIMA4 = 'ima4', + MPEG4AAC = 'aac ', + MPEG4CELP = 'celp', + MPEG4HVXC = 'hvxc', + MPEG4TWINVQ = 'twvq', + MACE3 = 'MAC3', + MACE6 = 'MAC6', + ULAW = 'ulaw', + ALAW = 'alaw', + QDESIGN = 'QDMC', + QDESIGN2 = 'QDM2', + QUALCOMM = 'Qclp', + MPEGLAYER1 = '.mp1', + MPEGLAYER2 = '.mp2', + MPEGLAYER3 = '.mp3', + APPLELOSSLESS = 'alac', + MPEG4AAC_HE = 'aach', + MPEG4AAC_LD = 'aacl', + MPEG4AAC_ELD = 'aace', + MPEG4AAC_ELD_SBR = 'aacf', + MPEG4AAC_ELD_V2 = 'aacg', + MPEG4AAC_HE_V2 = 'aacp', + MPEG4AAC_SPATIAL = 'aacs', + AMR = 'samr', + AMR_WB = 'sawb', + AUDIBLE = 'AUDB', + ILBC = 'ilbc', + DVIINTELIMA = 0x6d730011, + MICROSOFTGSM = 0x6d730031, + AES3 = 'aes3', + ENHANCEDAC3 = 'ec-3', +} + +export enum IOSAudioQuality { + MIN = 0, + LOW = 0x20, + MEDIUM = 0x40, + HIGH = 0x60, + MAX = 0x7f, +} + +export type RecordingOptionsAndroid = { + /** + * The desired audio encoder. See the [`AndroidAudioEncoder`](#androidaudioencoder) enum for all valid values. + */ + audioEncoder: AndroidAudioEncoder | number; + /** + * The desired file extension. Example valid values are `.3gp` and `.m4a`. + * For more information, see the [Android docs](https://developer.android.com/guide/topics/media/media-formats) + * for supported output formats. + */ + extension: string; + /** + * The desired file format. See the [`AndroidOutputFormat`](#androidoutputformat) enum for all valid values. + */ + outputFormat: AndroidOutputFormat | number; + /** + * The desired bit rate. + * + * Note that `prepareToRecordAsync()` may perform additional checks on the parameter to make sure whether the specified + * bit rate is applicable, and sometimes the passed bitRate will be clipped internally to ensure the audio recording + * can proceed smoothly based on the capabilities of the platform. + * + * @example `128000` + */ + bitRate?: number; + /** + * The desired maximum file size in bytes, after which the recording will stop (but `stopAndUnloadAsync()` must still + * be called after this point). + * + * @example `65536` + */ + maxFileSize?: number; + /** + * The desired number of channels. + * + * Note that `prepareToRecordAsync()` may perform additional checks on the parameter to make sure whether the specified + * number of audio channels are applicable. + * + * @example `1`, `2` + */ + numberOfChannels?: number; + /** + * The desired sample rate. + * + * Note that the sampling rate depends on the format for the audio recording, as well as the capabilities of the platform. + * For instance, the sampling rate supported by AAC audio coding standard ranges from 8 to 96 kHz, + * the sampling rate supported by AMRNB is 8kHz, and the sampling rate supported by AMRWB is 16kHz. + * Please consult with the related audio coding standard for the supported audio sampling rate. + * + * @example 44100 + */ + sampleRate?: number; +}; + +export type RecordingOptionsIOS = { + /** + * The desired audio quality. See the [`IOSAudioQuality`](#iosaudioquality) enum for all valid values. + */ + audioQuality: IOSAudioQuality | number; + /** + * The desired bit rate. + * + * @example `128000` + */ + bitRate: number; + /** + * The desired file extension. + * + * @example `'.caf'` + */ + extension: string; + /** + * The desired number of channels. + * + * @example `1`, `2` + */ + numberOfChannels: number; + /** + * The desired sample rate. + * + * @example `44100` + */ + sampleRate: number; + /** + * The desired bit depth hint. + * + * @example `16` + */ + bitDepthHint?: number; + /** + * The desired bit rate strategy. See the next section for an enumeration of all valid values of `bitRateStrategy`. + */ + bitRateStrategy?: number; + /** + * The desired PCM bit depth. + * + * @example `16` + */ + linearPCMBitDepth?: number; + /** + * A boolean describing if the PCM data should be formatted in big endian. + */ + linearPCMIsBigEndian?: boolean; + /** + * A boolean describing if the PCM data should be encoded in floating point or integral values. + */ + linearPCMIsFloat?: boolean; + /** + * The desired file format. See the [`IOSOutputFormat`](#iosoutputformat) enum for all valid values. + */ + outputFormat?: string | IOSOutputFormat | number; +}; + +// @docsMissing +export type RecordingOptionsWeb = { + bitsPerSecond?: number; + mimeType?: string; +}; + +export type RecordingOptions = { + /** + * Recording options for the Android platform. + */ + android: RecordingOptionsAndroid; + /** + * Recording options for the iOS platform. + */ + ios: RecordingOptionsIOS; + /** + * Recording options for the Web platform. + */ + web: RecordingOptionsWeb; + /** + * A boolean that determines whether audio level information will be part of the status object under the "metering" key. + */ + isMeteringEnabled?: boolean; + /** + * A boolean that hints to keep the audio active after `prepareToRecordAsync` completes. + * Setting this value can improve the speed at which the recording starts. Only set this value to `true` when you call `startAsync` + * immediately after `prepareToRecordAsync`. This value is automatically set when using `Audio.recording.createAsync()`. + */ + keepAudioActiveHint?: boolean; +}; + +export const Audio = AudioComponent + ? { + startRecording: async (recordingOptions: RecordingOptions, onRecordingStatusUpdate) => { + try { + console.log('Requesting permissions..'); + const permissionsGranted = await AudioComponent.getPermissionsAsync().granted; + if (!permissionsGranted) { + await AudioComponent.requestPermissionsAsync(); + } + await AudioComponent.setAudioModeAsync({ + allowsRecordingIOS: true, + playsInSilentModeIOS: true, + }); + console.log('Starting recording..'); + const androidOptions = { + audioEncoder: AndroidAudioEncoder.AAC, + extension: '.aac', + outputFormat: AndroidOutputFormat.AAC_ADTS, + }; + const iosOptions = { + audioQuality: IOSAudioQuality.HIGH, + bitRate: 128000, + extension: '.aac', + numberOfChannels: 2, + outputFormat: IOSOutputFormat.MPEG4AAC, + sampleRate: 44100, + }; + const options = { + ...recordingOptions, + android: androidOptions, + ios: iosOptions, + web: {}, + }; + + const { recording } = await AudioComponent.Recording.createAsync( + options, + onRecordingStatusUpdate, + ); + return { accessGranted: true, recording }; + } catch (error) { + console.error('Failed to start recording', error); + return { accessGranted: false, recording: null }; + } + }, + stopRecording: async () => { + try { + console.log('Stopping recording..'); + await AudioComponent.setAudioModeAsync({ + allowsRecordingIOS: false, + }); + } catch (error) { + console.log('Error stopping recoding', error); + } + }, + } + : null; diff --git a/package/expo-package/src/handlers/index.ts b/package/expo-package/src/handlers/index.ts index ede055d7b3..2518c330db 100644 --- a/package/expo-package/src/handlers/index.ts +++ b/package/expo-package/src/handlers/index.ts @@ -1,3 +1,4 @@ +export * from './Audio'; export * from './compressImage'; export * from './deleteFile'; export * from './getLocalAssetUri'; diff --git a/package/expo-package/src/index.js b/package/expo-package/src/index.js index 633e290b3a..818c0a53a6 100644 --- a/package/expo-package/src/index.js +++ b/package/expo-package/src/index.js @@ -3,6 +3,7 @@ import { FlatList } from 'react-native'; import { registerNativeHandlers } from 'stream-chat-react-native-core'; import { + Audio, compressImage, deleteFile, getLocalAssetUri, @@ -24,6 +25,7 @@ import { } from './optionalDependencies'; registerNativeHandlers({ + Audio, compressImage, deleteFile, FlatList, diff --git a/package/jest-setup.js b/package/jest-setup.js index 045737e9de..2bf6b4c61e 100644 --- a/package/jest-setup.js +++ b/package/jest-setup.js @@ -15,6 +15,10 @@ export const setNetInfoFetchMock = (fn) => { netInfoFetch = fn; }; registerNativeHandlers({ + Audio: { + startPlayer: jest.fn(), + stopPlayer: jest.fn(), + }, compressImage: () => null, deleteFile: () => null, FlatList, diff --git a/package/native-package/package.json b/package/native-package/package.json index 9207d74aa1..f7b69b5786 100644 --- a/package/native-package/package.json +++ b/package/native-package/package.json @@ -25,6 +25,7 @@ "react-native-image-crop-picker": ">=0.33.2", "react-native-image-resizer": ">=1.4.2", "react-native-share": ">=4.1.0", + "react-native-audio-recorder-player": ">=3.6.4", "react-native-video": ">=5.2.1" }, "peerDependenciesMeta": { @@ -40,6 +41,9 @@ "react-native-haptic-feedback": { "optional": true }, + "react-native-audio-recorder-player": { + "optional": true + }, "react-native-video": { "optional": true } diff --git a/package/native-package/src/handlers/Sound.tsx b/package/native-package/src/handlers/Sound.tsx index 19ffcbf46c..14638c0641 100644 --- a/package/native-package/src/handlers/Sound.tsx +++ b/package/native-package/src/handlers/Sound.tsx @@ -6,7 +6,7 @@ export const Sound = { initializeSound: null, // eslint-disable-next-line react/display-name Player: AudioVideoPlayer - ? ({ onBuffer, onEnd, onLoad, onProgress, paused, soundRef, style, uri }) => ( + ? ({ onBuffer, onEnd, onLoad, onProgress, paused, rate, soundRef, style, uri }) => ( { + const isRN71orAbove = Platform.constants.reactNativeVersion?.minor >= 71; + const isAndroid13orAbove = (Platform.Version as number) >= 33; + const shouldCheckForMediaPermissions = isRN71orAbove && isAndroid13orAbove; + + const getCheckPermissionPromise = () => { + if (shouldCheckForMediaPermissions) { + return Promise.all([ + PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO), + PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.READ_MEDIA_AUDIO), + ]).then( + ([hasRecordAudioPermission, hasReadMediaAudioPermission]) => + hasRecordAudioPermission && hasReadMediaAudioPermission, + ); + } else { + return Promise.all([ + PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO), + PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE), + ]).then( + ([hasRecordAudioPermission, hasReadExternalStorage]) => + hasRecordAudioPermission && hasReadExternalStorage, + ); + } + }; + const hasPermission = await getCheckPermissionPromise(); + if (!hasPermission) { + const getRequestPermissionPromise = () => { + if (shouldCheckForMediaPermissions) { + return PermissionsAndroid.requestMultiple([ + PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, + PermissionsAndroid.PERMISSIONS.READ_MEDIA_AUDIO, + ]).then( + (statuses) => + statuses[PermissionsAndroid.PERMISSIONS.RECORD_AUDIO] === + PermissionsAndroid.RESULTS.GRANTED && + statuses[PermissionsAndroid.PERMISSIONS.READ_MEDIA_AUDIO] === + PermissionsAndroid.RESULTS.GRANTED, + ); + } else { + return PermissionsAndroid.requestMultiple([ + PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, + PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, + ]).then( + (statuses) => + statuses[PermissionsAndroid.PERMISSIONS.RECORD_AUDIO] === + PermissionsAndroid.RESULTS.GRANTED && + statuses[PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE] === + PermissionsAndroid.RESULTS.GRANTED, + ); + } + }; + const granted = await getRequestPermissionPromise(); + return granted; + } + return true; +}; + +export const Audio = AudioRecorderPackage + ? { + pausePlayer: async () => { + console.log('Pause Player..'); + await audioRecorderPlayer.pausePlayer(); + }, + resumePlayer: async () => { + console.log('Resume Player..'); + await audioRecorderPlayer.resumePlayer(); + }, + startPlayer: async (uri, _, onPlaybackStatusUpdate) => { + try { + console.log('Starting Player..'); + const playback = await audioRecorderPlayer.startPlayer(uri); + console.log({ playback }); + audioRecorderPlayer.addPlayBackListener((status) => { + onPlaybackStatusUpdate(status); + }); + } catch (error) { + console.log('Error starting player', error); + } + }, + startRecording: async (options: RecordingOptions, onRecordingStatusUpdate) => { + console.log('Starting recording..'); + if (Platform.OS === 'android') { + try { + await verifyAndroidPermissions(); + } catch (err) { + console.warn('Audio Recording Permissions error', err); + return; + } + } + try { + const path = Platform.select({ + android: `${RNFS.CachesDirectoryPath}/sound.aac`, + ios: 'sound.aac', + }); + const audioSet = { + AudioEncoderAndroid: AudioEncoderAndroidType.AAC, + AudioSourceAndroid: AudioSourceAndroidType.MIC, + AVEncoderAudioQualityKeyIOS: AVEncoderAudioQualityIOSType.high, + AVFormatIDKeyIOS: AVEncodingOption.aac, + AVModeIOS: AVModeIOSOption.measurement, + AVNumberOfChannelsKeyIOS: 2, + OutputFormatAndroid: OutputFormatAndroidType.AAC_ADTS, + }; + const recording = await audioRecorderPlayer.startRecorder( + path, + audioSet, + options?.isMeteringEnabled, + ); + + audioRecorderPlayer.addRecordBackListener((status) => { + onRecordingStatusUpdate(status); + }); + return { accessGranted: true, recording }; + } catch (error) { + console.error('Failed to start recording', error); + return { accessGranted: false, recording: null }; + } + }, + stopPlayer: async () => { + console.log('Stopping player..'); + await audioRecorderPlayer.stopPlayer(); + audioRecorderPlayer.removePlayBackListener(); + }, + stopRecording: async () => { + console.log('Stopping recording..'); + await audioRecorderPlayer.stopRecorder(); + audioRecorderPlayer.removeRecordBackListener(); + }, + } + : null; diff --git a/package/native-package/src/optionalDependencies/index.ts b/package/native-package/src/optionalDependencies/index.ts index 24262302bf..fbb2228b1f 100644 --- a/package/native-package/src/optionalDependencies/index.ts +++ b/package/native-package/src/optionalDependencies/index.ts @@ -1,3 +1,4 @@ +export * from './Audio'; export * from './shareImage'; export * from './Video'; export * from './triggerHaptic'; diff --git a/package/src/components/Attachment/Attachment.tsx b/package/src/components/Attachment/Attachment.tsx index 19467f610e..e7ace05600 100644 --- a/package/src/components/Attachment/Attachment.tsx +++ b/package/src/components/Attachment/Attachment.tsx @@ -87,7 +87,11 @@ const AttachmentWithContext = < ); } - if (attachment.type === 'file' || attachment.type === 'audio') { + if ( + attachment.type === 'file' || + attachment.type === 'audio' || + attachment.type === 'voiceRecording' + ) { return ; } @@ -117,7 +121,8 @@ const areEqual = ; onLoad: (index: string, duration: number) => void; onPlayPause: (index: string, pausedStatus?: boolean) => void; onProgress: (index: string, currentTime?: number, hasEnd?: boolean) => void; + hideProgressBar?: boolean; + showSpeedSettings?: boolean; testID?: string; }; -const AudioAttachmentWithContext = (props: AudioAttachmentPropsWithContext) => { +/** + * AudioAttachment + * UI Component to preview the audio files + */ +export const AudioAttachment = (props: AudioAttachmentProps) => { + const [width, setWidth] = useState(0); + const [currentSpeed, setCurrentSpeed] = useState(1.0); const soundRef = React.useRef(null); - const { item, onLoad, onPlayPause, onProgress } = props; + const { + hideProgressBar = false, + item, + onLoad, + onPlayPause, + onProgress, + showSpeedSettings = false, + testID, + } = props; + /** This is for Native CLI Apps */ const handleLoad = (payload: VideoPayloadData) => { - onLoad(item.id, payload.duration); + onLoad(item.id, item.duration || payload.duration); }; + /** This is for Native CLI Apps */ const handleProgress = (data: VideoProgressData) => { - if (data.currentTime && data.seekableDuration) { + if (data.currentTime <= data.seekableDuration) { onProgress(item.id, data.currentTime); } }; + /** This is for Native CLI Apps */ + const handleEnd = () => { + onPlayPause(item.id, true); + onProgress(item.id, item.duration, true); + }; + const handlePlayPause = async (isPausedStatusAvailable?: boolean) => { if (soundRef.current) { if (isPausedStatusAvailable === undefined) { @@ -103,9 +76,13 @@ const AudioAttachmentWithContext = (props: AudioAttachmentPropsWithContext) => { if (soundRef.current.setPositionAsync) soundRef.current.setPositionAsync(0); } if (item.paused) { + // For expo CLI if (soundRef.current.playAsync) await soundRef.current.playAsync(); + if (soundRef.current.setProgressUpdateIntervalAsync) + await soundRef.current.setProgressUpdateIntervalAsync(60); onPlayPause(item.id, false); } else { + // For expo CLI if (soundRef.current.pauseAsync) await soundRef.current.pauseAsync(); onPlayPause(item.id, true); } @@ -117,17 +94,15 @@ const AudioAttachmentWithContext = (props: AudioAttachmentPropsWithContext) => { const handleProgressDrag = async (position: number) => { onProgress(item.id, position); + // For native CLI if (soundRef.current?.seek) soundRef.current.seek(position); + // For expo CLI if (soundRef.current?.setPositionAsync) { await soundRef.current.setPositionAsync(position * 1000); } }; - const handleEnd = () => { - onPlayPause(item.id, true); - onProgress(item.id, item.duration, true); - }; - + /** For Expo CLI */ const onPlaybackStatusUpdate = (playbackStatus: PlaybackStatus) => { if (!playbackStatus.isLoaded) { // Update your UI for the unloaded state @@ -136,7 +111,10 @@ const AudioAttachmentWithContext = (props: AudioAttachmentPropsWithContext) => { } } else { const { durationMillis, positionMillis } = playbackStatus; - onLoad(item.id, durationMillis / 1000); + // This is done for Expo CLI where we don't get file duration from file picker + if (item.duration === 0) { + onLoad(item.id, durationMillis / 1000); + } // Update your UI for the loaded state if (playbackStatus.isPlaying) { // Update your UI for the playing state @@ -187,24 +165,53 @@ const AudioAttachmentWithContext = (props: AudioAttachmentPropsWithContext) => { if (soundRef.current.pauseAsync) await soundRef.current.pauseAsync(); } else { if (soundRef.current.playAsync) await soundRef.current.playAsync(); + if (soundRef.current.setProgressUpdateIntervalAsync) + await soundRef.current.setProgressUpdateIntervalAsync(60); } } }; + // For expo CLI if (!Sound.Player) { initalPlayPause(); } }, [item.paused]); + const onSpeedChangeHandler = async () => { + if (currentSpeed === 2.0) { + setCurrentSpeed(1.0); + if (soundRef.current && soundRef.current.setRateAsync) { + await soundRef.current.setRateAsync(1.0); + } + } else { + if (currentSpeed === 1.0) { + setCurrentSpeed(1.5); + if (soundRef.current && soundRef.current.setRateAsync) { + await soundRef.current.setRateAsync(1.5); + } + } else if (currentSpeed === 1.5) { + setCurrentSpeed(2.0); + if (soundRef.current && soundRef.current.setRateAsync) { + await soundRef.current.setRateAsync(2.0); + } + } + } + }; + const { theme: { - colors: { accent_blue, black, grey_dark, static_black, static_white }, + audioAttachment: { + container, + leftContainer, + playPauseButton, + progressControlContainer, + progressDurationText, + rightContainer, + speedChangeButton, + speedChangeButtonText, + }, + colors: { accent_blue, black, grey_dark, grey_whisper, static_black, static_white, white }, messageInput: { - fileUploadPreview: { - audioAttachment: { progressControlView, progressDurationText, roundedView }, - fileContentContainer, - filenameText, - fileTextContainer, - }, + fileUploadPreview: { filenameText }, }, }, } = useTheme(); @@ -215,28 +222,40 @@ const AudioAttachmentWithContext = (props: AudioAttachmentPropsWithContext) => { ? progressValueInSeconds / 3600 >= 1 ? dayjs.duration(progressValueInSeconds, 'second').format('HH:mm:ss') : dayjs.duration(progressValueInSeconds, 'second').format('mm:ss') - : '00:00'; - - const lastIndexOfDot = item.file.name.lastIndexOf('.'); + : dayjs.duration(item.duration ?? 0, 'second').format('mm:ss'); return ( - - { + setWidth(nativeEvent.layout.width); + }} + style={[ + styles.container, + { + backgroundColor: white, + borderColor: grey_whisper, + }, + container, + ]} + testID={testID} + > + handlePlayPause()} style={[ - styles.roundedView, - roundedView, + styles.playPauseButton, { backgroundColor: static_white, shadowColor: black }, + playPauseButton, ]} > {item.paused ? ( - + ) : ( - + )} - - + + { filenameText, ]} > - {item.file.name.slice(0, 12) + '...' + item.file.name.slice(lastIndexOfDot)} + {getTrimmedAttachmentTitle(item.file.name)} - + {/* */} {Sound.Player && ( { onLoad={handleLoad} onProgress={handleProgress} paused={item.paused as boolean} + rate={currentSpeed} soundRef={soundRef} testID='sound-player' uri={item.file.uri} @@ -278,37 +292,127 @@ const AudioAttachmentWithContext = (props: AudioAttachmentPropsWithContext) => { {progressDuration} - - - + {!hideProgressBar && ( + + {item.file.waveform_data ? ( + { + if (item.file.waveform_data) { + const progress = (position / 30) * (item.duration as number); + handleProgressDrag(progress); + } + }} + progress={item.progress as number} + waveformData={item.file.waveform_data} + /> + ) : ( + + )} + + )} + {showSpeedSettings && ( + + {item.progress === 0 || item.progress === 1 ? ( + + )} ); }; -export type AudioAttachmentProps = Partial & { - item: Omit; - onLoad: (index: string, duration: number) => void; - onPlayPause: (index: string, pausedStatus?: boolean) => void; - onProgress: (index: string, currentTime?: number, hasEnd?: boolean) => void; - testID: string; -}; - -/** - * AudioAttachment - * UI Component to preview the audio files - */ -export const AudioAttachment = (props: AudioAttachmentProps) => ( - -); +const styles = StyleSheet.create({ + audioInfo: { + alignItems: 'center', + display: 'flex', + flexDirection: 'row', + }, + container: { + alignItems: 'center', + borderRadius: 12, + borderWidth: 1, + flexDirection: 'row', + paddingHorizontal: 8, + paddingVertical: 12, + }, + filenameText: { + fontSize: 14, + fontWeight: 'bold', + paddingBottom: 12, + paddingLeft: 8, + }, + leftContainer: { + justifyContent: 'space-around', + }, + playPauseButton: { + alignItems: 'center', + alignSelf: 'center', + borderRadius: 50, + display: 'flex', + elevation: 4, + justifyContent: 'center', + paddingVertical: 2, + shadowOffset: { + height: 2, + width: 0, + }, + shadowOpacity: 0.23, + shadowRadius: 2.62, + width: 36, + }, + progressControlContainer: {}, + progressDurationText: { + fontSize: 12, + paddingLeft: 10, + paddingRight: 8, + }, + rightContainer: { + marginLeft: 10, + }, + speedChangeButton: { + alignItems: 'center', + alignSelf: 'center', + borderRadius: 50, + elevation: 4, + justifyContent: 'center', + paddingVertical: 5, + shadowOffset: { + height: 2, + width: 0, + }, + shadowOpacity: 0.23, + shadowRadius: 2.62, + width: 36, + }, + speedChangeButtonText: { + fontSize: 12, + fontWeight: '500', + }, +}); AudioAttachment.displayName = 'AudioAttachment{messageInput{audioAttachment}}'; diff --git a/package/src/components/Attachment/FileAttachmentGroup.tsx b/package/src/components/Attachment/FileAttachmentGroup.tsx index 84f3c45fcc..d8898162c5 100644 --- a/package/src/components/Attachment/FileAttachmentGroup.tsx +++ b/package/src/components/Attachment/FileAttachmentGroup.tsx @@ -19,23 +19,6 @@ import { isAudioPackageAvailable } from '../../native'; import type { DefaultStreamChatGenerics } from '../../types/types'; -const FILE_PREVIEW_HEIGHT = 60; - -const styles = StyleSheet.create({ - container: { - padding: 4, - }, - fileContainer: { - borderRadius: 12, - borderWidth: 1, - flexDirection: 'row', - height: FILE_PREVIEW_HEIGHT, - justifyContent: 'space-between', - paddingLeft: 8, - paddingRight: 8, - }, -}); - export type FileAttachmentGroupPropsWithContext< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Pick, 'files'> & @@ -67,7 +50,9 @@ const FileAttachmentGroupWithContext = < const [filesToDisplay, setFilesToDisplay] = useState([]); useEffect(() => { - setFilesToDisplay(files.map((file) => ({ ...file, duration: 0, paused: true, progress: 0 }))); + setFilesToDisplay( + files.map((file) => ({ ...file, duration: file.duration || 0, paused: true, progress: 0 })), + ); }, [files]); // Handler triggered when an audio is loaded in the message input. The initial state is defined for the audio here and the duration is set. @@ -82,17 +67,17 @@ const FileAttachmentGroupWithContext = < // The handler which is triggered when the audio progresses/ the thumb is dragged in the progress control. The progressed duration is set here. const onProgress = (index: string, currentTime?: number, hasEnd?: boolean) => { - setFilesToDisplay((prevFileUploads) => - prevFileUploads.map((fileUpload, id) => ({ - ...fileUpload, + setFilesToDisplay((prevFilesToDisplay) => + prevFilesToDisplay.map((filesToDisplay, id) => ({ + ...filesToDisplay, progress: id.toString() === index ? hasEnd ? 1 : currentTime - ? currentTime / (fileUpload.duration as number) + ? currentTime / (filesToDisplay.duration as number) : 0 - : fileUpload.progress, + : filesToDisplay.progress, })), ); }; @@ -120,9 +105,8 @@ const FileAttachmentGroupWithContext = < const { theme: { - colors: { grey_whisper, white }, messageSimple: { - fileAttachmentGroup: { container }, + fileAttachmentGroup: { attachmentContainer, container }, }, }, } = useTheme(); @@ -135,39 +119,29 @@ const FileAttachmentGroupWithContext = < style={[ { paddingBottom: index !== files.length - 1 ? 4 : 0 }, stylesProp.attachmentContainer, + attachmentContainer, ]} > - {file.type === 'audio' && isAudioPackageAvailable() ? ( - - - + id: index.toString(), + paused: file.paused, + progress: file.progress, + }} + onLoad={onLoad} + onPlayPause={onPlayPause} + onProgress={onProgress} + showSpeedSettings={true} + testID='audio-attachment-preview' + /> ) : ( )} @@ -225,4 +199,10 @@ export const FileAttachmentGroup = < ); }; +const styles = StyleSheet.create({ + container: { + padding: 4, + }, +}); + FileAttachmentGroup.displayName = 'FileAttachmentGroup{messageSimple{fileAttachmentGroup}}'; diff --git a/package/src/components/Attachment/VideoThumbnail.tsx b/package/src/components/Attachment/VideoThumbnail.tsx index 14ed6cd08c..ef0ceb7891 100644 --- a/package/src/components/Attachment/VideoThumbnail.tsx +++ b/package/src/components/Attachment/VideoThumbnail.tsx @@ -55,7 +55,7 @@ export const VideoThumbnail = (props: VideoThumbnailProps) => { style={[styles.container, container, style]} > - + ); diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 76bd556157..7dc2c72c78 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -128,6 +128,12 @@ import { MessageStatus as MessageStatusDefault } from '../Message/MessageSimple/ import { ReactionList as ReactionListDefault } from '../Message/MessageSimple/ReactionList'; import { AttachButton as AttachButtonDefault } from '../MessageInput/AttachButton'; import { CommandsButton as CommandsButtonDefault } from '../MessageInput/CommandsButton'; +import { AudioRecorder as AudioRecorderDefault } from '../MessageInput/components/AudioRecorder/AudioRecorder'; +import { AudioRecordingButton as AudioRecordingButtonDefault } from '../MessageInput/components/AudioRecorder/AudioRecordingButton'; +import { AudioRecordingInProgress as AudioRecordingInProgressDefault } from '../MessageInput/components/AudioRecorder/AudioRecordingInProgress'; +import { AudioRecordingLockIndicator as AudioRecordingLockIndicatorDefault } from '../MessageInput/components/AudioRecorder/AudioRecordingLockIndicator'; +import { AudioRecordingPreview as AudioRecordingPreviewDefault } from '../MessageInput/components/AudioRecorder/AudioRecordingPreview'; +import { AudioRecordingWaveform as AudioRecordingWaveformDefault } from '../MessageInput/components/AudioRecorder/AudioRecordingWaveform'; import { InputEditingStateHeader as InputEditingStateHeaderDefault } from '../MessageInput/components/InputEditingStateHeader'; import { InputGiphySearch as InputGiphyCommandInputDefault } from '../MessageInput/components/InputGiphySearch'; import { InputReplyStateHeader as InputReplyStateHeaderDefault } from '../MessageInput/components/InputReplyStateHeader'; @@ -411,10 +417,21 @@ const ChannelWithContext = < additionalTextInputProps, additionalTouchableProps, allowThreadMessagesInChannel = true, + asyncMessagesLockDistance = 50, + asyncMessagesMinimumPressDuration = 500, + asyncMessagesMultiSendEnabled = true, + asyncMessagesSlideToCancelDistance = 100, AttachButton = AttachButtonDefault, Attachment = AttachmentDefault, AttachmentActions = AttachmentActionsDefault, AudioAttachment = AudioAttachmentDefault, + AudioAttachmentUploadPreview = AudioAttachmentDefault, + AudioRecorder = AudioRecorderDefault, + audioRecordingEnabled = false, + AudioRecordingInProgress = AudioRecordingInProgressDefault, + AudioRecordingLockIndicator = AudioRecordingLockIndicatorDefault, + AudioRecordingPreview = AudioRecordingPreviewDefault, + AudioRecordingWaveform = AudioRecordingWaveformDefault, AutoCompleteSuggestionHeader = AutoCompleteSuggestionHeaderDefault, AutoCompleteSuggestionItem = AutoCompleteSuggestionItemDefault, AutoCompleteSuggestionList = AutoCompleteSuggestionListDefault, @@ -552,6 +569,7 @@ const ChannelWithContext = < setWatchers, shouldSyncChannel, ShowThreadMessageInChannelButton = ShowThreadMessageInChannelButtonDefault, + StartAudioRecordingButton = AudioRecordingButtonDefault, stateUpdateThrottleInterval = defaultThrottleInterval, StickyHeader, supportedReactions = reactionData, @@ -1575,6 +1593,7 @@ const ChannelWithContext = < if ( (attachment.type === 'file' || attachment.type === 'audio' || + attachment.type === 'voiceRecording' || attachment.type === 'video') && attachment.asset_url && isLocalUrl(attachment.asset_url) && @@ -1593,6 +1612,7 @@ const ChannelWithContext = < if (response.thumb_url) { attachment.thumb_url = response.thumb_url; } + delete attachment.originalFile; dbApi.updateMessage({ message: { ...updatedMessage, cid: channel.cid }, @@ -2183,7 +2203,18 @@ const ChannelWithContext = < const inputMessageInputContext = useCreateInputMessageInputContext({ additionalTextInputProps, + asyncMessagesLockDistance, + asyncMessagesMinimumPressDuration, + asyncMessagesMultiSendEnabled, + asyncMessagesSlideToCancelDistance, AttachButton, + AudioAttachmentUploadPreview, + AudioRecorder, + audioRecordingEnabled, + AudioRecordingInProgress, + AudioRecordingLockIndicator, + AudioRecordingPreview, + AudioRecordingWaveform, autoCompleteSuggestionsLimit, autoCompleteTriggerSettings, channelId, @@ -2224,6 +2255,7 @@ const ChannelWithContext = < setInputRef, setQuotedMessageState, ShowThreadMessageInChannelButton, + StartAudioRecordingButton, UploadProgressIndicator, }); diff --git a/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts b/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts index 4263c2c42f..73722b706d 100644 --- a/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts +++ b/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts @@ -7,7 +7,18 @@ export const useCreateInputMessageInputContext = < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ additionalTextInputProps, + asyncMessagesLockDistance, + asyncMessagesMinimumPressDuration, + asyncMessagesMultiSendEnabled, + asyncMessagesSlideToCancelDistance, AttachButton, + AudioAttachmentUploadPreview, + AudioRecorder, + audioRecordingEnabled, + AudioRecordingInProgress, + AudioRecordingLockIndicator, + AudioRecordingPreview, + AudioRecordingWaveform, autoCompleteSuggestionsLimit, autoCompleteTriggerSettings, channelId, @@ -48,6 +59,7 @@ export const useCreateInputMessageInputContext = < setInputRef, setQuotedMessageState, ShowThreadMessageInChannelButton, + StartAudioRecordingButton, UploadProgressIndicator, }: InputMessageInputContextValue & { /** @@ -65,7 +77,18 @@ export const useCreateInputMessageInputContext = < const inputMessageInputContext: InputMessageInputContextValue = useMemo( () => ({ additionalTextInputProps, + asyncMessagesLockDistance, + asyncMessagesMinimumPressDuration, + asyncMessagesMultiSendEnabled, + asyncMessagesSlideToCancelDistance, AttachButton, + AudioAttachmentUploadPreview, + AudioRecorder, + audioRecordingEnabled, + AudioRecordingInProgress, + AudioRecordingLockIndicator, + AudioRecordingPreview, + AudioRecordingWaveform, autoCompleteSuggestionsLimit, autoCompleteTriggerSettings, clearEditingState, @@ -105,6 +128,7 @@ export const useCreateInputMessageInputContext = < setInputRef, setQuotedMessageState, ShowThreadMessageInChannelButton, + StartAudioRecordingButton, UploadProgressIndicator, }), [ diff --git a/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx index f9ec018d3a..843ebab59f 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGallery.test.tsx @@ -209,7 +209,7 @@ describe('ImageGallery', () => { const progressDurationComponent = screen.getByLabelText('Progress Duration'); await waitFor(() => { - expect(screen.getByLabelText('Play Icon')).not.toBeUndefined(); + expect(screen.queryAllByLabelText('Play Icon').length).toBeGreaterThan(0); expect(progressDurationComponent.children[0]).toBe('00:10'); }); }); diff --git a/package/src/components/ImageGallery/__tests__/ImageGalleryVideoControl.test.tsx b/package/src/components/ImageGallery/__tests__/ImageGalleryVideoControl.test.tsx index 7f07c6d716..794df3538f 100644 --- a/package/src/components/ImageGallery/__tests__/ImageGalleryVideoControl.test.tsx +++ b/package/src/components/ImageGallery/__tests__/ImageGalleryVideoControl.test.tsx @@ -42,10 +42,10 @@ describe('ImageGalleryOverlay', () => { it('should render the play icon when paused prop is true', async () => { render(getComponent({ paused: true })); - const component = screen.queryByLabelText('Play Icon') as ReactTestInstance; + const components = screen.queryAllByLabelText('Play Icon').length; await waitFor(() => { - expect(component).not.toBeUndefined(); + expect(components).toBeGreaterThan(0); }); }); diff --git a/package/src/components/ImageGallery/components/ImageGalleryVideoControl.tsx b/package/src/components/ImageGallery/components/ImageGalleryVideoControl.tsx index aea3af02f1..35c66aa389 100644 --- a/package/src/components/ImageGallery/components/ImageGalleryVideoControl.tsx +++ b/package/src/components/ImageGallery/components/ImageGalleryVideoControl.tsx @@ -75,14 +75,9 @@ export const ImageGalleryVideoControl = React.memo( {paused ? ( - + ) : ( - + )} diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx index d805acb8e3..d9b522e0ea 100644 --- a/package/src/components/Message/Message.tsx +++ b/package/src/components/Message/Message.tsx @@ -386,7 +386,7 @@ const MessageWithContext = < } else if (cur.type === 'video' && !cur.og_scrape_url) { acc.files.push(cur); acc.other = []; // remove other attachments if a file exists - } else if (cur.type === 'audio') { + } else if (cur.type === 'audio' || cur.type === 'voiceRecording') { acc.files.push(cur); } else if (cur.type === 'image' && !cur.title_link && !cur.og_scrape_url) { /** diff --git a/package/src/components/Message/MessageSimple/MessageContent.tsx b/package/src/components/Message/MessageSimple/MessageContent.tsx index 0b85a3172c..6e37601e05 100644 --- a/package/src/components/Message/MessageSimple/MessageContent.tsx +++ b/package/src/components/Message/MessageSimple/MessageContent.tsx @@ -510,7 +510,8 @@ const areEqual = , + icon: , title: t('Delete Message'), titleStyle: { color: accent_red }, }; @@ -345,7 +345,7 @@ export const useMessageActions = < await handleResendMessage(); }, actionType: 'retry', - icon: , + icon: , title: t('Resend'), }; diff --git a/package/src/components/MessageInput/FileUploadPreview.tsx b/package/src/components/MessageInput/FileUploadPreview.tsx index 6db3253bb8..fc3b8d9c13 100644 --- a/package/src/components/MessageInput/FileUploadPreview.tsx +++ b/package/src/components/MessageInput/FileUploadPreview.tsx @@ -20,7 +20,8 @@ import { Close } from '../../icons/Close'; import { Warning } from '../../icons/Warning'; import { isAudioPackageAvailable } from '../../native'; import type { DefaultStreamChatGenerics, FileUpload } from '../../types/types'; -import { FileState, getIndicatorTypeForFileState, ProgressIndicatorTypes } from '../../utils/utils'; +import { getTrimmedAttachmentTitle } from '../../utils/getTrimmedAttachmentTitle'; +import { getIndicatorTypeForFileState, ProgressIndicatorTypes } from '../../utils/utils'; import { getFileSizeDisplayText } from '../Attachment/FileAttachment'; import { WritingDirectionAwareText } from '../RTLComponents/WritingDirectionAwareText'; @@ -40,13 +41,8 @@ const styles = StyleSheet.create({ borderRadius: 12, borderWidth: 1, flexDirection: 'row', - height: FILE_PREVIEW_HEIGHT, - justifyContent: 'space-between', - marginBottom: 8, - paddingLeft: 8, - paddingRight: 8, + paddingHorizontal: 8, }, - fileContentContainer: { flexDirection: 'row' }, fileIcon: { alignItems: 'center', alignSelf: 'center', @@ -59,17 +55,17 @@ const styles = StyleSheet.create({ }, fileSizeText: { fontSize: 12, + marginTop: 10, paddingHorizontal: 10, }, fileTextContainer: { - height: '100%', justifyContent: 'space-around', + marginVertical: 10, }, flatList: { marginBottom: 12, maxHeight: FILE_PREVIEW_HEIGHT * 2.5 + 16 }, overlay: { borderRadius: 12, - marginLeft: 8, - marginRight: 8, + marginHorizontal: 8, marginTop: 2, }, unsupportedFile: { @@ -140,9 +136,9 @@ type FileUploadPreviewPropsWithContext< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Pick< MessageInputContextValue, - 'fileUploads' | 'removeFile' | 'uploadFile' | 'setFileUploads' + 'fileUploads' | 'removeFile' | 'uploadFile' | 'setFileUploads' | 'AudioAttachmentUploadPreview' > & - Pick, 'AudioAttachment' | 'FileAttachmentIcon'> & + Pick, 'FileAttachmentIcon'> & Pick, 'enableOfflineSupport'>; const FileUploadPreviewWithContext = < @@ -151,7 +147,7 @@ const FileUploadPreviewWithContext = < props: FileUploadPreviewPropsWithContext, ) => { const { - AudioAttachment, + AudioAttachmentUploadPreview, enableOfflineSupport, FileAttachmentIcon, fileUploads, @@ -163,12 +159,27 @@ const FileUploadPreviewWithContext = < const flatListRef = useRef | null>(null); const [flatListWidth, setFlatListWidth] = useState(0); + useEffect(() => { + setFileUploads( + fileUploads.map((file) => ({ + ...file, + duration: file.duration || 0, + paused: true, + progress: 0, + })), + ); + }, [fileUploads.length]); + // Handler triggered when an audio is loaded in the message input. The initial state is defined for the audio here and the duration is set. const onLoad = (index: string, duration: number) => { setFileUploads((prevFileUploads) => prevFileUploads.map((fileUpload) => ({ ...fileUpload, duration: fileUpload.id === index ? duration : fileUpload.duration, + file: { + ...fileUpload.file, + duration: fileUpload.id === index ? duration : fileUpload.duration, + }, })), ); }; @@ -213,26 +224,16 @@ const FileUploadPreviewWithContext = < const { theme: { - colors: { black, grey_dark, grey_gainsboro, grey_whisper, white }, + colors: { black, grey_dark, grey_gainsboro, grey_whisper }, messageInput: { - fileUploadPreview: { - audioAttachmentFileContainer, - dismiss, - fileContainer, - fileContentContainer, - filenameText, - fileTextContainer, - flatList, - }, + fileUploadPreview: { dismiss, fileContainer, filenameText, fileTextContainer, flatList }, }, }, } = useTheme(); - const renderItem = ({ index, item }: { index: number; item: FileUpload }) => { + const renderItem = ({ item }: { item: FileUpload }) => { const indicatorType = getIndicatorTypeForFileState(item.state, enableOfflineSupport); - const lastIndexOfDot = item.file.name.lastIndexOf('.'); - return ( <> {item.file.mimeType?.startsWith('audio/') && isAudioPackageAvailable() ? ( - - - - - + ) : ( - - - - - - - {item.file.name.slice(0, 12) + '...' + item.file.name.slice(lastIndexOfDot)} - - {indicatorType !== null && ( - - )} - + + + + + + {getTrimmedAttachmentTitle(item.file.name)} + + {indicatorType !== null && ( + + )} )} @@ -408,14 +377,14 @@ export const FileUploadPreview = < props: FileUploadPreviewProps, ) => { const { enableOfflineSupport } = useChatContext(); - const { fileUploads, removeFile, setFileUploads, uploadFile } = + const { AudioAttachmentUploadPreview, fileUploads, removeFile, setFileUploads, uploadFile } = useMessageInputContext(); - const { AudioAttachment, FileAttachmentIcon } = useMessagesContext(); + const { FileAttachmentIcon } = useMessagesContext(); return ( ); }; + const areEqual = ( prevProps: InputButtonsWithContextProps, nextProps: InputButtonsWithContextProps, diff --git a/package/src/components/MessageInput/MessageInput.tsx b/package/src/components/MessageInput/MessageInput.tsx index 2980768ce1..4a3fe764e1 100644 --- a/package/src/components/MessageInput/MessageInput.tsx +++ b/package/src/components/MessageInput/MessageInput.tsx @@ -7,8 +7,24 @@ import { View, } from 'react-native'; +import { + GestureEvent, + PanGestureHandler, + PanGestureHandlerEventPayload, +} from 'react-native-gesture-handler'; +import Animated, { + Extrapolation, + interpolate, + runOnJS, + useAnimatedGestureHandler, + useAnimatedStyle, + useSharedValue, + withSpring, +} from 'react-native-reanimated'; + import type { UserResponse } from 'stream-chat'; +import { useAudioController } from './hooks/useAudioController'; import { useCountdown } from './hooks/useCountdown'; import { ChatContextValue, useChatContext } from '../../contexts'; @@ -40,6 +56,7 @@ import { useTranslationContext, } from '../../contexts/translationContext/TranslationContext'; +import { triggerHaptic } from '../../native'; import type { Asset, DefaultStreamChatGenerics } from '../../types/types'; import { AutoCompleteInput } from '../AutoCompleteInput/AutoCompleteInput'; @@ -55,8 +72,9 @@ const styles = StyleSheet.create({ paddingRight: 16, }, composerContainer: { - alignItems: 'flex-end', + alignItems: 'center', flexDirection: 'row', + justifyContent: 'space-between', }, container: { borderTopWidth: 1, @@ -66,14 +84,14 @@ const styles = StyleSheet.create({ borderRadius: 20, borderWidth: 1, flex: 1, + marginHorizontal: 10, }, + micButtonContainer: {}, optionsContainer: { flexDirection: 'row', - paddingBottom: 10, - paddingRight: 10, }, replyContainer: { paddingBottom: 12, paddingHorizontal: 8 }, - sendButtonContainer: { paddingBottom: 10, paddingLeft: 10 }, + sendButtonContainer: {}, suggestionsListContainer: { position: 'absolute', width: '100%', @@ -92,7 +110,15 @@ type MessageInputPropsWithContext< MessageInputContextValue, | 'additionalTextInputProps' | 'asyncIds' + | 'audioRecordingEnabled' + | 'asyncMessagesLockDistance' + | 'asyncMessagesMinimumPressDuration' + | 'asyncMessagesSlideToCancelDistance' | 'asyncUploads' + | 'AudioRecorder' + | 'AudioRecordingInProgress' + | 'AudioRecordingLockIndicator' + | 'AudioRecordingPreview' | 'cooldownEndsAt' | 'CooldownTimer' | 'clearEditingState' @@ -123,8 +149,10 @@ type MessageInputPropsWithContext< | 'setGiphyActive' | 'showMoreOptions' | 'ShowThreadMessageInChannelButton' + | 'StartAudioRecordingButton' | 'removeFile' | 'removeImage' + | 'text' | 'uploadNewFile' | 'uploadNewImage' > & @@ -148,8 +176,16 @@ const MessageInputWithContext = < const { additionalTextInputProps, asyncIds, + asyncMessagesLockDistance, + asyncMessagesMinimumPressDuration, + asyncMessagesSlideToCancelDistance, asyncUploads, AttachmentPickerSelectionBar, + AudioRecorder, + audioRecordingEnabled, + AudioRecordingInProgress, + AudioRecordingLockIndicator, + AudioRecordingPreview, AutoCompleteSuggestionList, closeAttachmentPicker, cooldownEndsAt, @@ -183,7 +219,9 @@ const MessageInputWithContext = < sendMessageAsync, setShowMoreOptions, ShowThreadMessageInChannelButton, + StartAudioRecordingButton, suggestions, + text, thread, threadList, triggerType, @@ -200,11 +238,13 @@ const MessageInputWithContext = < colors: { border, grey_whisper, white, white_smoke }, messageInput: { attachmentSelectionBar, + attachmentSeparator, autoCompleteInputContainer, composerContainer, container, focusedInputBoxContainer, inputBoxContainer, + micButtonContainer, optionsContainer, replyContainer, sendButtonContainer, @@ -542,6 +582,124 @@ const MessageInputWithContext = < [additionalTextInputProps], ); + const { + deleteVoiceRecording, + micLocked, + onVoicePlayerPlayPause, + paused, + permissionsGranted, + position, + progress, + recording, + recordingDuration, + recordingStatus, + setMicLocked, + startVoiceRecording, + stopVoiceRecording, + uploadVoiceRecording, + waveformData, + } = useAudioController(); + + const isSendingButtonVisible = () => { + if (audioRecordingEnabled) { + if (recording) { + return false; + } + if (text && text.trim()) { + return true; + } + + const imagesAndFiles = [...imageUploads, ...fileUploads]; + if (imagesAndFiles.length === 0) return false; + } + + return true; + }; + + const micPositionX = useSharedValue(0); + const micPositionY = useSharedValue(0); + const X_AXIS_POSITION = -asyncMessagesSlideToCancelDistance; + const Y_AXIS_POSITION = -asyncMessagesLockDistance; + + const resetAudioRecording = async () => { + await deleteVoiceRecording(); + micPositionX.value = 0; + }; + + const micLockHandler = () => { + setMicLocked(true); + micPositionY.value = 0; + triggerHaptic('impactMedium'); + }; + + const handleMicGestureEvent = useAnimatedGestureHandler< + GestureEvent + >({ + onActive: (event) => { + const newPositionX = event.translationX; + const newPositionY = event.translationY; + + if (newPositionX <= 0 && newPositionX >= X_AXIS_POSITION) { + micPositionX.value = newPositionX; + } + if (newPositionY <= 0 && newPositionY >= Y_AXIS_POSITION) { + micPositionY.value = newPositionY; + } + }, + onFinish: () => { + if (micPositionY.value > Y_AXIS_POSITION / 2) { + micPositionY.value = withSpring(0); + } else { + micPositionY.value = withSpring(Y_AXIS_POSITION); + runOnJS(micLockHandler)(); + } + if (micPositionX.value > X_AXIS_POSITION / 2) { + micPositionX.value = withSpring(0); + } else { + micPositionX.value = withSpring(X_AXIS_POSITION); + runOnJS(resetAudioRecording)(); + } + }, + onStart: () => { + micPositionX.value = 0; + micPositionY.value = 0; + runOnJS(setMicLocked)(false); + }, + }); + + const animatedStyles = { + lockIndicator: useAnimatedStyle(() => ({ + transform: [ + { + translateY: interpolate( + micPositionY.value, + [0, Y_AXIS_POSITION], + [0, Y_AXIS_POSITION], + Extrapolation.CLAMP, + ), + }, + ], + })), + micButton: useAnimatedStyle(() => ({ + opacity: interpolate(micPositionX.value, [0, X_AXIS_POSITION], [1, 0], Extrapolation.CLAMP), + transform: [{ translateX: micPositionX.value }, { translateY: micPositionY.value }], + zIndex: 2, + })), + slideToCancel: useAnimatedStyle(() => ({ + opacity: interpolate(micPositionX.value, [0, X_AXIS_POSITION], [1, 0], Extrapolation.CLAMP), + transform: [ + { + translateX: interpolate( + micPositionX.value, + [0, X_AXIS_POSITION], + [0, X_AXIS_POSITION / 2], + Extrapolation.CLAMP, + ), + }, + ], + })), + }; + return ( <> {editing && } {quotedMessage && } + {recording && ( + <> + + {micLocked && + (recordingStatus === 'stopped' ? ( + + ) : ( + + ))} + + )} + {Input ? ( ) : ( <> - - {InputButtons && } - - - {((typeof editing !== 'boolean' && editing?.quoted_message) || quotedMessage) && ( - - + {recording ? ( + + ) : ( + <> + + {InputButtons && } - )} - {imageUploads.length ? : null} - {imageUploads.length && fileUploads.length ? ( - ) : null} - {fileUploads.length ? : null} - {giphyActive ? ( - - ) : ( - - - additionalTextInputProps={memoizedAdditionalTextInputProps} - cooldownActive={!!cooldownRemainingSeconds} - /> + > + {((typeof editing !== 'boolean' && editing?.quoted_message) || + quotedMessage) && ( + + + + )} + {imageUploads.length ? : null} + {imageUploads.length && fileUploads.length ? ( + + ) : null} + {fileUploads.length ? : null} + {giphyActive ? ( + + ) : ( + + + additionalTextInputProps={memoizedAdditionalTextInputProps} + cooldownActive={!!cooldownRemainingSeconds} + /> + + )} - )} - - - {cooldownRemainingSeconds ? ( + + )} + + {isSendingButtonVisible() && + (cooldownRemainingSeconds ? ( ) : ( - - )} - + + + + ))} + {audioRecordingEnabled && !micLocked && ( + + + + + + )} )} @@ -635,6 +860,7 @@ const MessageInputWithContext = < /> ) : null} + {selectedPicker && ( { const { additionalTextInputProps: prevAdditionalTextInputProps, + asyncMessagesLockDistance: prevAsyncMessagesLockDistance, + asyncMessagesMinimumPressDuration: prevAsyncMessagesMinimumPressDuration, + asyncMessagesSlideToCancelDistance: prevAsyncMessagesSlideToCancelDistance, asyncUploads: prevAsyncUploads, + audioRecordingEnabled: prevAsyncMessagesEnabled, disabled: prevDisabled, editing: prevEditing, fileUploads: prevFileUploads, @@ -678,7 +908,11 @@ const areEqual = (); @@ -861,8 +1121,17 @@ export const MessageInput = < {...{ additionalTextInputProps, asyncIds, + asyncMessagesLockDistance, + asyncMessagesMinimumPressDuration, + asyncMessagesSlideToCancelDistance, asyncUploads, AttachmentPickerSelectionBar, + AudioRecorder, + audioRecordingEnabled, + AudioRecordingInProgress, + AudioRecordingLockIndicator, + AudioRecordingPreview, + AudioRecordingWaveform, AutoCompleteSuggestionHeader, AutoCompleteSuggestionItem, AutoCompleteSuggestionList, @@ -903,8 +1172,10 @@ export const MessageInput = < setShowMoreOptions, showMoreOptions, ShowThreadMessageInChannelButton, + StartAudioRecordingButton, suggestions, t, + text, thread, threadList, triggerType, diff --git a/package/src/components/MessageInput/SendButton.tsx b/package/src/components/MessageInput/SendButton.tsx index cb121f77d6..af63977251 100644 --- a/package/src/components/MessageInput/SendButton.tsx +++ b/package/src/components/MessageInput/SendButton.tsx @@ -39,9 +39,13 @@ const SendButtonWithContext = < style={[sendButton]} testID='send-button' > - {giphyActive && } - {!giphyActive && disabled && } - {!giphyActive && !disabled && } + {giphyActive ? ( + + ) : disabled ? ( + + ) : ( + + )} ); }; diff --git a/package/src/components/MessageInput/__tests__/MessageInput.test.js b/package/src/components/MessageInput/__tests__/MessageInput.test.js index 57bd044dc4..070b924947 100644 --- a/package/src/components/MessageInput/__tests__/MessageInput.test.js +++ b/package/src/components/MessageInput/__tests__/MessageInput.test.js @@ -107,7 +107,6 @@ describe('MessageInput', () => { expect(queryByTestId('upload-file-touchable')).toBeTruthy(); expect(queryByTestId('take-photo-touchable')).toBeTruthy(); expect(queryByTestId('auto-complete-text-input')).toBeTruthy(); - expect(queryByTestId('send-button')).toBeTruthy(); expect(queryByText('Editing Message')).toBeFalsy(); }); }); diff --git a/package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.js.snap b/package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.js.snap index 760bc19d0f..8f5786422b 100644 --- a/package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.js.snap +++ b/package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.js.snap @@ -40,14 +40,14 @@ exports[`SendButton should render a non-editing disabled SendButton 1`] = ` > + + + + = Pick, 'disabled'> & + Pick, 'asyncMessagesMultiSendEnabled'> & { + /** + * Function to stop and delete the voice recording. + */ + deleteVoiceRecording: () => Promise; + /** + * Boolean used to show if the voice recording state is locked. This makes sure the mic button shouldn't be pressed any longer. + * When the mic is locked the `AudioRecordingInProgress` component shows up. + */ + micLocked: boolean; + /** + * The current voice recording that is in progress. + */ + recording: AudioRecordingReturnType; + /** + * Boolean to determine if the recording has been stopped. + */ + recordingStopped: boolean; + /** + * Function to stop the ongoing voice recording. + */ + stopVoiceRecording: () => Promise; + /** + * Function to upload the voice recording. + */ + uploadVoiceRecording: (multiSendEnabled: boolean) => Promise; + /** + * The duration of the voice recording. + */ + recordingDuration?: number; + /** + * Style used in slide to cancel container. + */ + slideToCancelStyle?: StyleProp; + }; + +const StopRecording = ({ + stopVoiceRecordingHandler, +}: { + stopVoiceRecordingHandler: () => Promise; +}) => { + const { + theme: { + colors: { accent_red }, + messageInput: { + audioRecorder: { circleStopIcon, pausedContainer }, + }, + }, + } = useTheme(); + return ( + + + + ); +}; + +const UploadRecording = ({ + asyncMessagesMultiSendEnabled, + uploadVoiceRecordingHandler, +}: { + asyncMessagesMultiSendEnabled: boolean; + uploadVoiceRecordingHandler: (multiSendEnabled: boolean) => Promise; +}) => { + const { + theme: { + colors: { accent_blue }, + messageInput: { + audioRecorder: { checkContainer, sendCheckIcon }, + }, + }, + } = useTheme(); + return ( + { + await uploadVoiceRecordingHandler(asyncMessagesMultiSendEnabled); + }} + style={[styles.checkContainer, checkContainer]} + > + + + ); +}; + +const DeleteRecording = ({ + deleteVoiceRecordingHandler, + disabled, +}: { + deleteVoiceRecordingHandler: () => Promise; + disabled?: boolean; +}) => { + const { + theme: { + colors: { accent_blue }, + messageInput: { + audioRecorder: { deleteContainer, deleteIcon }, + }, + }, + } = useTheme(); + return ( + + + + ); +}; + +const AudioRecorderWithContext = < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( + props: AudioRecorderPropsWithContext, +) => { + const { + asyncMessagesMultiSendEnabled, + deleteVoiceRecording, + disabled, + micLocked, + recordingDuration, + recordingStopped, + slideToCancelStyle, + stopVoiceRecording, + uploadVoiceRecording, + } = props; + + const { + theme: { + colors: { accent_red, grey_dark }, + messageInput: { + audioRecorder: { arrowLeftIcon, micContainer, micIcon, slideToCancelContainer }, + }, + }, + } = useTheme(); + + if (micLocked) { + if (recordingStopped) { + return ( + <> + + + + ); + } else { + return ( + <> + + + + + + + ); + } + } else { + return ( + <> + + + + {recordingDuration ? dayjs.duration(recordingDuration).format('mm:ss') : null} + + + + Slide to Cancel + + + + ); + } +}; + +const areEqual = ( + prevProps: AudioRecorderPropsWithContext, + nextProps: AudioRecorderPropsWithContext, +) => { + const { + asyncMessagesMultiSendEnabled: prevAsyncMessagesMultiSendEnabled, + disabled: prevDisabled, + micLocked: prevMicLocked, + recording: prevRecording, + recordingDuration: prevRecordingDuration, + recordingStopped: prevRecordingStopped, + } = prevProps; + const { + asyncMessagesMultiSendEnabled: nextAsyncMessagesMultiSendEnabled, + disabled: nextDisabled, + micLocked: nextMicLocked, + recording: nextRecording, + recordingDuration: nextRecordingDuration, + recordingStopped: nextRecordingStopped, + } = nextProps; + + const asyncMessagesMultiSendEnabledEqual = + prevAsyncMessagesMultiSendEnabled === nextAsyncMessagesMultiSendEnabled; + if (!asyncMessagesMultiSendEnabledEqual) return false; + + const disabledEqual = prevDisabled === nextDisabled; + if (!disabledEqual) return false; + + const micLockedEqual = prevMicLocked === nextMicLocked; + if (!micLockedEqual) return false; + + const recordingEqual = prevRecording === nextRecording; + if (!recordingEqual) return false; + + const recordingDurationEqual = prevRecordingDuration === nextRecordingDuration; + if (!recordingDurationEqual) return false; + + const recordingStoppedEqual = prevRecordingStopped === nextRecordingStopped; + if (!recordingStoppedEqual) return false; + + return true; +}; + +const MemoizedAudioRecorder = React.memo( + AudioRecorderWithContext, + areEqual, +) as typeof AudioRecorderWithContext; + +export type AudioRecorderProps< + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +> = Partial> & + Pick< + AudioRecorderPropsWithContext, + | 'deleteVoiceRecording' + | 'micLocked' + | 'recording' + | 'recordingStopped' + | 'stopVoiceRecording' + | 'uploadVoiceRecording' + >; + +/** + * Component to display the Recording UI in the Message Input. + */ +export const AudioRecorder = < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( + props: AudioRecorderProps, +) => { + const { disabled = false } = useChannelContext(); + const { asyncMessagesMultiSendEnabled } = useMessageInputContext(); + + return ( + + ); +}; + +const styles = StyleSheet.create({ + checkContainer: {}, + deleteContainer: {}, + durationLabel: { + fontSize: 14, + }, + micContainer: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'center', + }, + pausedContainer: {}, + slideToCancel: { + fontSize: 18, + }, + slideToCancelContainer: { + alignItems: 'center', + flexDirection: 'row', + }, +}); + +AudioRecorder.displayName = 'AudioRecorder{messageInput}'; diff --git a/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingButton.tsx b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingButton.tsx new file mode 100644 index 0000000000..826d972fb2 --- /dev/null +++ b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingButton.tsx @@ -0,0 +1,192 @@ +import React from 'react'; +import { Alert, Linking, Pressable, StyleSheet } from 'react-native'; + +import { + ChannelContextValue, + useChannelContext, +} from '../../../../contexts/channelContext/ChannelContext'; +import { + MessageInputContextValue, + useMessageInputContext, +} from '../../../../contexts/messageInputContext/MessageInputContext'; +import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; +import { useTranslationContext } from '../../../../contexts/translationContext/TranslationContext'; +import { Mic } from '../../../../icons/Mic'; +import { AudioRecordingReturnType, triggerHaptic } from '../../../../native'; + +import type { DefaultStreamChatGenerics } from '../../../../types/types'; + +type AudioRecordingButtonPropsWithContext< + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +> = Pick, 'disabled'> & + Pick, 'asyncMessagesMinimumPressDuration'> & { + /** + * The current voice recording that is in progress. + */ + recording: AudioRecordingReturnType; + /** + * Size of the mic button. + */ + buttonSize?: number; + /** + * Handler to determine what should happen on long press of the mic button. + */ + handleLongPress?: () => void; + /** + * Handler to determine what should happen on press of the mic button. + */ + handlePress?: () => void; + /** + * Boolean to determine if the audio recording permissions are granted. + */ + permissionsGranted?: boolean; + /** + * Function to start the voice recording. + */ + startVoiceRecording?: () => Promise; + }; + +const AudioRecordingButtonWithContext = < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( + props: AudioRecordingButtonPropsWithContext, +) => { + const { + asyncMessagesMinimumPressDuration, + buttonSize, + disabled, + handleLongPress, + handlePress, + permissionsGranted, + recording, + startVoiceRecording, + } = props; + + const { + theme: { + colors: { grey, light_gray, white }, + messageInput: { + audioRecordingButton: { container, micIcon }, + }, + }, + } = useTheme(); + const { t } = useTranslationContext(); + + const onPressHandler = () => { + if (handlePress) { + handlePress(); + } + if (!recording) { + triggerHaptic('notificationError'); + Alert.alert(t('Hold to start recording.')); + } + }; + + const onLongPressHandler = () => { + if (handleLongPress) { + handleLongPress(); + return; + } + if (!recording) { + triggerHaptic('impactHeavy'); + if (!permissionsGranted) { + Alert.alert(t('Please allow Audio permissions in settings.'), '', [ + { + onPress: () => { + Linking.openSettings(); + }, + text: t('Open Settings'), + }, + ]); + return; + } + if (startVoiceRecording) startVoiceRecording(); + } + }; + + return ( + [ + styles.container, + { + backgroundColor: pressed ? light_gray : white, + height: buttonSize || 40, + width: buttonSize || 40, + }, + container, + ]} + testID='audio-button' + > + + + ); +}; + +const areEqual = ( + prevProps: AudioRecordingButtonPropsWithContext, + nextProps: AudioRecordingButtonPropsWithContext, +) => { + const { + asyncMessagesMinimumPressDuration: prevAsyncMessagesMinimumPressDuration, + disabled: prevDisabled, + recording: prevRecording, + } = prevProps; + const { + asyncMessagesMinimumPressDuration: nextAsyncMessagesMinimumPressDuration, + disabled: nextDisabled, + recording: nextRecording, + } = nextProps; + + const asyncMessagesMinimumPressDurationEqual = + prevAsyncMessagesMinimumPressDuration === nextAsyncMessagesMinimumPressDuration; + if (!asyncMessagesMinimumPressDurationEqual) return false; + + const disabledEqual = prevDisabled === nextDisabled; + if (!disabledEqual) return false; + + const recordingEqual = prevRecording === nextRecording; + if (!recordingEqual) return false; + + return true; +}; + +const MemoizedAudioRecordingButton = React.memo( + AudioRecordingButtonWithContext, + areEqual, +) as typeof AudioRecordingButtonWithContext; + +export type AudioRecordingButtonProps< + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +> = Partial> & { + recording: AudioRecordingReturnType; +}; + +/** + * Component to display the mic button on the Message Input. + */ +export const AudioRecordingButton = < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( + props: AudioRecordingButtonProps, +) => { + const { disabled = false } = useChannelContext(); + const { asyncMessagesMinimumPressDuration } = useMessageInputContext(); + + return ( + + ); +}; + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + borderRadius: 50, + justifyContent: 'center', + }, +}); + +AudioRecordingButton.displayName = 'AudioRecordingButton{messageInput}'; diff --git a/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx new file mode 100644 index 0000000000..7eeb96d130 --- /dev/null +++ b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx @@ -0,0 +1,114 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; + +import dayjs from 'dayjs'; + +import { + MessageInputContextValue, + useMessageInputContext, +} from '../../../../contexts/messageInputContext/MessageInputContext'; +import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; + +import type { DefaultStreamChatGenerics } from '../../../../types/types'; + +type AudioRecordingInProgressPropsWithContext< + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +> = Pick, 'AudioRecordingWaveform'> & { + /** + * The waveform data to be presented to show the audio levels. + */ + waveformData: number[]; + /** + * Maximum number of waveform lines that should be rendered in the UI. + */ + maxDataPointsDrawn?: number; + /** + * The duration of the voice recording. + */ + recordingDuration?: number; +}; + +const AudioRecordingInProgressWithContext = < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( + props: AudioRecordingInProgressPropsWithContext, +) => { + const { + AudioRecordingWaveform, + maxDataPointsDrawn = 80, + recordingDuration, + waveformData, + } = props; + + const { + theme: { + colors: { grey_dark }, + messageInput: { + audioRecordingInProgress: { container, durationText }, + }, + }, + } = useTheme(); + + return ( + + {/* `durationMillis` is for Expo apps, `currentPosition` is for Native CLI apps. */} + + {recordingDuration ? dayjs.duration(recordingDuration).format('mm:ss') : null} + + + + ); +}; + +const areEqual = ( + prevProps: AudioRecordingInProgressPropsWithContext, + nextProps: AudioRecordingInProgressPropsWithContext, +) => { + const { recordingDuration: prevRecordingDuration } = prevProps; + const { recordingDuration: nextRecordingDuration } = nextProps; + + const recordingDurationEqual = prevRecordingDuration === nextRecordingDuration; + + if (!recordingDurationEqual) return false; + + return true; +}; + +const MemoizedAudioRecordingInProgress = React.memo( + AudioRecordingInProgressWithContext, + areEqual, +) as typeof AudioRecordingInProgressWithContext; + +export type AudioRecordingInProgressProps< + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +> = 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(); + + return ; +}; + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + paddingBottom: 8, + paddingTop: 4, + }, + durationText: { + fontSize: 16, + }, +}); + +AudioRecordingInProgress.displayName = 'AudioRecordingInProgress{messageInput}'; diff --git a/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator.tsx b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator.tsx new file mode 100644 index 0000000000..936fcd3381 --- /dev/null +++ b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator.tsx @@ -0,0 +1,85 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { StyleProp, StyleSheet, ViewStyle } from 'react-native'; + +import Animated from 'react-native-reanimated'; + +import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; +import { ArrowUp, Lock } from '../../../../icons'; + +export type AudioRecordingLockIndicatorProps = { + /** + * Boolean used to show if the voice recording state is locked. This makes sure the mic button shouldn't be pressed any longer. + * When the mic is locked the `AudioRecordingInProgress` component shows up. + */ + micLocked: boolean; + /** + * Height of the message input, to apply necessary position adjustments to this component. + */ + messageInputHeight?: number; + /** + * Styles of the lock indicator. + */ + style?: StyleProp; +}; + +/** + * Component displayed to show the lock state of the recording when the button is slided up. + */ +export const AudioRecordingLockIndicator = ({ + messageInputHeight, + micLocked, + style, +}: AudioRecordingLockIndicatorProps) => { + const [visible, setVisible] = useState(true); + const timeoutRef = useRef(); + + useEffect(() => { + timeoutRef.current = setTimeout(() => { + if (micLocked) { + setVisible(false); + } + }, 2000); + + return () => { + clearTimeout(timeoutRef.current); + }; + }, [micLocked]); + + const { + theme: { + colors: { accent_blue, grey, light_gray }, + messageInput: { + audioRecordingLockIndicator: { arrowUpIcon, container, lockIcon }, + }, + }, + } = useTheme(); + + if (!visible) { + return null; + } + + return ( + + + {!micLocked && } + + ); +}; + +const styles = StyleSheet.create({ + container: { + borderRadius: 50, + margin: 5, + padding: 8, + position: 'absolute', + right: 0, + zIndex: 1, + }, +}); diff --git a/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingPreview.tsx b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingPreview.tsx new file mode 100644 index 0000000000..d38c091bef --- /dev/null +++ b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingPreview.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { Pressable, StyleSheet, Text, View } from 'react-native'; + +import dayjs from 'dayjs'; + +import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; +import { Pause, Play } from '../../../../icons'; + +import { WaveProgressBar } from '../../../ProgressControl/WaveProgressBar'; + +export type AudioRecordingPreviewProps = { + /** + * Boolean used to show the paused state of the player. + */ + paused: boolean; + /** + * Number used to show the current position of the audio being played. + */ + position: number; + /** + * Number used to show the percentage of progress of the audio being played. It should be in 0-1 range. + */ + progress: number; + /** + * The waveform data to be presented to show the audio levels. + */ + waveformData: number[]; + /** + * Function to play or pause the audio player. + */ + onVoicePlayerPlayPause?: () => Promise; +}; + +/** + * Component displayed when the audio is recorded and can be previewed. + */ +export const AudioRecordingPreview = (props: AudioRecordingPreviewProps) => { + const { onVoicePlayerPlayPause, paused, position, progress, waveformData } = props; + + const { + theme: { + colors: { accent_blue, grey_dark }, + messageInput: { + audioRecordingPreview: { + container, + currentTime, + infoContainer, + pauseIcon, + playIcon, + progressBar, + }, + }, + }, + } = useTheme(); + + return ( + + + + {paused ? ( + + ) : ( + + )} + + {/* `durationMillis` is for Expo apps, `currentPosition` is for Native CLI apps. */} + + {dayjs.duration(position).format('mm:ss')} + + + + {/* Since the progress is in range 0-1 we convert it in terms of 100% */} + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + display: 'flex', + flexDirection: 'row', + paddingBottom: 8, + paddingTop: 4, + }, + currentTime: { + fontSize: 16, + marginLeft: 4, + }, + infoContainer: { + alignItems: 'center', + display: 'flex', + flex: 1, + flexDirection: 'row', + }, + progressBar: { + flex: 3, + }, +}); + +AudioRecordingPreview.displayName = 'AudioRecordingPreview{messageInput}'; diff --git a/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingWaveform.tsx b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingWaveform.tsx new file mode 100644 index 0000000000..f0691ac1dd --- /dev/null +++ b/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingWaveform.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; + +import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; + +export type AudioRecordingWaveformProps = { + /** + * Maximum number of waveform lines that should be rendered in the UI. + */ + maxDataPointsDrawn: number; + /** + * The waveform data to be presented to show the audio levels. + */ + waveformData: number[]; +}; + +/** + * Waveform Component displayed when the audio is in the recording state. + */ +export const AudioRecordingWaveform = (props: AudioRecordingWaveformProps) => { + const { maxDataPointsDrawn, waveformData } = props; + const { + theme: { + colors: { grey_dark }, + messageInput: { + audioRecordingWaveform: { container, waveform: waveformTheme }, + }, + }, + } = useTheme(); + return ( + + {waveformData.slice(-maxDataPointsDrawn).map((waveform, index) => ( + 3 ? waveform * 30 : 3, + }, + waveformTheme, + ]} + /> + ))} + + ); +}; + +const styles = StyleSheet.create({ + container: { + alignSelf: 'center', + flexDirection: 'row', + }, + waveform: { + alignSelf: 'center', + borderRadius: 2, + marginHorizontal: 1, + width: 2, + }, +}); + +AudioRecordingWaveform.displayName = 'AudioRecordingWaveform{messageInput}'; diff --git a/package/src/components/MessageInput/hooks/useAudioController.tsx b/package/src/components/MessageInput/hooks/useAudioController.tsx new file mode 100644 index 0000000000..42e0c62460 --- /dev/null +++ b/package/src/components/MessageInput/hooks/useAudioController.tsx @@ -0,0 +1,288 @@ +import { useEffect, useRef, useState } from 'react'; + +import { Alert, Platform } from 'react-native'; + +import { useMessageInputContext } from '../../../contexts/messageInputContext/MessageInputContext'; +import { + Audio, + AudioRecordingReturnType, + PlaybackStatus, + RecordingStatus, + Sound, + SoundReturnType, + triggerHaptic, +} from '../../../native'; +import { File } from '../../../types/types'; +import { resampleWaveformData } from '../utils/audioSampling'; +import { normalizeAudioLevel } from '../utils/normalizeAudioLevel'; + +export type RecordingStatusStates = 'idle' | 'recording' | 'stopped'; + +/** + * The hook that controls all the async audio core features including start/stop or recording, player, upload/delete of the recorded audio. + */ +export const useAudioController = () => { + const [micLocked, setMicLocked] = useState(false); + const [permissionsGranted, setPermissionsGranted] = useState(true); + const [paused, setPaused] = useState(true); + const [position, setPosition] = useState(0); + const [progress, setProgress] = useState(0); + const [waveformData, setWaveformData] = useState([]); + const [isScheduledForSubmit, setIsScheduleForSubmit] = useState(false); + const [recording, setRecording] = useState(undefined); + const [recordingDuration, setRecordingDuration] = useState(0); + const [recordingStatus, setRecordingStatus] = useState('idle'); + + const { sendMessage, uploadNewFile } = useMessageInputContext(); + + // For playback support in Expo CLI apps + const soundRef = useRef(null); + + // Effect to stop the player when the component unmounts + useEffect( + () => () => { + stopVoicePlayer(); + }, + [], + ); + + useEffect(() => { + if (isScheduledForSubmit) { + sendMessage(); + setIsScheduleForSubmit(false); + } + }, [isScheduledForSubmit, sendMessage]); + + const onVoicePlayerProgressHandler = (currentPosition: number, playbackDuration: number) => { + const currentProgress = currentPosition / playbackDuration; + if (currentProgress === 1) { + setPaused(true); + setProgress(0); + } else { + setProgress(currentProgress); + } + }; + + const onVoicePlayerPlaybackStatusUpdate = (status: PlaybackStatus) => { + if (status.shouldPlay === undefined || status.shouldPlay === true) { + setPosition(status?.currentPosition || status?.positionMillis); + setRecordingDuration(status.duration || status.durationMillis); + + if (status.didJustFinish) { + onVoicePlayerProgressHandler(status.durationMillis, status.durationMillis); + } else { + // For Native CLI + if (status.currentPosition && status.duration) + onVoicePlayerProgressHandler(status.currentPosition, status.duration); + // For Expo CLI + else if (status.positionMillis && status.durationMillis) + onVoicePlayerProgressHandler(status.positionMillis, status.durationMillis); + } + } + }; + + const onVoicePlayerPlayPause = async () => { + if (paused) { + if (progress === 0) await startVoicePlayer(); + else { + // For Native CLI + if (Audio.resumePlayer) await Audio.resumePlayer(); + // For Expo CLI + if (soundRef.current?.playAsync) await soundRef.current.playAsync(); + } + } else { + // For Native CLI + if (Audio.pausePlayer) await Audio.pausePlayer(); + // For Expo CLI + if (soundRef.current?.pauseAsync) await soundRef.current.pauseAsync(); + } + setPaused(!paused); + }; + + /** + * Function to start playing voice recording to preview it after recording. + */ + const startVoicePlayer = async () => { + if (!recording) return; + // For Native CLI + if (Audio.startPlayer) + await Audio.startPlayer(recording, {}, onVoicePlayerPlaybackStatusUpdate); + // For Expo CLI + if (recording && typeof recording !== 'string') { + const uri = recording.getURI(); + if (uri) { + soundRef.current = await Sound.initializeSound( + { uri }, + {}, + onVoicePlayerPlaybackStatusUpdate, + ); + if (soundRef.current?.playAsync && soundRef.current.setProgressUpdateIntervalAsync) { + await soundRef.current.playAsync(); + await soundRef.current.setProgressUpdateIntervalAsync( + Platform.OS === 'android' ? 100 : 60, + ); + } + } + } + }; + + /** + * Function to stop playing voice recording. + */ + const stopVoicePlayer = async () => { + // For Native CLI + if (Audio.stopPlayer) { + await Audio.stopPlayer(); + } + // For Expo CLI + if (recording && typeof recording !== 'string') { + if (soundRef.current?.stopAsync && soundRef.current?.unloadAsync) { + await soundRef.current.stopAsync(); + await soundRef.current?.unloadAsync(); + } + } + }; + + const onRecordingStatusUpdate = (status: RecordingStatus) => { + if (status.isDoneRecording === true) { + return; + } + setRecordingDuration(status?.currentPosition || status.durationMillis); + // For expo android the lower bound is -120 so we need to normalize according to it. The `status.currentMetering` is undefined for Expo CLI apps, so we can use it. + const lowerBound = Platform.OS === 'ios' || status.currentMetering ? -60 : -120; + const normalizedAudioLevel = normalizeAudioLevel( + status.currentMetering || status.metering, + lowerBound, + ); + setWaveformData((prev) => [...prev, normalizedAudioLevel]); + }; + + /** + * Function to start voice recording. + */ + const startVoiceRecording = async () => { + setRecordingStatus('recording'); + const recordingInfo = await Audio.startRecording( + { + isMeteringEnabled: true, + }, + onRecordingStatusUpdate, + ); + const accessGranted = recordingInfo.accessGranted; + if (accessGranted) { + setPermissionsGranted(true); + const recording = recordingInfo.recording; + if (recording && typeof recording !== 'string') { + recording.setProgressUpdateInterval(Platform.OS === 'android' ? 100 : 60); + } + setRecording(recording); + await stopVoicePlayer(); + } else { + setPermissionsGranted(false); + resetState(); + Alert.alert('Please allow Audio permissions in settings.'); + } + }; + + /** + * Function to stop voice recording. + */ + const stopVoiceRecording = async () => { + if (recording) { + // For Expo CLI + if (typeof recording !== 'string') { + await recording.stopAndUnloadAsync(); + await Audio.stopRecording(); + } + // For RN CLI + else { + await Audio.stopRecording(); + } + } + setRecordingStatus('stopped'); + }; + + /** + * Function to reset the state of the message input for async voice messages. + */ + const resetState = () => { + setRecording(undefined); + setRecordingStatus('idle'); + setMicLocked(false); + setWaveformData([]); + setPaused(true); + setPosition(0); + setProgress(0); + }; + + /** + * Function to delete voice recording. + */ + const deleteVoiceRecording = async () => { + if (recordingStatus === 'recording') { + await stopVoiceRecording(); + } + if (!paused) { + await stopVoicePlayer(); + } + resetState(); + triggerHaptic('impactMedium'); + }; + + /** + * Function to upload or send voice recording. + * @param multiSendEnabled boolean + */ + const uploadVoiceRecording = async (multiSendEnabled: boolean) => { + if (!paused) { + await stopVoicePlayer(); + } + if (recordingStatus === 'recording') { + await stopVoiceRecording(); + } + + const durationInSeconds = parseFloat((recordingDuration / 1000).toFixed(3)); + + const resampledWaveformData = resampleWaveformData(waveformData, 100); + + const file: File = { + duration: durationInSeconds, + mimeType: 'audio/aac', + name: `audio_recording_${new Date().toISOString()}.aac`, + type: 'voiceRecording', + uri: typeof recording !== 'string' ? (recording?.getURI() as string) : (recording as string), + waveform_data: resampledWaveformData, + }; + + if (multiSendEnabled) { + await uploadNewFile(file); + } else { + // FIXME: cannot call handleSubmit() directly as the function has stale reference to file uploads + await uploadNewFile(file); + setIsScheduleForSubmit(true); + } + resetState(); + }; + + return { + deleteVoiceRecording, + micLocked, + onVoicePlayerPlayPause, + paused, + permissionsGranted, + position, + progress, + recording, + recordingDuration, + recordingStatus, + setMicLocked, + setRecording, + setRecordingDuration, + setRecordingStatus, + setWaveformData, + startVoiceRecording, + stopVoiceRecording, + uploadVoiceRecording, + waveformData, + }; +}; diff --git a/package/src/components/MessageInput/utils/audioSampling.ts b/package/src/components/MessageInput/utils/audioSampling.ts new file mode 100644 index 0000000000..ff4b3589fa --- /dev/null +++ b/package/src/components/MessageInput/utils/audioSampling.ts @@ -0,0 +1,108 @@ +export const resampleWaveformData = (waveformData: number[], amplitudesCount: number) => + waveformData.length === amplitudesCount + ? waveformData + : waveformData.length > amplitudesCount + ? downSample(waveformData, amplitudesCount) + : upSample(waveformData, amplitudesCount); + +/** + * The downSample function uses the Largest-Triangle-Three-Buckets (LTTB) algorithm. + * See the thesis Downsampling Time Series for Visual Representation by Sveinn Steinarsson for more (https://skemman.is/bitstream/1946/15343/3/SS_MSthesis.pdf) + * @param data + * @param targetOutputSize + */ +export function downSample(data: number[], targetOutputSize: number): number[] { + if (data.length <= targetOutputSize || targetOutputSize === 0) { + return data; + } + + if (targetOutputSize === 1) return [mean(data)]; + + const result: number[] = []; + // bucket size adjusted due to the fact that the first and the last item in the original data array is kept in target output + const bucketSize = (data.length - 2) / (targetOutputSize - 2); + let lastSelectedPointIndex = 0; + result.push(data[lastSelectedPointIndex]); // Always add the first point + let maxAreaPoint, maxArea, triangleArea; + + for (let bucketIndex = 1; bucketIndex < targetOutputSize - 1; bucketIndex++) { + const previousBucketRefPoint = data[lastSelectedPointIndex]; + const nextBucketMean = getNextBucketMean(data, bucketIndex, bucketSize); + + const currentBucketStartIndex = Math.floor((bucketIndex - 1) * bucketSize) + 1; + const nextBucketStartIndex = Math.floor(bucketIndex * bucketSize) + 1; + const countUnitsBetweenAtoC = 1 + nextBucketStartIndex - currentBucketStartIndex; + + maxArea = triangleArea = -1; + + for ( + let currentPointIndex = currentBucketStartIndex; + currentPointIndex < nextBucketStartIndex; + currentPointIndex++ + ) { + const countUnitsBetweenAtoB = Math.abs(currentPointIndex - currentBucketStartIndex) + 1; + const countUnitsBetweenBtoC = countUnitsBetweenAtoC - countUnitsBetweenAtoB; + const currentPointValue = data[currentPointIndex]; + + triangleArea = triangleAreaHeron( + triangleBase(Math.abs(previousBucketRefPoint - currentPointValue), countUnitsBetweenAtoB), + triangleBase(Math.abs(currentPointValue - nextBucketMean), countUnitsBetweenBtoC), + triangleBase(Math.abs(previousBucketRefPoint - nextBucketMean), countUnitsBetweenAtoC), + ); + + if (triangleArea > maxArea) { + maxArea = triangleArea; + maxAreaPoint = data[currentPointIndex]; + lastSelectedPointIndex = currentPointIndex; + } + } + + if (typeof maxAreaPoint !== 'undefined') result.push(maxAreaPoint); + } + + result.push(data[data.length - 1]); // Always add the last point + + return result; +} + +const triangleAreaHeron = (a: number, b: number, c: number) => { + const s = (a + b + c) / 2; + return Math.sqrt(s * (s - a) * (s - b) * (s - c)); +}; + +const triangleBase = (a: number, b: number) => Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); +const mean = (values: number[]) => values.reduce((acc, value) => acc + value, 0) / values.length; +export const divMod = (num: number, divisor: number) => [Math.floor(num / divisor), num % divisor]; + +const getNextBucketMean = (data: number[], currentBucketIndex: number, bucketSize: number) => { + const nextBucketStartIndex = Math.floor(currentBucketIndex * bucketSize) + 1; + let nextNextBucketStartIndex = Math.floor((currentBucketIndex + 1) * bucketSize) + 1; + nextNextBucketStartIndex = + nextNextBucketStartIndex < data.length ? nextNextBucketStartIndex : data.length; + + return mean(data.slice(nextBucketStartIndex, nextNextBucketStartIndex)); +}; + +export const upSample = (values: number[], targetSize: number) => { + if (!values.length) { + console.warn('Cannot extend empty array of amplitudes.'); + return values; + } + + if (values.length > targetSize) { + console.warn('Requested to extend the waveformData that is longer than the target list size'); + return values; + } + + if (targetSize === values.length) return values; + + // eslint-disable-next-line prefer-const + let [bucketSize, remainder] = divMod(targetSize, values.length); + const result: number[] = []; + + for (let i = 0; i < values.length; i++) { + const extra = remainder && remainder-- ? 1 : 0; + result.push(...Array(bucketSize + extra).fill(values[i])); + } + return result; +}; diff --git a/package/src/components/MessageInput/utils/normalizeAudioLevel.ts b/package/src/components/MessageInput/utils/normalizeAudioLevel.ts new file mode 100644 index 0000000000..ae4110cc85 --- /dev/null +++ b/package/src/components/MessageInput/utils/normalizeAudioLevel.ts @@ -0,0 +1,20 @@ +/** + * Utility function to normalize the audio level. + */ +export const normalizeAudioLevel = (value: number, lowerBound: number) => { + // For Native CLI, the lower bound is around -50 + const upperBound = 0; + + const delta = upperBound - lowerBound; + + // In Native CLI Android, the value is undefined for loud audio + if (value === undefined) return 1; + + if (value < lowerBound) { + return 0; + } else if (value >= upperBound) { + return 1; + } else { + return Math.abs((value - lowerBound) / delta); + } +}; diff --git a/package/src/components/ProgressControl/ProgressControl.tsx b/package/src/components/ProgressControl/ProgressControl.tsx index 9efb280549..5f11fbb1b2 100644 --- a/package/src/components/ProgressControl/ProgressControl.tsx +++ b/package/src/components/ProgressControl/ProgressControl.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { Dimensions, StyleSheet, View } from 'react-native'; import { PanGestureHandler } from 'react-native-gesture-handler'; import Animated, { cancelAnimation, @@ -14,66 +14,65 @@ import { useTheme } from '../../contexts/themeContext/ThemeContext'; export type ProgressControlProps = { duration: number; filledColor: string; - onPlayPause: (status?: boolean) => void; progress: number; testID: string; - width: number; + width: number | string; + onPlayPause?: (status?: boolean) => void; onProgressDrag?: (progress: number) => void; }; const height = 2; -const styles = StyleSheet.create({ - containerStyle: { - borderRadius: 50, - height, - }, - innerStyle: { - height, - }, - progressControlThumbStyle: { - borderRadius: 5, - elevation: 6, - height: 20, - shadowOffset: { - height: 3, - width: 0, - }, - shadowOpacity: 0.27, - shadowRadius: 4.65, - top: -11, - width: 5, - }, -}); const ProgressControlThumb = () => { const { theme: { - colors: { black }, + colors: { black, grey_dark, static_white }, }, } = useTheme(); return ( ); }; export const ProgressControl = React.memo( (props: ProgressControlProps) => { - const { duration, filledColor, onPlayPause, onProgressDrag, progress, testID, width } = props; + const { + duration, + filledColor: filledColorFromProp, + onPlayPause, + onProgressDrag, + progress, + testID, + width, + } = props; + const { width: windowWidth } = Dimensions.get('screen'); + const widthInNumbers = width + ? typeof width === 'string' + ? (windowWidth * Number(width?.substring(0, width.length - 1))) / 100 + : width + : 0; const { theme: { colors: { grey_dark }, + progressControl: { container, filledColor: filledColorFromTheme, filledStyles, thumb }, }, } = useTheme(); const state = useSharedValue(0); const translateX = useSharedValue(0); + const filledColor = filledColorFromProp || filledColorFromTheme; useEffect(() => { - state.value = progress * width; - translateX.value = progress * width; - }, [progress]); + if (progress <= 1) { + state.value = progress * widthInNumbers; + translateX.value = progress * widthInNumbers; + } + }, [progress, widthInNumbers]); const animatedStyles = useAnimatedStyle(() => ({ backgroundColor: filledColor, @@ -88,33 +87,36 @@ export const ProgressControl = React.memo( { onActive: (event) => { state.value = translateX.value + event.translationX; - if (state.value > width) state.value = width; + if (state.value > widthInNumbers) state.value = widthInNumbers; else if (state.value < 0) state.value = 0; }, onFinish: () => { translateX.value = state.value; - const dragFinishLocationInSeconds = (state.value / width) * duration; + const dragFinishLocationInSeconds = (state.value / widthInNumbers) * duration; if (onProgressDrag) runOnJS(onProgressDrag)(dragFinishLocationInSeconds); - runOnJS(onPlayPause)(false); + if (onPlayPause) runOnJS(onPlayPause)(false); }, onStart: () => { - runOnJS(onPlayPause)(true); + if (onPlayPause) runOnJS(onPlayPause)(true); cancelAnimation(translateX); state.value = translateX.value; }, }, - [duration], + [duration, widthInNumbers], ); + return ( - - + + - + {onProgressDrag && } @@ -122,10 +124,37 @@ export const ProgressControl = React.memo( ); }, (prevProps, nextProps) => { - if (prevProps.duration === nextProps.duration && prevProps.progress === nextProps.progress) + if ( + prevProps.duration === nextProps.duration && + prevProps.progress === nextProps.progress && + prevProps.width === nextProps.width + ) return true; else return false; }, ); +const styles = StyleSheet.create({ + container: { + borderRadius: 50, + height, + }, + filledStyle: { + height, + }, + progressControlThumbStyle: { + borderRadius: 5, + borderWidth: 0.2, + elevation: 6, + height: 20, + shadowOffset: { + height: 3, + width: 0, + }, + shadowOpacity: 0.27, + shadowRadius: 4.65, + top: -11, + width: 5, + }, +}); ProgressControl.displayName = 'ProgressControl'; diff --git a/package/src/components/ProgressControl/WaveProgressBar.tsx b/package/src/components/ProgressControl/WaveProgressBar.tsx new file mode 100644 index 0000000000..c43cd0e7f6 --- /dev/null +++ b/package/src/components/ProgressControl/WaveProgressBar.tsx @@ -0,0 +1,180 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { Platform, Pressable, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; +import { PanGestureHandler } from 'react-native-gesture-handler'; +import Animated, { + runOnJS, + useAnimatedGestureHandler, + useAnimatedStyle, + useSharedValue, +} from 'react-native-reanimated'; + +import { useTheme } from '../../contexts/themeContext/ThemeContext'; +import { triggerHaptic } from '../../native'; +import { resampleWaveformData } from '../MessageInput/utils/audioSampling'; + +export type WaveProgressBarProps = { + progress: number; + waveformData: number[]; + amplitudesCount?: number; + filledColor?: string; + onPlayPause?: (status?: boolean) => void; + onProgressDrag?: (progress: number) => void; +}; + +const WAVEFORM_WIDTH = 2; + +const ProgressControlThumb = ({ style }: { style?: StyleProp }) => { + const { + theme: { + colors: { black, grey_dark, static_white }, + }, + } = useTheme(); + return ( + + + + ); +}; + +export const WaveProgressBar = React.memo( + (props: WaveProgressBarProps) => { + const [endPosition, setEndPosition] = useState(0); + const [currentWaveformProgress, setCurrentWaveformProgress] = useState(0); + /* On Android, the seek doesn't work for AAC files, hence we disable progress drag for now */ + const showProgressDrag = Platform.OS === 'ios'; + const { + amplitudesCount = 70, + filledColor, + onPlayPause, + onProgressDrag, + progress, + waveformData, + } = props; + const { + theme: { + colors: { accent_blue, grey_dark }, + waveProgressBar: { container, thumb, waveform: waveformTheme }, + }, + } = useTheme(); + const state = useSharedValue(0); + + useEffect(() => { + const stageProgress = Math.floor( + progress * (showProgressDrag ? amplitudesCount - 1 : amplitudesCount), + ); + state.value = stageProgress * (WAVEFORM_WIDTH * 2); + setEndPosition(state.value); + setCurrentWaveformProgress(stageProgress); + }, [progress]); + + const stringifiedWaveformData = waveformData.toString(); + + const resampledWaveformData = useMemo( + () => resampleWaveformData(waveformData, amplitudesCount), + [amplitudesCount, stringifiedWaveformData], + ); + + const thumbStyles = useAnimatedStyle(() => ({ + position: 'absolute', + transform: [{ translateX: state.value }], + })); + + const onGestureEvent = useAnimatedGestureHandler( + { + onActive: (event) => { + const stage = Math.floor((endPosition + event.translationX) / (WAVEFORM_WIDTH * 2)); + runOnJS(setCurrentWaveformProgress)(stage); + state.value = stage * (WAVEFORM_WIDTH * 2); + if (state.value < 0) { + state.value = 0; + } else if (state.value > amplitudesCount * (WAVEFORM_WIDTH * 2)) { + state.value = (amplitudesCount - 1) * (WAVEFORM_WIDTH * 2); + } else { + runOnJS(triggerHaptic)('impactLight'); + } + }, + onFinish: () => { + const stage = Math.floor(state.value / (WAVEFORM_WIDTH * 2)); + runOnJS(setEndPosition)(state.value); + if (onProgressDrag) runOnJS(onProgressDrag)(stage); + if (onPlayPause) runOnJS(onPlayPause)(false); + }, + onStart: () => { + if (onPlayPause) runOnJS(onPlayPause)(true); + state.value = endPosition; + }, + }, + [amplitudesCount, endPosition], + ); + + return ( + + {resampledWaveformData.map((waveform, index) => ( + 3 ? waveform * 25 : 3, + }, + waveformTheme, + ]} + /> + ))} + {showProgressDrag && onProgressDrag && ( + + + + + + )} + + ); + }, + (prevProps, nextProps) => { + if (prevProps.amplitudesCount !== nextProps.amplitudesCount) return false; + if (prevProps.progress !== nextProps.progress) return false; + else return true; + }, +); + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + alignSelf: 'center', + flexDirection: 'row', + }, + progressControlThumbStyle: { + borderRadius: 5, + borderWidth: 0.2, + elevation: 6, + height: 25, + shadowOffset: { + height: 3, + width: 0, + }, + shadowOpacity: 0.27, + shadowRadius: 4.65, + width: WAVEFORM_WIDTH * 2, + }, + waveform: { + alignSelf: 'center', + borderRadius: 2, + marginHorizontal: WAVEFORM_WIDTH / 2, + width: WAVEFORM_WIDTH, + }, +}); + +WaveProgressBar.displayName = 'WaveProgressBar'; diff --git a/package/src/components/Reply/Reply.tsx b/package/src/components/Reply/Reply.tsx index 9acb30291f..f1f20be57c 100644 --- a/package/src/components/Reply/Reply.tsx +++ b/package/src/components/Reply/Reply.tsx @@ -1,5 +1,8 @@ import React, { useContext, useMemo, useState } from 'react'; -import { Image, ImageStyle, StyleSheet, View, ViewStyle } from 'react-native'; + +import { Image, ImageStyle, StyleSheet, Text, View, ViewStyle } from 'react-native'; + +import dayjs from 'dayjs'; import merge from 'lodash/merge'; @@ -21,6 +24,7 @@ import { } from '../../contexts/translationContext/TranslationContext'; import type { DefaultStreamChatGenerics } from '../../types/types'; import { getResizedImageUrl } from '../../utils/getResizedImageUrl'; +import { getTrimmedAttachmentTitle } from '../../utils/getTrimmedAttachmentTitle'; import { hasOnlyEmojis } from '../../utils/utils'; import { FileIcon as FileIconDefault } from '../Attachment/FileIcon'; @@ -51,7 +55,10 @@ const styles = StyleSheet.create({ flexGrow: 1, flexShrink: 1, }, - text: { fontSize: 12 }, + secondaryText: { + paddingHorizontal: 8, + }, + text: { fontSize: 12, fontWeight: 'bold', overflow: 'hidden' }, textContainer: { maxWidth: undefined, paddingHorizontal: 8 }, videoThumbnailContainerStyle: { borderRadius: 8, @@ -87,7 +94,11 @@ const getMessageType = < ) => { let messageType; - const isLastAttachmentFile = lastAttachment.type === 'file' || lastAttachment.type === 'audio'; + const isLastAttachmentFile = lastAttachment.type === 'file'; + + const isLastAttachmentAudio = lastAttachment.type === 'audio'; + + const isLastAttachmentVoiceRecording = lastAttachment.type === 'voiceRecording'; const isLastAttachmentVideo = lastAttachment.type === 'video'; @@ -105,6 +116,10 @@ const getMessageType = < messageType = 'file'; } else if (isLastAttachmentVideo) { messageType = 'video'; + } else if (isLastAttachmentAudio) { + messageType = 'audio'; + } else if (isLastAttachmentVoiceRecording) { + messageType = 'voiceRecording'; } else if (isLastAttachmentImageOrGiphy) { if (isLastAttachmentImage) messageType = 'image'; else messageType = undefined; @@ -142,6 +157,7 @@ const ReplyWithContext = < imageAttachment, markdownStyles, messageContainer, + secondaryText, textContainer, videoThumbnail: { container: videoThumbnailContainerStyle, @@ -163,11 +179,15 @@ const ReplyWithContext = < const lastAttachment = quotedMessage.attachments?.slice(-1)[0] as Attachment; const messageType = lastAttachment && getMessageType(lastAttachment); + const trimmedLastAttachmentTitle = getTrimmedAttachmentTitle(lastAttachment?.title); + const hasImage = !error && lastAttachment && messageType !== 'file' && messageType !== 'video' && + messageType !== 'audio' && + messageType !== 'voiceRecording' && (lastAttachment.image_url || lastAttachment.thumb_url || lastAttachment.og_scrape_url); const onlyEmojis = !lastAttachment && emojiOnlyText; @@ -189,7 +209,7 @@ const ReplyWithContext = < ]} > {!error && lastAttachment ? ( - messageType === 'file' ? ( + messageType === 'file' || messageType === 'voiceRecording' || messageType === 'audio' ? ( ) : null} - - markdownStyles={ - quotedMessage.type === 'deleted' - ? merge({ em: { color: grey } }, deletedText) - : { text: styles.text, ...markdownStyles } - } - message={{ - ...quotedMessage, - text: + + + markdownStyles={ quotedMessage.type === 'deleted' - ? `_${t('Message deleted')}_` - : quotedMessage.text - ? quotedMessage.text.length > 170 - ? `${quotedMessage.text.slice(0, 170)}...` + ? merge({ em: { color: grey } }, deletedText) + : { text: styles.text, ...markdownStyles } + } + message={{ + ...quotedMessage, + text: + quotedMessage.type === 'deleted' + ? `_${t('Message deleted')}_` : quotedMessage.text - : messageType === 'image' - ? t('Photo') - : messageType === 'video' - ? t('Video') - : messageType === 'file' - ? lastAttachment?.title || '' - : '', - }} - onlyEmojis={onlyEmojis} - styles={{ - textContainer: [ - { - marginRight: - hasImage || messageType === 'video' - ? Number( - stylesProp.imageAttachment?.height || - imageAttachment.height || - styles.imageAttachment.height, - ) + - Number( - stylesProp.imageAttachment?.marginLeft || - imageAttachment.marginLeft || - styles.imageAttachment.marginLeft, - ) - : messageType === 'file' - ? attachmentSize + - Number( - stylesProp.fileAttachmentContainer?.paddingLeft || - fileAttachmentContainer.paddingLeft || - styles.fileAttachmentContainer.paddingLeft, - ) - : undefined, - }, - styles.textContainer, - textContainer, - stylesProp.textContainer, - ], - }} - /> + ? quotedMessage.text.length > 170 + ? `${quotedMessage.text.slice(0, 170)}...` + : quotedMessage.text + : messageType === 'image' + ? t('Photo') + : messageType === 'video' + ? t('Video') + : messageType === 'file' || + messageType === 'audio' || + messageType === 'voiceRecording' + ? trimmedLastAttachmentTitle || '' + : '', + }} + onlyEmojis={onlyEmojis} + styles={{ + textContainer: [ + { + marginRight: + hasImage || messageType === 'video' + ? Number( + stylesProp.imageAttachment?.height || + imageAttachment.height || + styles.imageAttachment.height, + ) + + Number( + stylesProp.imageAttachment?.marginLeft || + imageAttachment.marginLeft || + styles.imageAttachment.marginLeft, + ) + : messageType === 'file' || + messageType === 'audio' || + messageType === 'voiceRecording' + ? attachmentSize + + Number( + stylesProp.fileAttachmentContainer?.paddingLeft || + fileAttachmentContainer.paddingLeft || + styles.fileAttachmentContainer.paddingLeft, + ) + : undefined, + }, + styles.textContainer, + textContainer, + stylesProp.textContainer, + ], + }} + /> + {messageType === 'audio' || messageType === 'voiceRecording' ? ( + + {lastAttachment.duration + ? dayjs.duration(lastAttachment.duration, 'second').format('mm:ss') + : ''} + + ) : null} + ); diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap index 30410562f5..4854dc28f2 100644 --- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap @@ -1503,8 +1503,9 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { - "alignItems": "flex-end", + "alignItems": "center", "flexDirection": "row", + "justifyContent": "space-between", }, {}, ] @@ -1515,8 +1516,6 @@ exports[`Thread should match thread snapshot 1`] = ` [ { "flexDirection": "row", - "paddingBottom": 10, - "paddingRight": 10, }, {}, ] @@ -1526,7 +1525,7 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { - "paddingRight": 10, + "paddingRight": 5, }, {}, ] @@ -1729,6 +1728,7 @@ exports[`Thread should match thread snapshot 1`] = ` "borderRadius": 20, "borderWidth": 1, "flex": 1, + "marginHorizontal": 10, }, { "borderColor": "#ECEBEB", @@ -1790,10 +1790,7 @@ exports[`Thread should match thread snapshot 1`] = ` + >; setText: React.Dispatch>; showMoreOptions: boolean; - /** - * Text value of the TextInput - */ text: string; toggleAttachmentPicker: () => void; /** @@ -213,12 +217,68 @@ export type LocalMessageInputContext< export type InputMessageInputContextValue< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Pick, 'disabled'> & { + /** + * 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. + */ + asyncMessagesLockDistance: number; + /** + * Controls the minimum duration that the user has to press on the record button in the composer, in order to start recording a new voice message. + */ + asyncMessagesMinimumPressDuration: number; + /** + * When it’s enabled, recorded messages won’t be sent immediately. Instead they will “stack up” in the composer allowing the user to send multiple voice recording as part of the same message. + */ + asyncMessagesMultiSendEnabled: boolean; + /** + * Controls how many pixels to the leading side the user has to scroll in order to cancel the recording of a voice message. + */ + asyncMessagesSlideToCancelDistance: number; /** * Custom UI component for attach button. * * Defaults to and accepts same props as: [AttachButton](https://getstream.io/chat/docs/sdk/reactnative/ui-components/attach-button/) */ AttachButton: React.ComponentType>; + /** + * Custom UI component for audio attachment upload preview. + * + * Defaults to and accepts same props as: [AudioAttachmentUploadPreview](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/AudioAttachment.tsx) + */ + AudioAttachmentUploadPreview: React.ComponentType; + /** + * Custom UI component for audio recorder UI. + * + * 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>; + /** + * Controls whether the async audio feature is enabled. + */ + audioRecordingEnabled: boolean; + /** + * Custom UI component to render audio recording in progress. + * + * **Default** [AudioRecordingInProgress](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingInProgress.tsx) + */ + AudioRecordingInProgress: React.ComponentType; + /** + * Custom UI component for audio recording lock indicator. + * + * Defaults to and accepts same props as: [AudioRecordingLockIndicator](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingLockIndicator.tsx) + */ + AudioRecordingLockIndicator: React.ComponentType; + /** + * Custom UI component to render audio recording preview. + * + * **Default** [AudioRecordingPreview](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingPreview.tsx) + */ + AudioRecordingPreview: React.ComponentType; + /** + * Custom UI component to render audio recording waveform. + * + * **Default** [AudioRecordingWaveform](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/components/AudioRecorder/AudioRecordingWaveform.tsx) + */ + AudioRecordingWaveform: React.ComponentType; clearEditingState: () => void; clearQuotedMessageState: () => void; @@ -260,6 +320,7 @@ export type InputMessageInputContextValue< InputReplyStateHeader: React.ComponentType>; /** Limit on allowed number of files to attach at a time. */ maxNumberOfFiles: number; + /** * Custom UI component for more options button. * @@ -285,12 +346,19 @@ export type InputMessageInputContextValue< ShowThreadMessageInChannelButton: React.ComponentType<{ threadList?: boolean; }>; + /** + * Custom UI component for audio recording mic button. + * + * 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>; /** * Custom UI component to render upload progress indicator on attachment preview. * * **Default** [UploadProgressIndicator](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/UploadProgressIndicator.tsx) */ UploadProgressIndicator: React.ComponentType; + /** * Additional props for underlying TextInput component. These props will be forwarded as it is to TextInput component. * @@ -666,7 +734,7 @@ export const MessageInputProvider = < }; const mapFileUploadToAttachment = (file: FileUpload): Attachment => { - if (file.file.mimeType?.startsWith('image/')) { + if (file.type === 'image') { return { fallback: file.file.name, image_url: file.url, @@ -674,7 +742,7 @@ export const MessageInputProvider = < originalFile: file.file, type: 'image', }; - } else if (file.file.mimeType?.startsWith('audio/')) { + } else if (file.type === 'audio') { return { asset_url: file.url || file.file.uri, duration: file.file.duration, @@ -684,7 +752,7 @@ export const MessageInputProvider = < title: file.file.name, type: 'audio', }; - } else if (file.file.mimeType?.startsWith('video/')) { + } else if (file.type === 'video') { return { asset_url: file.url || file.file.uri, duration: file.file.duration, @@ -695,6 +763,17 @@ export const MessageInputProvider = < title: file.file.name, type: 'video', }; + } else if (file.type === '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: 'voiceRecording', + waveform_data: file.file.waveform_data, + }; } else { return { asset_url: file.url || file.file.uri, @@ -1009,7 +1088,11 @@ export const MessageInputProvider = < } uploadAbortControllerRef.current.delete(file.name); } - const extraData: Partial = { thumb_url: response.thumb_url, url: response.file }; + + const extraData: Partial = { + thumb_url: response.thumb_url, + url: response.file, + }; setFileUploads(getUploadSetStateAction(id, FileState.UPLOADED, extraData)); } catch (error: unknown) { if ( @@ -1121,13 +1204,15 @@ export const MessageInputProvider = < ? FileState.NOT_SUPPORTED : FileState.UPLOADING; + // 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 newFile: FileUpload = { - duration: 0, + duration: file.duration || 0, file, id: file.id || id, - paused: true, - progress: 0, state: fileState, + type: fileType, }; await Promise.all([ diff --git a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts index 68f18d7627..a09a8737d7 100644 --- a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts +++ b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts @@ -10,8 +10,19 @@ export const useCreateMessageInputContext = < additionalTextInputProps, appendText, asyncIds, + asyncMessagesLockDistance, + asyncMessagesMinimumPressDuration, + asyncMessagesMultiSendEnabled, + asyncMessagesSlideToCancelDistance, asyncUploads, AttachButton, + AudioAttachmentUploadPreview, + AudioRecorder, + audioRecordingEnabled, + AudioRecordingInProgress, + AudioRecordingLockIndicator, + AudioRecordingPreview, + AudioRecordingWaveform, autoCompleteSuggestionsLimit, clearEditingState, clearQuotedMessageState, @@ -57,6 +68,7 @@ export const useCreateMessageInputContext = < openCommandsPicker, openFilePicker, openMentionsPicker, + pickFile, quotedMessage, removeFile, @@ -85,6 +97,7 @@ export const useCreateMessageInputContext = < setText, showMoreOptions, ShowThreadMessageInChannelButton, + StartAudioRecordingButton, text, thread, toggleAttachmentPicker, @@ -117,8 +130,19 @@ export const useCreateMessageInputContext = < additionalTextInputProps, appendText, asyncIds, + asyncMessagesLockDistance, + asyncMessagesMinimumPressDuration, + asyncMessagesMultiSendEnabled, + asyncMessagesSlideToCancelDistance, asyncUploads, AttachButton, + AudioAttachmentUploadPreview, + AudioRecorder, + audioRecordingEnabled, + AudioRecordingInProgress, + AudioRecordingLockIndicator, + AudioRecordingPreview, + AudioRecordingWaveform, autoCompleteSuggestionsLimit, clearEditingState, clearQuotedMessageState, @@ -192,6 +216,7 @@ export const useCreateMessageInputContext = < setText, showMoreOptions, ShowThreadMessageInChannelButton, + StartAudioRecordingButton, text, toggleAttachmentPicker, triggerSettings, diff --git a/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts b/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts index 048261a3fd..9abf1c0b4b 100644 --- a/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts +++ b/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts @@ -1,5 +1,7 @@ import { useEffect, useState } from 'react'; +import { Attachment } from 'stream-chat'; + import type { DefaultStreamChatGenerics, FileUpload, ImageUpload } from '../../../types/types'; import { generateRandomId } from '../../../utils/utils'; @@ -16,6 +18,7 @@ export const useMessageDetailsForState = < const [mentionedUsers, setMentionedUsers] = useState([]); const [numberOfUploads, setNumberOfUploads] = useState(0); const [showMoreOptions, setShowMoreOptions] = useState(true); + const initialTextValue = initialValue || ''; const [text, setText] = useState(initialTextValue); @@ -23,7 +26,10 @@ export const useMessageDetailsForState = < if (text !== initialTextValue) { setShowMoreOptions(false); } - }, [text]); + if (fileUploads.length || imageUploads.length) { + setShowMoreOptions(false); + } + }, [text, imageUploads.length, fileUploads.length]); const messageValue = message === undefined ? '' : `${message.id}${message.text}${message.updated_at}`; @@ -35,6 +41,73 @@ export const useMessageDetailsForState = < } }, [messageValue]); + const mapAttachmentToFileUpload = (attachment: Attachment): FileUpload => { + const id = generateRandomId(); + + if (attachment.type === 'audio') { + return { + file: { + duration: attachment.duration, + mimeType: attachment.mime_type, + name: attachment.title || '', + size: attachment.file_size, + uri: attachment.asset_url, + }, + id, + state: 'finished', + url: attachment.asset_url, + }; + } else if (attachment.type === 'video') { + return { + file: { + mimeType: attachment.mime_type, + name: attachment.title || '', + size: attachment.file_size, + }, + id, + state: 'finished', + thumb_url: attachment.thumb_url, + url: attachment.asset_url, + }; + } else if (attachment.type === 'voiceRecording') { + return { + file: { + duration: attachment.duration, + mimeType: attachment.mime_type, + name: attachment.title || '', + size: attachment.file_size, + uri: attachment.asset_url, + waveform_data: attachment.waveform_data, + }, + id, + state: 'finished', + url: attachment.asset_url, + }; + } else if (attachment.type === 'file') { + return { + file: { + mimeType: attachment.mime_type, + name: attachment.title || '', + size: attachment.file_size, + }, + id, + state: 'finished', + url: attachment.asset_url, + }; + } else { + return { + file: { + mimeType: attachment.mime_type, + name: attachment.title || '', + size: attachment.file_size, + }, + id, + state: 'finished', + url: attachment.asset_url, + }; + } + }; + useEffect(() => { if (message) { setText(message?.text || ''); @@ -44,19 +117,7 @@ export const useMessageDetailsForState = < const attachments = Array.isArray(message.attachments) ? message.attachments : []; for (const attachment of attachments) { - if (attachment.type === 'file') { - const id = generateRandomId(); - newFileUploads.push({ - file: { - mimeType: attachment.mime_type, - name: attachment.title || '', - size: attachment.file_size, - }, - id, - state: 'finished', - url: attachment.asset_url, - }); - } else if (attachment.type === 'image') { + if (attachment.type === 'image') { const id = generateRandomId(); newImageUploads.push({ file: { @@ -68,19 +129,11 @@ export const useMessageDetailsForState = < state: 'finished', url: attachment.image_url || attachment.asset_url || attachment.thumb_url, }); - } else if (attachment.type === 'video') { - const id = generateRandomId(); - newFileUploads.push({ - file: { - mimeType: attachment.mime_type, - name: attachment.title || '', - size: attachment.file_size, - }, - id, - state: 'finished', - thumb_url: attachment.thumb_url, - url: attachment.asset_url, - }); + } else { + const fileUpload = mapAttachmentToFileUpload(attachment); + if (fileUpload) { + newFileUploads.push(fileUpload); + } } } if (newFileUploads.length) { diff --git a/package/src/contexts/themeContext/utils/theme.ts b/package/src/contexts/themeContext/utils/theme.ts index ae35861009..543babc400 100644 --- a/package/src/contexts/themeContext/utils/theme.ts +++ b/package/src/contexts/themeContext/utils/theme.ts @@ -96,6 +96,16 @@ export type Theme = { container: ViewStyle; icon: ViewStyle; }; + audioAttachment: { + container: ViewStyle; + leftContainer: ViewStyle; + playPauseButton: ViewStyle; + progressControlContainer: ViewStyle; + progressDurationText: TextStyle; + rightContainer: ViewStyle; + speedChangeButton: ViewStyle; + speedChangeButtonText: TextStyle; + }; avatar: { BASE_AVATAR_SIZE: number; container: ViewStyle; @@ -230,6 +240,44 @@ export type Theme = { attachButton: ViewStyle; attachButtonContainer: ViewStyle; attachmentSelectionBar: ViewStyle; + attachmentSeparator: ViewStyle; + audioRecorder: { + arrowLeftIcon: IconProps; + checkContainer: ViewStyle; + circleStopIcon: IconProps; + deleteContainer: ViewStyle; + deleteIcon: IconProps; + micContainer: ViewStyle; + micIcon: IconProps; + pausedContainer: ViewStyle; + sendCheckIcon: IconProps; + slideToCancelContainer: ViewStyle; + }; + audioRecordingButton: { + container: ViewStyle; + micIcon: IconProps; + }; + audioRecordingInProgress: { + container: ViewStyle; + durationText: TextStyle; + }; + audioRecordingLockIndicator: { + arrowUpIcon: IconProps; + container: ViewStyle; + lockIcon: IconProps; + }; + audioRecordingPreview: { + container: ViewStyle; + currentTime: TextStyle; + infoContainer: ViewStyle; + pauseIcon: IconProps; + playIcon: IconProps; + progressBar: ViewStyle; + }; + audioRecordingWaveform: { + container: ViewStyle; + waveform: ViewStyle; + }; autoCompleteInputContainer: ViewStyle; commandsButton: ViewStyle; commandsButtonContainer: ViewStyle; @@ -247,15 +295,8 @@ export type Theme = { editingBoxHeaderTitle: TextStyle; }; fileUploadPreview: { - audioAttachment: { - progressControlView: ViewStyle; - progressDurationText: TextStyle; - roundedView: ViewStyle; - }; - audioAttachmentFileContainer: ViewStyle; dismiss: ViewStyle; fileContainer: ViewStyle; - fileContentContainer: ViewStyle; filenameText: TextStyle; fileSizeText: TextStyle; fileTextContainer: ViewStyle; @@ -275,6 +316,7 @@ export type Theme = { }; inputBox: TextStyle; inputBoxContainer: ViewStyle; + micButtonContainer: ViewStyle; moreOptionsButton: ViewStyle; optionsContainer: ViewStyle; replyContainer: ViewStyle; @@ -437,6 +479,7 @@ export type Theme = { title: TextStyle; }; fileAttachmentGroup: { + attachmentContainer: ViewStyle; container: ViewStyle; }; gallery: { @@ -545,12 +588,19 @@ export type Theme = { reactionSize: number; }; }; + progressControl: { + container: ViewStyle; + filledColor: ColorValue; + filledStyles: ViewStyle; + thumb: ViewStyle; + }; reply: { container: ViewStyle; fileAttachmentContainer: ViewStyle; imageAttachment: ImageStyle; markdownStyles: MarkdownStyle; messageContainer: ViewStyle; + secondaryText: ViewStyle; textContainer: ViewStyle; videoThumbnail: { container: ViewStyle; @@ -573,6 +623,11 @@ export type Theme = { fontSize: TextStyle['fontSize']; }; }; + waveProgressBar: { + container: ViewStyle; + thumb: ViewStyle; + waveform: ViewStyle; + }; }; export const defaultTheme: Theme = { @@ -592,6 +647,16 @@ export const defaultTheme: Theme = { container: {}, icon: {}, }, + audioAttachment: { + container: {}, + leftContainer: {}, + playPauseButton: {}, + progressControlContainer: {}, + progressDurationText: {}, + rightContainer: {}, + speedChangeButton: {}, + speedChangeButtonText: {}, + }, avatar: { BASE_AVATAR_SIZE: 32, container: {}, @@ -745,6 +810,31 @@ export const defaultTheme: Theme = { attachButton: {}, attachButtonContainer: {}, attachmentSelectionBar: {}, + attachmentSeparator: {}, + audioRecorder: { + arrowLeftIcon: {}, + checkContainer: {}, + circleStopIcon: {}, + deleteContainer: {}, + deleteIcon: {}, + micContainer: {}, + micIcon: {}, + pausedContainer: {}, + sendCheckIcon: {}, + slideToCancelContainer: {}, + }, + audioRecordingButton: { container: {}, micIcon: {} }, + audioRecordingInProgress: { container: {}, durationText: {} }, + audioRecordingLockIndicator: { arrowUpIcon: {}, container: {}, lockIcon: {} }, + audioRecordingPreview: { + container: {}, + currentTime: {}, + infoContainer: {}, + pauseIcon: {}, + playIcon: {}, + progressBar: {}, + }, + audioRecordingWaveform: { container: {}, waveform: {} }, autoCompleteInputContainer: {}, commandsButton: {}, commandsButtonContainer: {}, @@ -762,15 +852,8 @@ export const defaultTheme: Theme = { editingBoxHeaderTitle: {}, }, fileUploadPreview: { - audioAttachment: { - progressControlView: {}, - progressDurationText: {}, - roundedView: {}, - }, - audioAttachmentFileContainer: {}, dismiss: {}, fileContainer: {}, - fileContentContainer: {}, filenameText: {}, fileSizeText: {}, fileTextContainer: {}, @@ -790,6 +873,7 @@ export const defaultTheme: Theme = { }, inputBox: {}, inputBoxContainer: {}, + micButtonContainer: {}, moreOptionsButton: {}, optionsContainer: {}, replyContainer: {}, @@ -979,6 +1063,7 @@ export const defaultTheme: Theme = { title: {}, }, fileAttachmentGroup: { + attachmentContainer: {}, container: {}, }, gallery: { @@ -1095,12 +1180,19 @@ export const defaultTheme: Theme = { reactionSize: 24, }, }, + progressControl: { + container: {}, + filledColor: '', + filledStyles: {}, + thumb: {}, + }, reply: { container: {}, fileAttachmentContainer: {}, imageAttachment: {}, markdownStyles: {}, messageContainer: {}, + secondaryText: {}, textContainer: {}, videoThumbnail: { container: {}, @@ -1120,4 +1212,9 @@ export const defaultTheme: Theme = { fontSize: 14, }, }, + waveProgressBar: { + container: {}, + thumb: {}, + waveform: {}, + }, }; diff --git a/package/src/i18n/en.json b/package/src/i18n/en.json index 6068a42ee7..cfa52931ec 100644 --- a/package/src/i18n/en.json +++ b/package/src/i18n/en.json @@ -27,6 +27,7 @@ "Flag": "Flag", "Flag Message": "Flag Message", "Flag action failed either due to a network issue or the message is already flagged": "Flag action failed either due to a network issue or the message is already flagged.", + "Hold to start recording.": "Hold to start recording.", "How about sending your first message to a friend?": "How about sending your first message to a friend?", "Instant Commands": "Instant Commands", "Let's start chatting!": "Let's start chatting!", @@ -48,6 +49,7 @@ "Photos and Videos": "Photos and Videos", "Pin to Conversation": "Pin to Conversation", "Pinned by": "Pinned by", + "Please allow Audio permissions in settings.": "Please allow Audio permissions in settings.", "Please enable access to your photos and videos so you can share them.": "Please enable access to your photos and videos so you can share them.", "Please select a channel first": "Please select a channel first", "Reconnecting...": "Reconnecting...", diff --git a/package/src/i18n/es.json b/package/src/i18n/es.json index 5a8e29379b..3054d16201 100644 --- a/package/src/i18n/es.json +++ b/package/src/i18n/es.json @@ -27,6 +27,7 @@ "Flag": "Reportar", "Flag Message": "Reportar mensaje", "Flag action failed either due to a network issue or the message is already flagged": "El reporte falló debido a un problema de red o el mensaje ya fue reportado.", + "Hold to start recording.": "Mantén presionado para comenzar a grabar.", "How about sending your first message to a friend?": "¿Qué tal enviar tu primer mensaje a un amigo?", "Instant Commands": "Comandos instantáneos", "Let's start chatting!": "¡Empecemos a charlar!", @@ -48,6 +49,7 @@ "Photos and Videos": "Fotos y videos", "Pin to Conversation": "Fijar a la conversación", "Pinned by": "Fijado por", + "Please allow Audio permissions in settings.": "Por favor, permita los permisos de audio en la configuración.", "Please enable access to your photos and videos so you can share them.": "Por favor, habilita el acceso a tus fotos y videos para poder compartirlos.", "Please select a channel first": "Por favor, selecciona primero un canal", "Reconnecting...": "Reconectando...", diff --git a/package/src/i18n/fr.json b/package/src/i18n/fr.json index 970fe8cf32..bacfea36e5 100644 --- a/package/src/i18n/fr.json +++ b/package/src/i18n/fr.json @@ -27,6 +27,7 @@ "Flag": "Signaler", "Flag Message": "Signaler le message", "Flag action failed either due to a network issue or the message is already flagged": "L'action de signalisation a échoué en raison d'un problème de réseau ou le message est déjà signalé.", + "Hold to start recording.": "Hold to start recording.", "How about sending your first message to a friend?": "Et si vous envoyiez votre premier message à un ami ?", "Instant Commands": "Commandes Instantanées", "Let's start chatting!": "Commençons à discuter !", @@ -48,6 +49,7 @@ "Photos and Videos": "Photos et vidéos", "Pin to Conversation": "Épingler à la conversation", "Pinned by": "Épinglé par", + "Please allow Audio permissions in settings.": "Veuillez autoriser les permissions audio dans les paramètres.", "Please enable access to your photos and videos so you can share them.": "Veuillez autoriser l'accès à vos photos et vidéos afin de pouvoir les partager.", "Please select a channel first": "Veuillez d'abord selectionnez un canal", "Reconnecting...": "Se Reconnecter...", diff --git a/package/src/i18n/he.json b/package/src/i18n/he.json index 9d959f4505..0636ef522d 100644 --- a/package/src/i18n/he.json +++ b/package/src/i18n/he.json @@ -27,6 +27,7 @@ "Flag": "סמן", "Flag Message": "סמן הודעה", "Flag action failed either due to a network issue or the message is already flagged": "פעולת הסימון נכשלה בגלל בעיית רשת או שההודעה כבר סומנה.", + "Hold to start recording.": "לחץ והחזק כדי להתחיל להקליט.", "How about sending your first message to a friend?": "מה דעתך לשלוח את ההודעה הראשונה שלך לחבר?", "Instant Commands": "פעולות מיידיות", "Let's start chatting!": "בואו נתחיל לשוחח!", @@ -48,6 +49,7 @@ "Photos and Videos": "תמונות ווידאו", "Pin to Conversation": "הצמד/י לשיחה", "Pinned by": " - הוצמד לשיחה", + "Please allow Audio permissions in settings.": "בבקשה, הרשה הרשאות שמע בהגדרות.", "Please enable access to your photos and videos so you can share them.": "אפשר/י גישה לתמונות ולסרטונים שלך כדי שתוכל/י לשתף אותם.", "Please select a channel first": "אנא בחר/י שיחה תחילה", "Reconnecting...": "מתחבר מחדש...", diff --git a/package/src/i18n/hi.json b/package/src/i18n/hi.json index 73f00c3f1c..f36688a5d2 100644 --- a/package/src/i18n/hi.json +++ b/package/src/i18n/hi.json @@ -27,6 +27,7 @@ "Flag": "झंडा", "Flag Message": "झंडा संदेश", "Flag action failed either due to a network issue or the message is already flagged": "फ़्लैग कार्रवाई या तो नेटवर्क समस्या के कारण विफल हो गई या संदेश पहले से फ़्लैग किया गया है।", + "Hold to start recording.": "रिकॉर्डिंग शुरू करने के लिए दबाएं।", "How about sending your first message to a friend?": "किसी मित्र को अपना पहला संदेश भेजने के बारे में क्या ख़याल है?", "Instant Commands": "त्वरित कमांड", "Let's start chatting!": "आइए चैट करना शुरू करें!", @@ -48,6 +49,7 @@ "Photos and Videos": "तस्वीरें और वीडियों", "Pin to Conversation": "बातचीत में पिन करें", "Pinned by": "द्वारा पिन किया गया", + "Please allow Audio permissions in settings.": "कृपया सेटिंग्स में ऑडियो की अनुमति दें।", "Please enable access to your photos and videos so you can share them.": "कृपया अपनी फ़ोटो और वीडियो तक पहुंच सक्षम करें ताकि आप उन्हें साझा कर सकें।", "Please select a channel first": "कृपया पहले एक चैनल चुनें", "Reconnecting...": "पुनः कनेक्ट हो...", diff --git a/package/src/i18n/it.json b/package/src/i18n/it.json index 9e95f2da41..bc4d2a396a 100644 --- a/package/src/i18n/it.json +++ b/package/src/i18n/it.json @@ -27,6 +27,7 @@ "Flag": "Contrassegna", "Flag Message": "Contrassegna Messaggio", "Flag action failed either due to a network issue or the message is already flagged": "L'azione di segnalazione non è riuscita a causa di un problema di rete o il messaggio è già segnalato.", + "Hold to start recording.": "Tieni premuto per avviare la registrazione.", "How about sending your first message to a friend?": "Che ne dici di inviare il tuo primo messaggio ad un amico?", "Instant Commands": "Comandi Istantanei", "Let's start chatting!": "Iniziamo a chattare!", @@ -48,6 +49,7 @@ "Photos and Videos": "Foto e Video", "Pin to Conversation": "Metti in evidenza", "Pinned by": "Fissato da", + "Please allow Audio permissions in settings.": "Si prega di consentire le autorizzazioni audio nelle impostazioni.", "Please enable access to your photos and videos so you can share them.": "Abilita l'accesso alle tue foto e ai tuoi video in modo da poterli condividere.", "Please select a channel first": "Seleziona un canale", "Reconnecting...": "Ricollegarsi...", diff --git a/package/src/i18n/ja.json b/package/src/i18n/ja.json index 543a893952..2a972462a9 100644 --- a/package/src/i18n/ja.json +++ b/package/src/i18n/ja.json @@ -27,6 +27,7 @@ "Flag": "フラグ", "Flag Message": "メッセージをフラグする", "Flag action failed either due to a network issue or the message is already flagged": "ネットワーク接続に問題があるか、すでにフラグが設定されているため、フラグが失敗しました。", + "Hold to start recording.": "録音を開始するには押し続けてください。", "How about sending your first message to a friend?": "初めてのメッセージを友達に送ってみてはいかがでしょうか?", "Instant Commands": "インスタントコマンド", "Let's start chatting!": "チャットを始めましょう!", @@ -48,6 +49,7 @@ "Photos and Videos": "写真と動画", "Pin to Conversation": "会話にピンする", "Pinned by": "ピン留めされユーザー", + "Please allow Audio permissions in settings.": "設定でオーディオの権限を許可してください。", "Please enable access to your photos and videos so you can share them.": "写真やビデオへのアクセスを有効にして、共有できるようにしてください。", "Please select a channel first": "最初にチャンネルを選択してください", "Reconnecting...": "再接続中。。。", diff --git a/package/src/i18n/ko.json b/package/src/i18n/ko.json index cf3da3d879..e5615b0bc0 100644 --- a/package/src/i18n/ko.json +++ b/package/src/i18n/ko.json @@ -27,6 +27,7 @@ "Flag": "플래그", "Flag Message": "메시지를 플래그하기", "Flag action failed either due to a network issue or the message is already flagged": "네트워크 연결에 문제가 있거나 이미 플래그 되어서 플래그에 실패했습니다.", + "Hold to start recording.": "녹음을 시작하려면 눌러주세요.", "How about sending your first message to a friend?": "친구에게 첫 번째 메시지를 보내는 것은 어떻습니까?", "Instant Commands": "인스턴트 명령", "Let's start chatting!": "채팅을 시작합시다!", @@ -48,6 +49,7 @@ "Photos and Videos": "사진과 동영상", "Pin to Conversation": "대화에 고정합니다", "Pinned by": "고정된 사용자", + "Please allow Audio permissions in settings.": "설정에서 오디오 권한을 허용해주세요.", "Please enable access to your photos and videos so you can share them.": "사진 및 비디오에 대한 액세스를 사용하여 공유 할 수 있도록합니다.", "Please select a channel first": "먼저 채널을 선택하십시오", "Reconnecting...": "다시 연결 중...", diff --git a/package/src/i18n/nl.json b/package/src/i18n/nl.json index 090a16c3f7..44c96cc749 100644 --- a/package/src/i18n/nl.json +++ b/package/src/i18n/nl.json @@ -27,6 +27,7 @@ "Flag": "Markeer", "Flag Message": "Markeer bericht", "Flag action failed either due to a network issue or the message is already flagged": "Rapporteren mislukt door een netwerk fout of het berich is al gerapporteerd", + "Hold to start recording.": "Houd vast om opname te starten.", "How about sending your first message to a friend?": "Wat dacht je ervan om je eerste bericht naar een vriend te sturen?", "Instant Commands": "Directe Opdrachten", "Let's start chatting!": "Laten we beginnen met chatten!", @@ -48,6 +49,7 @@ "Photos and Videos": "Foto's en video's", "Pin to Conversation": "Vastmaken aan gesprek", "Pinned by": "Vastgemaakt door", + "Please allow Audio permissions in settings.": "Gelieve audio toestemmingen toe te staan in de instellingen.", "Please enable access to your photos and videos so you can share them.": "Schakel toegang tot uw foto's en video's in zodat u ze kunt delen.", "Please select a channel first": "Selecteer eerst een kanaal", "Reconnecting...": "Opnieuw Verbinding Maken...", diff --git a/package/src/i18n/pt-BR.json b/package/src/i18n/pt-BR.json index d8842a22f2..e495313b16 100644 --- a/package/src/i18n/pt-BR.json +++ b/package/src/i18n/pt-BR.json @@ -27,6 +27,7 @@ "Flag": "Reportar", "Flag Message": "Reportar Mensagem", "Flag action failed either due to a network issue or the message is already flagged": "A ação para reportar a mensagem falhou devido a um problema de rede ou a mensagem já foi reportada.", + "Hold to start recording.": "Mantenha pressionado para começar a gravar.", "How about sending your first message to a friend?": "Que tal enviar sua primeira mensagem para um amigo?", "Instant Commands": "Comandos Instantâneos", "Let's start chatting!": "Vamos começar a conversar!", @@ -48,6 +49,7 @@ "Photos and Videos": "Fotos e Vídeos", "Pin to Conversation": "Fixar na Conversa", "Pinned by": "Fixado por", + "Please allow Audio permissions in settings.": "Por favor, permita as permissões de áudio nas configurações.", "Please enable access to your photos and videos so you can share them.": "Por favor, habilite o acesso às suas fotos e vídeos para poder compartilhá-los.", "Please select a channel first": "Por favor, selecione um canal primeiro", "Reconnecting...": "Reconectando...", diff --git a/package/src/i18n/ru.json b/package/src/i18n/ru.json index 5348b810d8..a0536a702c 100644 --- a/package/src/i18n/ru.json +++ b/package/src/i18n/ru.json @@ -27,6 +27,7 @@ "Flag": "Пометить", "Flag Message": "Пометить сообщение", "Flag action failed either due to a network issue or the message is already flagged": "Не удалось отправить жалобу. Возможные причины: проблема с подключением к интернету или ваша жалоба уже была принята.", + "Hold to start recording.": "Удерживайте, чтобы начать запись.", "How about sending your first message to a friend?": "Как насчет отправки первого сообщения другу?", "Instant Commands": "Мгновенные Команды", "Let's start chatting!": "Давайте начнем общаться!", @@ -48,6 +49,7 @@ "Photos and Videos": "Фото и видео", "Pin to Conversation": "Закрепить к беседе", "Pinned by": "Закреплено пользователем", + "Please allow Audio permissions in settings.": "Пожалуйста, разрешите разрешения на аудио в настройках.", "Please enable access to your photos and videos so you can share them.": "Разрешите доступ к своим фотографиям и видео, чтобы вы могли ими поделиться.", "Please select a channel first": "Пожалуйста, сначала выберите канал", "Reconnecting...": "Переподключение...", diff --git a/package/src/i18n/tr.json b/package/src/i18n/tr.json index a05baaf009..3acf6d96bd 100644 --- a/package/src/i18n/tr.json +++ b/package/src/i18n/tr.json @@ -27,6 +27,7 @@ "Flag": "Raporla", "Flag Message": "Mesajı Raporla", "Flag action failed either due to a network issue or the message is already flagged": "Mesajın daha önce raporlanmış olması veya bir ağ bağlantısı sorunu nedeniyle raporlama işlemi başarısız oldu.", + "Hold to start recording.": "Kayıt yapmak için basılı tutun.", "How about sending your first message to a friend?": "İlk mesajınızı bir arkadaşınıza göndermeye ne dersiniz?", "Instant Commands": "Anlık Komutlar", "Let's start chatting!": "Haydi sohbete başlayalım!", @@ -48,6 +49,7 @@ "Photos and Videos": "Fotoğraflar ve Videolar", "Pin to Conversation": "Konuşmaya sabitle", "Pinned by": "Tarafından sabitlendi", + "Please allow Audio permissions in settings.": "Lütfen ayarlarda ses izinlerine izin verin", "Please enable access to your photos and videos so you can share them.": "Paylaşım yapabilmek için lutfen fotoğraflarınıza ve videolarınıza erişimi etkinleştirin.", "Please select a channel first": "Lütfen önce bir kanal seçiniz", "Reconnecting...": "Yeniden Bağlanılıyor...", diff --git a/package/src/icons/ArrowLeft.tsx b/package/src/icons/ArrowLeft.tsx new file mode 100644 index 0000000000..beddc8537b --- /dev/null +++ b/package/src/icons/ArrowLeft.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import { Path, Svg } from 'react-native-svg'; + +import { IconProps } from './utils/base'; + +type Props = IconProps & { + size: number; +}; + +export const ArrowLeft = ({ size, ...rest }: Props) => ( + + + +); diff --git a/package/src/icons/ArrowUp.tsx b/package/src/icons/ArrowUp.tsx new file mode 100644 index 0000000000..b8a500ef60 --- /dev/null +++ b/package/src/icons/ArrowUp.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +import { Path, Svg } from 'react-native-svg'; + +import { ColorValue } from 'react-native/types'; + +type Props = { + color: ColorValue; + size: number; +}; + +export const ArrowUp = ({ color, size }: Props) => ( + + + +); diff --git a/package/src/icons/Audio.tsx b/package/src/icons/Audio.tsx index 0498cc6c32..8d0873b623 100644 --- a/package/src/icons/Audio.tsx +++ b/package/src/icons/Audio.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { Defs, LinearGradient, Stop } from 'react-native-svg'; +import { Path } from 'react-native-svg'; -import { IconProps, RootPath, RootSvg } from './utils/base'; +import { IconProps, RootSvg } from './utils/base'; export const Audio = (props: IconProps) => ( ( width={props.width || 34} {...props} > - - - + + - - - - - - ); diff --git a/package/src/icons/CircleStop.tsx b/package/src/icons/CircleStop.tsx new file mode 100644 index 0000000000..c69a47da2b --- /dev/null +++ b/package/src/icons/CircleStop.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import { Path, Svg } from 'react-native-svg'; + +import { IconProps } from './utils/base'; + +type Props = IconProps & { + size: number; +}; + +export const CircleStop = ({ size, ...rest }: Props) => ( + + + +); diff --git a/package/src/icons/Delete.tsx b/package/src/icons/Delete.tsx index 6ad3713657..cd31fe6fd5 100644 --- a/package/src/icons/Delete.tsx +++ b/package/src/icons/Delete.tsx @@ -1,16 +1,22 @@ import React from 'react'; -import { IconProps, RootPath, RootSvg } from './utils/base'; +import Svg, { Path } from 'react-native-svg'; -export const Delete = (props: IconProps) => ( - - ( + + - - + ); diff --git a/package/src/icons/Lock.tsx b/package/src/icons/Lock.tsx new file mode 100644 index 0000000000..d2e531282c --- /dev/null +++ b/package/src/icons/Lock.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import { G, Path, Svg } from 'react-native-svg'; + +import { ColorValue } from 'react-native/types'; + +type Props = { + color: ColorValue; + size: number; +}; + +export const Lock = ({ color, size }: Props) => ( + + + + + +); diff --git a/package/src/icons/Mic.tsx b/package/src/icons/Mic.tsx new file mode 100644 index 0000000000..91489e94d9 --- /dev/null +++ b/package/src/icons/Mic.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import { G, Path, Svg } from 'react-native-svg'; + +import { IconProps } from './utils/base'; + +type Props = IconProps & { + size: number; +}; + +export const Mic = ({ size, ...rest }: Props) => ( + + + + + +); diff --git a/package/src/icons/Pause.tsx b/package/src/icons/Pause.tsx index 4419a7d8d9..69e53750bb 100644 --- a/package/src/icons/Pause.tsx +++ b/package/src/icons/Pause.tsx @@ -1,14 +1,15 @@ import React from 'react'; -import { Rect } from 'react-native-svg'; +import Svg, { Path } from 'react-native-svg'; -import { IconProps, RootSvg } from './utils/base'; +import { IconProps } from './utils/base'; -export const Pause = (props: IconProps) => { - const { height, width } = props; - return ( - - - - - ); -}; +type Props = IconProps; + +export const Pause = ({ height, width, ...rest }: Props) => ( + + + +); diff --git a/package/src/icons/Play.tsx b/package/src/icons/Play.tsx index f90e514f15..cb7117ea89 100644 --- a/package/src/icons/Play.tsx +++ b/package/src/icons/Play.tsx @@ -1,17 +1,13 @@ import React from 'react'; -import { IconProps, RootPath, RootSvg } from './utils/base'; +import Svg, { Path } from 'react-native-svg'; -export const Play = (props: IconProps) => ( - - - +import { IconProps } from './utils/base'; + +type Props = IconProps; + +export const Play = ({ height, width, ...rest }: Props) => ( + + + ); diff --git a/package/src/icons/SendCheck.tsx b/package/src/icons/SendCheck.tsx new file mode 100644 index 0000000000..a9eb83e59b --- /dev/null +++ b/package/src/icons/SendCheck.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import Svg, { Path } from 'react-native-svg'; + +import { IconProps } from './utils/base'; + +type Props = IconProps & { + size: number; +}; + +export const SendCheck = ({ size, ...rest }: Props) => ( + + + +); diff --git a/package/src/icons/SendRight.tsx b/package/src/icons/SendRight.tsx index d0fc1f8c8a..737977f450 100644 --- a/package/src/icons/SendRight.tsx +++ b/package/src/icons/SendRight.tsx @@ -1,12 +1,21 @@ import React from 'react'; -import { IconProps, RootPath, RootSvg } from './utils/base'; +import Svg, { Circle, Path } from 'react-native-svg'; -export const SendRight = (props: IconProps) => ( - - ( + + + - + ); diff --git a/package/src/icons/SendUp.tsx b/package/src/icons/SendUp.tsx index 8af39c83c1..e0ab3e23ed 100644 --- a/package/src/icons/SendUp.tsx +++ b/package/src/icons/SendUp.tsx @@ -1,12 +1,21 @@ import React from 'react'; -import { IconProps, RootPath, RootSvg } from './utils/base'; +import Svg, { Circle, Path } from 'react-native-svg'; -export const SendUp = (props: IconProps) => ( - - ( + + + - + ); diff --git a/package/src/icons/Stop.tsx b/package/src/icons/Stop.tsx new file mode 100644 index 0000000000..3ba9e540e9 --- /dev/null +++ b/package/src/icons/Stop.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +import { IconProps, RootPath, RootSvg } from './utils/base'; + +export const Stop: React.FC = (props) => ( + + + +); diff --git a/package/src/icons/index.ts b/package/src/icons/index.ts index f4c429b392..9edb551332 100644 --- a/package/src/icons/index.ts +++ b/package/src/icons/index.ts @@ -1,6 +1,8 @@ export * from './utils/base'; export * from './ArrowRight'; +export * from './ArrowLeft'; +export * from './ArrowUp'; export * from './AtMentions'; export * from './Attach'; export * from './Audio'; @@ -9,6 +11,7 @@ export * from './Check'; export * from './CheckAll'; export * from './CheckSend'; export * from './CircleClose'; +export * from './CircleStop'; export * from './Close'; export * from './Copy'; export * from './CSV'; @@ -33,6 +36,7 @@ export * from './Imgur'; export * from './Lightning'; export * from './Link'; export * from './Loading'; +export * from './Lock'; export * from './Logo'; export * from './LOLReaction'; export * from './LoveReaction'; @@ -42,6 +46,7 @@ export * from './MenuPointHorizontal'; export * from './MenuPointVertical'; export * from './MessageFlag'; export * from './MessageIcon'; +export * from './Mic'; export * from './Mute'; export * from './Notification'; export * from './ODT'; @@ -57,6 +62,7 @@ export * from './RAR'; export * from './Refresh'; export * from './RTF'; export * from './Search'; +export * from './SendCheck'; export * from './SendRight'; export * from './SendUp'; export * from './SEVEN_Z'; @@ -64,6 +70,7 @@ export * from './Share'; export * from './ShareRightArrow'; export * from './Smile'; export * from './Sound'; +export * from './Stop'; export * from './TAR'; export * from './ThreadReply'; export * from './ThumbsDownReaction'; diff --git a/package/src/native.ts b/package/src/native.ts index cb16805141..44ee874048 100644 --- a/package/src/native.ts +++ b/package/src/native.ts @@ -108,14 +108,18 @@ type TriggerHaptic = (method: HapticFeedbackMethod) => void | never; export let triggerHaptic: TriggerHaptic = fail; export type PlaybackStatus = { + currentPosition: number; didJustFinish: boolean; + duration: number; durationMillis: number; error: string; isBuffering: boolean; isLoaded: boolean; isLooping: boolean; + isMuted: boolean; isPlaying: boolean; positionMillis: number; + shouldPlay: boolean; }; export type AVPlaybackStatusToSet = { @@ -152,13 +156,18 @@ export type SoundReturnType = { onPlaybackStatusUpdate?: (playbackStatus: PlaybackStatus) => void; onProgress?: (data: VideoProgressData) => void; onReadyForDisplay?: () => void; + pause?: () => void; pauseAsync?: () => void; play?: () => void; playAsync?: () => void; + rate?: number; replayAsync?: () => void; resizeMode?: string; + resume?: () => void; seek?: (progress: number) => void; setPositionAsync?: (millis: number) => void; + setProgressUpdateIntervalAsync?: (progressUpdateIntervalMillis: number) => void; + setRateAsync?: (rate: number) => void; soundRef?: React.RefObject; stopAsync?: () => void; style?: StyleProp; @@ -175,6 +184,60 @@ export type SoundType = { Player: React.ComponentType | null; }; +export type RecordingStatus = { + canRecord: boolean; + currentMetering: number; + currentPosition: number; + durationMillis: number; + isDoneRecording: boolean; + isRecording: boolean; + metering: number; + mediaServicesDidReset?: boolean; + uri?: string | null; +}; + +export type AudioRecordingReturnType = + | string + | { + getStatusAsync: () => Promise; + getURI: () => string | null; + pauseAsync: () => Promise; + recording: string; + setProgressUpdateInterval: (progressUpdateIntervalMillis: number) => void; + stopAndUnloadAsync: () => Promise; + } + | undefined; + +export type AudioReturnType = { + accessGranted: boolean; + recording?: AudioRecordingReturnType; +}; + +export type RecordingOptions = { + /** + * A boolean that determines whether audio level information will be part of the status object under the "metering" key. + */ + isMeteringEnabled?: boolean; +}; + +export type AudioType = { + startRecording: ( + options?: RecordingOptions, + onRecordingStatusUpdate?: (recordingStatus: RecordingStatus) => void, + ) => Promise; + stopRecording: () => Promise; + pausePlayer?: () => Promise; + resumePlayer?: () => Promise; + startPlayer?: ( + uri?: AudioRecordingReturnType, + initialStatus?: Partial, + onPlaybackStatusUpdate?: (playbackStatus: PlaybackStatus) => void, + ) => Promise; + stopPlayer?: () => Promise; +}; + +export let Audio: AudioType; + export let Sound: SoundType; export type VideoProgressData = { @@ -221,6 +284,7 @@ export type VideoType = { export let Video: React.ComponentType; type Handlers = { + Audio?: AudioType; compressImage?: CompressImage; deleteFile?: DeleteFile; FlatList?: typeof DefaultFlatList; @@ -241,6 +305,10 @@ type Handlers = { }; export const registerNativeHandlers = (handlers: Handlers) => { + if (handlers.Audio) { + Audio = handlers.Audio; + } + if (handlers.compressImage) { compressImage = handlers.compressImage; } @@ -311,3 +379,4 @@ export const registerNativeHandlers = (handlers: Handlers) => { export const isVideoPackageAvailable = () => !!Video; export const isAudioPackageAvailable = () => !!Sound.Player || !!Sound.initializeSound; +export const isRecordingPackageAvailable = () => !!Audio; diff --git a/package/src/types/types.ts b/package/src/types/types.ts index 1da03c17bb..e0d53f6973 100644 --- a/package/src/types/types.ts +++ b/package/src/types/types.ts @@ -20,8 +20,10 @@ export type File = { id?: string; mimeType?: string; size?: number; + type?: 'file' | 'image' | 'video' | 'audio' | 'voiceRecording'; // 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 FileUpload = { @@ -32,7 +34,9 @@ export type FileUpload = { paused?: boolean; progress?: number; thumb_url?: string; + type?: string; url?: string; + waveform_data?: number[]; }; export type ImageUpload = { @@ -45,10 +49,12 @@ export type ImageUpload = { }; export type DefaultAttachmentType = UnknownType & { + duration?: number; file_size?: number; mime_type?: string; originalFile?: File; originalImage?: Partial; + waveform_data?: number[]; }; interface DefaultUserType extends UnknownType { diff --git a/package/src/utils/getTrimmedAttachmentTitle.ts b/package/src/utils/getTrimmedAttachmentTitle.ts new file mode 100644 index 0000000000..59079851b0 --- /dev/null +++ b/package/src/utils/getTrimmedAttachmentTitle.ts @@ -0,0 +1,5 @@ +export const getTrimmedAttachmentTitle = (title?: string) => { + if (!title) return ''; + const lastIndexOfDot = title.lastIndexOf('.'); + return title.length < 12 ? title : title.slice(0, 12) + '...' + title.slice(lastIndexOfDot); +};