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 0000000000..e413b986d2
Binary files /dev/null and b/docusaurus/docs/reactnative/assets/guides/audio-support/audio-attachment-upload-preview.jpg differ
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 0000000000..241a7f67e3
Binary files /dev/null and b/docusaurus/docs/reactnative/assets/guides/audio-support/audio-attachment.jpg differ
diff --git a/docusaurus/docs/reactnative/assets/guides/audio-support/audio-recorder-lock-button.png b/docusaurus/docs/reactnative/assets/guides/audio-support/audio-recorder-lock-button.png
new file mode 100644
index 0000000000..b25bb6ee9b
Binary files /dev/null and b/docusaurus/docs/reactnative/assets/guides/audio-support/audio-recorder-lock-button.png differ
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 0000000000..74b0528128
Binary files /dev/null and b/docusaurus/docs/reactnative/assets/guides/audio-support/audio-recording-in-progress.png differ
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 0000000000..8cdcbf458a
Binary files /dev/null and b/docusaurus/docs/reactnative/assets/guides/audio-support/audio-recording-preview.jpg differ
diff --git a/docusaurus/docs/reactnative/assets/guides/audio-support/start-recording.png b/docusaurus/docs/reactnative/assets/guides/audio-support/start-recording.png
new file mode 100644
index 0000000000..d0889fde51
Binary files /dev/null and b/docusaurus/docs/reactnative/assets/guides/audio-support/start-recording.png differ
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 ? (
+
+ ) : (
+
+ {`x${currentSpeed}`}
+
+ )}
+
+ )}
);
};
-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);
+};