@sendbird/uikit-react-native 3.1.2 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -42
- package/lib/commonjs/components/ChannelInput/EditInput.js +2 -11
- package/lib/commonjs/components/ChannelInput/EditInput.js.map +1 -1
- package/lib/commonjs/components/ChannelInput/MessageToReplyPreview.js +145 -0
- package/lib/commonjs/components/ChannelInput/MessageToReplyPreview.js.map +1 -0
- package/lib/commonjs/components/ChannelInput/SendInput.js +149 -323
- package/lib/commonjs/components/ChannelInput/SendInput.js.map +1 -1
- package/lib/commonjs/components/ChannelInput/VoiceMessageInput.js +238 -0
- package/lib/commonjs/components/ChannelInput/VoiceMessageInput.js.map +1 -0
- package/lib/commonjs/components/ChannelInput/index.js +34 -3
- package/lib/commonjs/components/ChannelInput/index.js.map +1 -1
- package/lib/commonjs/components/ChannelMessageList/index.js +148 -116
- package/lib/commonjs/components/ChannelMessageList/index.js.map +1 -1
- package/lib/commonjs/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.js +24 -13
- package/lib/commonjs/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.js.map +1 -1
- package/lib/commonjs/components/GroupChannelMessageRenderer/index.js +134 -6
- package/lib/commonjs/components/GroupChannelMessageRenderer/index.js.map +1 -1
- package/lib/commonjs/components/MessageSearchResultItem.js +1 -0
- package/lib/commonjs/components/MessageSearchResultItem.js.map +1 -1
- package/lib/commonjs/components/OpenChannelMessageRenderer/index.js +1 -0
- package/lib/commonjs/components/OpenChannelMessageRenderer/index.js.map +1 -1
- package/lib/commonjs/components/ReactionBottomSheets/ReactionUserListBottomSheet.js +2 -2
- package/lib/commonjs/components/ReactionBottomSheets/ReactionUserListBottomSheet.js.map +1 -1
- package/lib/commonjs/components/ReactionBottomSheets/index.js.map +1 -1
- package/lib/commonjs/components/StatusComposition.js.map +1 -1
- package/lib/commonjs/constants.js +5 -1
- package/lib/commonjs/constants.js.map +1 -1
- package/lib/commonjs/containers/GroupChannelPreviewContainer.js +1 -0
- package/lib/commonjs/containers/GroupChannelPreviewContainer.js.map +1 -1
- package/lib/commonjs/containers/InternalErrorBoundaryContainer.js.map +1 -1
- package/lib/commonjs/containers/SendbirdUIKitContainer.js +72 -34
- package/lib/commonjs/containers/SendbirdUIKitContainer.js.map +1 -1
- package/lib/commonjs/contexts/PlatformServiceCtx.js +16 -12
- package/lib/commonjs/contexts/PlatformServiceCtx.js.map +1 -1
- package/lib/commonjs/contexts/ReactionCtx.js +3 -2
- package/lib/commonjs/contexts/ReactionCtx.js.map +1 -1
- package/lib/commonjs/contexts/SendbirdChatCtx.js +2 -0
- package/lib/commonjs/contexts/SendbirdChatCtx.js.map +1 -1
- package/lib/commonjs/domain/groupChannel/component/GroupChannelHeader.js +14 -4
- package/lib/commonjs/domain/groupChannel/component/GroupChannelHeader.js.map +1 -1
- package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js +28 -42
- package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
- package/lib/commonjs/domain/groupChannel/module/moduleContext.js +109 -5
- package/lib/commonjs/domain/groupChannel/module/moduleContext.js.map +1 -1
- package/lib/commonjs/domain/groupChannel/types.js.map +1 -1
- package/lib/commonjs/domain/userList/types.js.map +1 -1
- package/lib/commonjs/fragments/createGroupChannelFragment.js +34 -7
- package/lib/commonjs/fragments/createGroupChannelFragment.js.map +1 -1
- package/lib/commonjs/fragments/createMessageSearchFragment.js +1 -1
- package/lib/commonjs/fragments/createMessageSearchFragment.js.map +1 -1
- package/lib/commonjs/hooks/useChannelInputItems.js +211 -0
- package/lib/commonjs/hooks/useChannelInputItems.js.map +1 -0
- package/lib/commonjs/hooks/useConnection.js +1 -1
- package/lib/commonjs/hooks/useConnection.js.map +1 -1
- package/lib/commonjs/hooks/useVoiceMessageInput.js +207 -0
- package/lib/commonjs/hooks/useVoiceMessageInput.js.map +1 -0
- package/lib/commonjs/index.js +36 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/libs/MentionManager.js.map +1 -1
- package/lib/commonjs/libs/SBUUtils.js +4 -0
- package/lib/commonjs/libs/SBUUtils.js.map +1 -1
- package/lib/commonjs/libs/VoiceMessageConfig.js +30 -0
- package/lib/commonjs/libs/VoiceMessageConfig.js.map +1 -0
- package/lib/commonjs/localization/StringSet.type.js.map +1 -1
- package/lib/commonjs/localization/createBaseStringSet.js +24 -9
- package/lib/commonjs/localization/createBaseStringSet.js.map +1 -1
- package/lib/commonjs/platform/createFileService.expo.js +10 -0
- package/lib/commonjs/platform/createFileService.expo.js.map +1 -1
- package/lib/commonjs/platform/createFileService.native.js +19 -0
- package/lib/commonjs/platform/createFileService.native.js.map +1 -1
- package/lib/commonjs/platform/createPlayerService.expo.js +137 -0
- package/lib/commonjs/platform/createPlayerService.expo.js.map +1 -0
- package/lib/commonjs/platform/createPlayerService.native.js +139 -0
- package/lib/commonjs/platform/createPlayerService.native.js.map +1 -0
- package/lib/commonjs/platform/createRecorderService.expo.js +158 -0
- package/lib/commonjs/platform/createRecorderService.expo.js.map +1 -0
- package/lib/commonjs/platform/createRecorderService.native.js +157 -0
- package/lib/commonjs/platform/createRecorderService.native.js.map +1 -0
- package/lib/commonjs/platform/types.js.map +1 -1
- package/lib/commonjs/types.js +7 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/utils/promise.js +138 -0
- package/lib/commonjs/utils/promise.js.map +1 -0
- package/lib/commonjs/version.js +1 -1
- package/lib/commonjs/version.js.map +1 -1
- package/lib/module/components/ChannelInput/EditInput.js +3 -12
- package/lib/module/components/ChannelInput/EditInput.js.map +1 -1
- package/lib/module/components/ChannelInput/MessageToReplyPreview.js +137 -0
- package/lib/module/components/ChannelInput/MessageToReplyPreview.js.map +1 -0
- package/lib/module/components/ChannelInput/SendInput.js +152 -326
- package/lib/module/components/ChannelInput/SendInput.js.map +1 -1
- package/lib/module/components/ChannelInput/VoiceMessageInput.js +228 -0
- package/lib/module/components/ChannelInput/VoiceMessageInput.js.map +1 -0
- package/lib/module/components/ChannelInput/index.js +36 -5
- package/lib/module/components/ChannelInput/index.js.map +1 -1
- package/lib/module/components/ChannelMessageList/index.js +149 -117
- package/lib/module/components/ChannelMessageList/index.js.map +1 -1
- package/lib/module/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.js +24 -13
- package/lib/module/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.js.map +1 -1
- package/lib/module/components/GroupChannelMessageRenderer/index.js +132 -7
- package/lib/module/components/GroupChannelMessageRenderer/index.js.map +1 -1
- package/lib/module/components/MessageSearchResultItem.js +2 -1
- package/lib/module/components/MessageSearchResultItem.js.map +1 -1
- package/lib/module/components/OpenChannelMessageRenderer/index.js +1 -0
- package/lib/module/components/OpenChannelMessageRenderer/index.js.map +1 -1
- package/lib/module/components/ReactionBottomSheets/ReactionUserListBottomSheet.js +2 -2
- package/lib/module/components/ReactionBottomSheets/ReactionUserListBottomSheet.js.map +1 -1
- package/lib/module/components/ReactionBottomSheets/index.js.map +1 -1
- package/lib/module/components/StatusComposition.js.map +1 -1
- package/lib/module/constants.js +2 -0
- package/lib/module/constants.js.map +1 -1
- package/lib/module/containers/GroupChannelPreviewContainer.js +2 -1
- package/lib/module/containers/GroupChannelPreviewContainer.js.map +1 -1
- package/lib/module/containers/InternalErrorBoundaryContainer.js.map +1 -1
- package/lib/module/containers/SendbirdUIKitContainer.js +74 -36
- package/lib/module/containers/SendbirdUIKitContainer.js.map +1 -1
- package/lib/module/contexts/PlatformServiceCtx.js +14 -11
- package/lib/module/contexts/PlatformServiceCtx.js.map +1 -1
- package/lib/module/contexts/ReactionCtx.js +3 -2
- package/lib/module/contexts/ReactionCtx.js.map +1 -1
- package/lib/module/contexts/SendbirdChatCtx.js +2 -0
- package/lib/module/contexts/SendbirdChatCtx.js.map +1 -1
- package/lib/module/domain/groupChannel/component/GroupChannelHeader.js +15 -5
- package/lib/module/domain/groupChannel/component/GroupChannelHeader.js.map +1 -1
- package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js +29 -43
- package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
- package/lib/module/domain/groupChannel/module/moduleContext.js +111 -7
- package/lib/module/domain/groupChannel/module/moduleContext.js.map +1 -1
- package/lib/module/domain/groupChannel/types.js.map +1 -1
- package/lib/module/domain/userList/types.js.map +1 -1
- package/lib/module/fragments/createGroupChannelFragment.js +36 -9
- package/lib/module/fragments/createGroupChannelFragment.js.map +1 -1
- package/lib/module/fragments/createMessageSearchFragment.js +1 -1
- package/lib/module/fragments/createMessageSearchFragment.js.map +1 -1
- package/lib/module/hooks/useChannelInputItems.js +203 -0
- package/lib/module/hooks/useChannelInputItems.js.map +1 -0
- package/lib/module/hooks/useConnection.js +1 -1
- package/lib/module/hooks/useConnection.js.map +1 -1
- package/lib/module/hooks/useVoiceMessageInput.js +199 -0
- package/lib/module/hooks/useVoiceMessageInput.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/libs/MentionManager.js.map +1 -1
- package/lib/module/libs/SBUUtils.js +4 -0
- package/lib/module/libs/SBUUtils.js.map +1 -1
- package/lib/module/libs/VoiceMessageConfig.js +23 -0
- package/lib/module/libs/VoiceMessageConfig.js.map +1 -0
- package/lib/module/localization/StringSet.type.js.map +1 -1
- package/lib/module/localization/createBaseStringSet.js +25 -10
- package/lib/module/localization/createBaseStringSet.js.map +1 -1
- package/lib/module/platform/createFileService.expo.js +10 -0
- package/lib/module/platform/createFileService.expo.js.map +1 -1
- package/lib/module/platform/createFileService.native.js +19 -0
- package/lib/module/platform/createFileService.native.js.map +1 -1
- package/lib/module/platform/createPlayerService.expo.js +129 -0
- package/lib/module/platform/createPlayerService.expo.js.map +1 -0
- package/lib/module/platform/createPlayerService.native.js +132 -0
- package/lib/module/platform/createPlayerService.native.js.map +1 -0
- package/lib/module/platform/createRecorderService.expo.js +150 -0
- package/lib/module/platform/createRecorderService.expo.js.map +1 -0
- package/lib/module/platform/createRecorderService.native.js +149 -0
- package/lib/module/platform/createRecorderService.native.js.map +1 -0
- package/lib/module/platform/types.js.map +1 -1
- package/lib/module/types.js +5 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/promise.js +132 -0
- package/lib/module/utils/promise.js.map +1 -0
- package/lib/module/version.js +1 -1
- package/lib/module/version.js.map +1 -1
- package/lib/typescript/src/components/ChannelCover.d.ts +2 -1
- package/lib/typescript/src/components/ChannelInput/AttachmentsButton.d.ts +2 -1
- package/lib/typescript/src/components/ChannelInput/MessageToReplyPreview.d.ts +7 -0
- package/lib/typescript/src/components/ChannelInput/VoiceMessageInput.d.ts +11 -0
- package/lib/typescript/src/components/ChannelInput/index.d.ts +9 -3
- package/lib/typescript/src/components/ChannelMessageList/index.d.ts +4 -1
- package/lib/typescript/src/components/FileViewer.d.ts +2 -1
- package/lib/typescript/src/components/GroupChannelMessageRenderer/GroupChannelMessageDateSeparator.d.ts +2 -1
- package/lib/typescript/src/components/GroupChannelMessageRenderer/GroupChannelMessageFocusAnimation.d.ts +1 -1
- package/lib/typescript/src/components/GroupChannelMessageRenderer/GroupChannelMessageOutgoingStatus.d.ts +1 -1
- package/lib/typescript/src/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.d.ts +4 -2
- package/lib/typescript/src/components/GroupChannelMessageRenderer/index.d.ts +3 -0
- package/lib/typescript/src/components/NewMessagesButton.d.ts +1 -1
- package/lib/typescript/src/components/OpenChannelMessageRenderer/OpenChannelMessageDateSeparator.d.ts +2 -1
- package/lib/typescript/src/components/OpenChannelMessageRenderer/index.d.ts +2 -0
- package/lib/typescript/src/components/ProviderLayout.d.ts +1 -1
- package/lib/typescript/src/components/ReactionAddons/BottomSheetReactionAddon.d.ts +2 -1
- package/lib/typescript/src/components/ReactionAddons/MessageReactionAddon.d.ts +2 -1
- package/lib/typescript/src/components/ReactionAddons/ReactionRoundedButton.d.ts +3 -2
- package/lib/typescript/src/components/ReactionAddons/index.d.ts +3 -2
- package/lib/typescript/src/components/ReactionBottomSheets/ReactionListBottomSheet.d.ts +2 -1
- package/lib/typescript/src/components/ReactionBottomSheets/ReactionUserListBottomSheet.d.ts +2 -1
- package/lib/typescript/src/components/ReactionBottomSheets/index.d.ts +4 -4
- package/lib/typescript/src/components/ScrollToBottomButton.d.ts +1 -1
- package/lib/typescript/src/components/StatusComposition.d.ts +4 -4
- package/lib/typescript/src/components/TypedPlaceholder.d.ts +2 -1
- package/lib/typescript/src/components/UserActionBar.d.ts +2 -1
- package/lib/typescript/src/components/UserSelectableBar.d.ts +2 -1
- package/lib/typescript/src/constants.d.ts +2 -0
- package/lib/typescript/src/containers/GroupChannelPreviewContainer.d.ts +2 -1
- package/lib/typescript/src/containers/InternalErrorBoundaryContainer.d.ts +3 -3
- package/lib/typescript/src/containers/SendbirdUIKitContainer.d.ts +19 -8
- package/lib/typescript/src/contexts/LocalizationCtx.d.ts +1 -1
- package/lib/typescript/src/contexts/PlatformServiceCtx.d.ts +8 -8
- package/lib/typescript/src/contexts/ReactionCtx.d.ts +5 -2
- package/lib/typescript/src/contexts/SendbirdChatCtx.d.ts +6 -3
- package/lib/typescript/src/contexts/UserProfileCtx.d.ts +1 -1
- package/lib/typescript/src/domain/groupChannel/component/GroupChannelHeader.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannel/component/GroupChannelInput.d.ts +1 -1
- package/lib/typescript/src/domain/groupChannel/component/GroupChannelMessageList.d.ts +1 -1
- package/lib/typescript/src/domain/groupChannel/component/GroupChannelStatusEmpty.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannel/component/GroupChannelStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannel/component/GroupChannelSuggestedMentionList.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannel/types.d.ts +48 -0
- package/lib/typescript/src/domain/groupChannelBannedUsers/component/GroupChannelBannedUsersHeader.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelBannedUsers/component/GroupChannelBannedUsersList.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelBannedUsers/component/GroupChannelBannedUsersStatusEmpty.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelBannedUsers/component/GroupChannelBannedUsersStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelList/component/GroupChannelListHeader.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelList/component/GroupChannelListList.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelList/component/GroupChannelListStatusEmpty.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelList/component/GroupChannelListStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelList/component/GroupChannelListTypeSelector.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelModeration/component/GroupChannelModerationHeader.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelModeration/component/GroupChannelModerationMenu.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelMutedMembers/component/GroupChannelMutedMembersHeader.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelMutedMembers/component/GroupChannelMutedMembersList.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelMutedMembers/component/GroupChannelMutedMembersStatusEmpty.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelMutedMembers/component/GroupChannelMutedMembersStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelNotifications/component/GroupChannelNotificationsHeader.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelNotifications/component/GroupChannelNotificationsView.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelOperators/component/GroupChannelOperatorsHeader.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelOperators/component/GroupChannelOperatorsList.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelOperators/component/GroupChannelOperatorsStatusEmpty.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelOperators/component/GroupChannelOperatorsStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelSettings/component/GroupChannelSettingsHeader.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelSettings/component/GroupChannelSettingsInfo.d.ts +2 -1
- package/lib/typescript/src/domain/groupChannelSettings/component/GroupChannelSettingsMenu.d.ts +2 -1
- package/lib/typescript/src/domain/messageSearch/component/MessageSearchHeader.d.ts +2 -1
- package/lib/typescript/src/domain/messageSearch/component/MessageSearchList.d.ts +2 -1
- package/lib/typescript/src/domain/messageSearch/component/MessageSearchStatusEmpty.d.ts +2 -1
- package/lib/typescript/src/domain/messageSearch/component/MessageSearchStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/openChannel/component/OpenChannelHeader.d.ts +2 -2
- package/lib/typescript/src/domain/openChannel/component/OpenChannelInput.d.ts +1 -1
- package/lib/typescript/src/domain/openChannel/component/OpenChannelMessageList.d.ts +1 -1
- package/lib/typescript/src/domain/openChannel/component/OpenChannelStatusEmpty.d.ts +2 -1
- package/lib/typescript/src/domain/openChannel/component/OpenChannelStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelBannedUsers/component/OpenChannelBannedUsersHeader.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelBannedUsers/component/OpenChannelBannedUsersList.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelBannedUsers/component/OpenChannelBannedUsersStatusEmpty.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelBannedUsers/component/OpenChannelBannedUsersStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelCreate/component/OpenChannelCreateHeader.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelCreate/component/OpenChannelCreateProfileInput.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelCreate/component/OpenChannelCreateStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelList/component/OpenChannelListHeader.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelList/component/OpenChannelListList.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelList/component/OpenChannelListStatusEmpty.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelList/component/OpenChannelListStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelModeration/component/OpenChannelModerationHeader.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelModeration/component/OpenChannelModerationMenu.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelMutedParticipants/component/OpenChannelMutedParticipantsHeader.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelMutedParticipants/component/OpenChannelMutedParticipantsList.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelMutedParticipants/component/OpenChannelMutedParticipantsStatusEmpty.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelMutedParticipants/component/OpenChannelMutedParticipantsStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelOperators/component/OpenChannelOperatorsHeader.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelOperators/component/OpenChannelOperatorsList.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelOperators/component/OpenChannelOperatorsStatusEmpty.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelOperators/component/OpenChannelOperatorsStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelSettings/component/OpenChannelSettingsHeader.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelSettings/component/OpenChannelSettingsInfo.d.ts +2 -1
- package/lib/typescript/src/domain/openChannelSettings/component/OpenChannelSettingsMenu.d.ts +2 -1
- package/lib/typescript/src/domain/userList/component/UserListHeader.d.ts +3 -3
- package/lib/typescript/src/domain/userList/component/UserListList.d.ts +1 -1
- package/lib/typescript/src/domain/userList/component/UserListStatusEmpty.d.ts +2 -1
- package/lib/typescript/src/domain/userList/component/UserListStatusLoading.d.ts +2 -1
- package/lib/typescript/src/domain/userList/types.d.ts +2 -2
- package/lib/typescript/src/hooks/useChannelInputItems.d.ts +10 -0
- package/lib/typescript/src/hooks/useVoiceMessageInput.d.ts +53 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/libs/MentionManager.d.ts +2 -1
- package/lib/typescript/src/libs/SBUUtils.d.ts +1 -0
- package/lib/typescript/src/libs/VoiceMessageConfig.d.ts +25 -0
- package/lib/typescript/src/localization/StringSet.type.d.ts +7 -0
- package/lib/typescript/src/platform/createPlayerService.expo.d.ts +7 -0
- package/lib/typescript/src/platform/createPlayerService.native.d.ts +9 -0
- package/lib/typescript/src/platform/createRecorderService.expo.d.ts +7 -0
- package/lib/typescript/src/platform/createRecorderService.native.d.ts +9 -0
- package/lib/typescript/src/platform/types.d.ts +100 -1
- package/lib/typescript/src/types.d.ts +5 -1
- package/lib/typescript/src/utils/promise.d.ts +7 -0
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +10 -12
- package/src/components/ChannelInput/EditInput.tsx +3 -15
- package/src/components/ChannelInput/MessageToReplyPreview.tsx +133 -0
- package/src/components/ChannelInput/SendInput.tsx +131 -329
- package/src/components/ChannelInput/VoiceMessageInput.tsx +206 -0
- package/src/components/ChannelInput/index.tsx +37 -6
- package/src/components/ChannelMessageList/index.tsx +145 -113
- package/src/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.tsx +24 -11
- package/src/components/GroupChannelMessageRenderer/index.tsx +113 -4
- package/src/components/MessageSearchResultItem.tsx +2 -1
- package/src/components/OpenChannelMessageRenderer/index.tsx +1 -0
- package/src/components/ReactionBottomSheets/ReactionUserListBottomSheet.tsx +2 -2
- package/src/components/ReactionBottomSheets/index.tsx +3 -2
- package/src/components/StatusComposition.tsx +3 -3
- package/src/constants.ts +2 -0
- package/src/containers/GroupChannelPreviewContainer.tsx +2 -0
- package/src/containers/InternalErrorBoundaryContainer.tsx +1 -1
- package/src/containers/SendbirdUIKitContainer.tsx +103 -59
- package/src/contexts/PlatformServiceCtx.tsx +22 -20
- package/src/contexts/ReactionCtx.tsx +7 -5
- package/src/contexts/SendbirdChatCtx.tsx +10 -2
- package/src/domain/groupChannel/component/GroupChannelHeader.tsx +14 -3
- package/src/domain/groupChannel/component/GroupChannelMessageList.tsx +30 -43
- package/src/domain/groupChannel/module/moduleContext.tsx +119 -7
- package/src/domain/groupChannel/types.ts +45 -0
- package/src/domain/userList/types.ts +2 -2
- package/src/fragments/createGroupChannelFragment.tsx +43 -8
- package/src/fragments/createMessageSearchFragment.tsx +1 -1
- package/src/hooks/useChannelInputItems.ts +215 -0
- package/src/hooks/useConnection.ts +1 -1
- package/src/hooks/useVoiceMessageInput.ts +237 -0
- package/src/index.ts +9 -1
- package/src/libs/MentionManager.tsx +1 -1
- package/src/libs/SBUUtils.ts +5 -0
- package/src/libs/VoiceMessageConfig.ts +28 -0
- package/src/localization/StringSet.type.ts +8 -0
- package/src/localization/createBaseStringSet.ts +27 -11
- package/src/platform/createFileService.expo.ts +10 -0
- package/src/platform/createFileService.native.ts +19 -0
- package/src/platform/createPlayerService.expo.tsx +142 -0
- package/src/platform/createPlayerService.native.tsx +148 -0
- package/src/platform/createRecorderService.expo.tsx +160 -0
- package/src/platform/createRecorderService.native.tsx +170 -0
- package/src/platform/types.ts +114 -1
- package/src/types.ts +6 -1
- package/src/utils/promise.ts +139 -0
- package/src/version.ts +1 -1
|
@@ -240,6 +240,7 @@ export interface StringSet {
|
|
|
240
240
|
PERMISSION_APP_NAME: string;
|
|
241
241
|
PERMISSION_CAMERA: string;
|
|
242
242
|
PERMISSION_DEVICE_STORAGE: string;
|
|
243
|
+
PERMISSION_MICROPHONE: string;
|
|
243
244
|
|
|
244
245
|
USER_NO_NAME: string;
|
|
245
246
|
CHANNEL_NO_MEMBERS: string;
|
|
@@ -249,6 +250,7 @@ export interface StringSet {
|
|
|
249
250
|
parentMessage: SendbirdUserMessage | SendbirdFileMessage,
|
|
250
251
|
currentUserId?: string,
|
|
251
252
|
) => string;
|
|
253
|
+
MESSAGE_UNAVAILABLE: string;
|
|
252
254
|
|
|
253
255
|
USER_BAR_ME_POSTFIX: string;
|
|
254
256
|
USER_BAR_OPERATOR: string;
|
|
@@ -291,6 +293,10 @@ export interface StringSet {
|
|
|
291
293
|
/** Channel > Message > Failed **/
|
|
292
294
|
CHANNEL_MESSAGE_FAILED_RETRY: string;
|
|
293
295
|
CHANNEL_MESSAGE_FAILED_REMOVE: string;
|
|
296
|
+
|
|
297
|
+
/** Voice message **/
|
|
298
|
+
VOICE_MESSAGE: string;
|
|
299
|
+
VOICE_MESSAGE_INPUT_CANCEL: string;
|
|
294
300
|
};
|
|
295
301
|
FILE_VIEWER: {
|
|
296
302
|
TITLE: (message: SendbirdFileMessage) => string;
|
|
@@ -331,6 +337,8 @@ export interface StringSet {
|
|
|
331
337
|
RESEND_MSG_ERROR: string;
|
|
332
338
|
DELETE_MSG_ERROR: string;
|
|
333
339
|
SEND_MSG_ERROR: string;
|
|
340
|
+
USER_MUTED_ERROR: string;
|
|
341
|
+
CHANNEL_FROZEN_ERROR: string;
|
|
334
342
|
UPDATE_MSG_ERROR: string;
|
|
335
343
|
TURN_ON_NOTIFICATIONS_ERROR: string;
|
|
336
344
|
TURN_OFF_NOTIFICATIONS_ERROR: string;
|
|
@@ -3,16 +3,16 @@ import type { Locale } from 'date-fns';
|
|
|
3
3
|
import type { PartialDeep } from '@sendbird/uikit-utils';
|
|
4
4
|
import {
|
|
5
5
|
getDateSeparatorFormat,
|
|
6
|
-
getFileTypeFromMessage,
|
|
7
|
-
getGroupChannelLastMessage,
|
|
8
6
|
getGroupChannelPreviewTime,
|
|
9
7
|
getGroupChannelTitle,
|
|
10
8
|
getMessagePreviewBody,
|
|
11
9
|
getMessagePreviewTime,
|
|
12
10
|
getMessagePreviewTitle,
|
|
13
11
|
getMessageTimeFormat,
|
|
12
|
+
getMessageType,
|
|
14
13
|
getOpenChannelParticipants,
|
|
15
14
|
getOpenChannelTitle,
|
|
15
|
+
isVoiceMessage,
|
|
16
16
|
} from '@sendbird/uikit-utils';
|
|
17
17
|
|
|
18
18
|
import { UNKNOWN_USER_ID } from '../constants';
|
|
@@ -33,6 +33,7 @@ type StringSetCreateOptions = {
|
|
|
33
33
|
export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOptions): StringSet => {
|
|
34
34
|
const USER_NO_NAME = overrides?.LABELS?.USER_NO_NAME ?? '(No name)';
|
|
35
35
|
const CHANNEL_NO_MEMBERS = overrides?.LABELS?.CHANNEL_NO_MEMBERS ?? '(No members)';
|
|
36
|
+
|
|
36
37
|
return {
|
|
37
38
|
OPEN_CHANNEL: {
|
|
38
39
|
HEADER_TITLE: (channel) => getOpenChannelTitle(channel),
|
|
@@ -191,7 +192,11 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
|
|
|
191
192
|
CHANNEL_PREVIEW_TITLE: (currentUserId, channel) =>
|
|
192
193
|
getGroupChannelTitle(currentUserId, channel, USER_NO_NAME, CHANNEL_NO_MEMBERS),
|
|
193
194
|
CHANNEL_PREVIEW_TITLE_CAPTION: (channel, locale) => getGroupChannelPreviewTime(channel, locale ?? dateLocale),
|
|
194
|
-
CHANNEL_PREVIEW_BODY: (channel) =>
|
|
195
|
+
CHANNEL_PREVIEW_BODY: (channel) => {
|
|
196
|
+
if (!channel.lastMessage) return '';
|
|
197
|
+
if (isVoiceMessage(channel.lastMessage)) return 'Voice message';
|
|
198
|
+
return getMessagePreviewBody(channel.lastMessage);
|
|
199
|
+
},
|
|
195
200
|
TYPE_SELECTOR_HEADER_TITLE: 'Channel type',
|
|
196
201
|
TYPE_SELECTOR_GROUP: 'Group',
|
|
197
202
|
TYPE_SELECTOR_SUPER_GROUP: 'Super group',
|
|
@@ -232,7 +237,10 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
|
|
|
232
237
|
HEADER_INPUT_PLACEHOLDER: 'Search',
|
|
233
238
|
HEADER_RIGHT: 'Search',
|
|
234
239
|
SEARCH_RESULT_ITEM_TITLE: (message) => getMessagePreviewTitle(message),
|
|
235
|
-
SEARCH_RESULT_ITEM_BODY: (message) =>
|
|
240
|
+
SEARCH_RESULT_ITEM_BODY: (message) => {
|
|
241
|
+
if (isVoiceMessage(message)) return 'Voice message';
|
|
242
|
+
return getMessagePreviewBody(message);
|
|
243
|
+
},
|
|
236
244
|
SEARCH_RESULT_ITEM_TITLE_CAPTION: (message, locale) => {
|
|
237
245
|
return getMessagePreviewTime(message.createdAt, locale ?? dateLocale);
|
|
238
246
|
},
|
|
@@ -241,6 +249,7 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
|
|
|
241
249
|
PERMISSION_APP_NAME: 'Application',
|
|
242
250
|
PERMISSION_CAMERA: 'camera',
|
|
243
251
|
PERMISSION_DEVICE_STORAGE: 'device storage',
|
|
252
|
+
PERMISSION_MICROPHONE: 'microphone',
|
|
244
253
|
USER_NO_NAME,
|
|
245
254
|
CHANNEL_NO_MEMBERS,
|
|
246
255
|
TYPING_INDICATOR_TYPINGS: (users, NO_NAME = USER_NO_NAME) => {
|
|
@@ -255,6 +264,7 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
|
|
|
255
264
|
const receiverNickname = parent.sender.nickname || USER_NO_NAME;
|
|
256
265
|
return `${reply.sender.userId !== currentUserId ? senderNickname : 'You'} replied to ${receiverNickname}`;
|
|
257
266
|
},
|
|
267
|
+
MESSAGE_UNAVAILABLE: 'Message unavailable',
|
|
258
268
|
|
|
259
269
|
USER_BAR_ME_POSTFIX: ' (You)',
|
|
260
270
|
USER_BAR_OPERATOR: 'Operator',
|
|
@@ -288,22 +298,26 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
|
|
|
288
298
|
CHANNEL_INPUT_REPLY_PREVIEW_TITLE: (user) => `Reply to ${user.nickname || USER_NO_NAME}`,
|
|
289
299
|
CHANNEL_INPUT_REPLY_PREVIEW_BODY: (message) => {
|
|
290
300
|
if (message.isFileMessage()) {
|
|
291
|
-
const
|
|
292
|
-
switch (
|
|
293
|
-
case 'image':
|
|
301
|
+
const messageType = getMessageType(message);
|
|
302
|
+
switch (messageType) {
|
|
303
|
+
case 'file.image':
|
|
294
304
|
return message.type.toLowerCase().includes('gif') ? 'GIF' : 'Photo';
|
|
295
|
-
case 'video':
|
|
305
|
+
case 'file.video':
|
|
296
306
|
return 'Video';
|
|
297
|
-
case 'audio':
|
|
307
|
+
case 'file.audio':
|
|
298
308
|
return 'Audio';
|
|
309
|
+
case 'file.voice':
|
|
310
|
+
return 'Voice message';
|
|
299
311
|
default:
|
|
300
312
|
return message.name;
|
|
301
313
|
}
|
|
302
314
|
} else if (message.isUserMessage()) {
|
|
303
315
|
return message.message;
|
|
304
316
|
}
|
|
305
|
-
return 'Unknown message
|
|
317
|
+
return 'Unknown message';
|
|
306
318
|
},
|
|
319
|
+
VOICE_MESSAGE: 'Voice message',
|
|
320
|
+
VOICE_MESSAGE_INPUT_CANCEL: 'Cancel',
|
|
307
321
|
...overrides?.LABELS,
|
|
308
322
|
},
|
|
309
323
|
FILE_VIEWER: {
|
|
@@ -329,7 +343,7 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
|
|
|
329
343
|
ALERT_DEFAULT_OK: 'OK',
|
|
330
344
|
ALERT_PERMISSIONS_TITLE: 'Allow access?',
|
|
331
345
|
ALERT_PERMISSIONS_MESSAGE: (permission, appName = 'Application') => {
|
|
332
|
-
return `${appName}
|
|
346
|
+
return `${appName} needs permission to access your ${permission}.`;
|
|
333
347
|
},
|
|
334
348
|
ALERT_PERMISSIONS_OK: 'Go to settings',
|
|
335
349
|
PROMPT_DEFAULT_OK: 'Submit',
|
|
@@ -348,6 +362,8 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
|
|
|
348
362
|
DELETE_MSG_ERROR: "Couldn't delete message.",
|
|
349
363
|
RESEND_MSG_ERROR: "Couldn't send message.",
|
|
350
364
|
SEND_MSG_ERROR: "Couldn't send message.",
|
|
365
|
+
USER_MUTED_ERROR: "You're muted by the operator.",
|
|
366
|
+
CHANNEL_FROZEN_ERROR: 'Channel is frozen.',
|
|
351
367
|
UPDATE_MSG_ERROR: "Couldn't edit message.",
|
|
352
368
|
TURN_ON_NOTIFICATIONS_ERROR: "Couldn't turn on notifications.",
|
|
353
369
|
TURN_OFF_NOTIFICATIONS_ERROR: "Couldn't turn off notifications.",
|
|
@@ -142,6 +142,16 @@ const createExpoFileService = ({
|
|
|
142
142
|
}
|
|
143
143
|
return response.uri;
|
|
144
144
|
}
|
|
145
|
+
createRecordFilePath(customExtension = 'm4a'): { recordFilePath: string; uri: string } {
|
|
146
|
+
const basePath = fsModule.cacheDirectory;
|
|
147
|
+
if (!basePath) throw new Error('Cannot determine directory');
|
|
148
|
+
|
|
149
|
+
const filename = `record-${Date.now()}.${customExtension}`;
|
|
150
|
+
return {
|
|
151
|
+
uri: `${basePath}/${filename}`,
|
|
152
|
+
recordFilePath: `${basePath}/${filename}`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
145
155
|
}
|
|
146
156
|
|
|
147
157
|
return new ExpoFileServiceInterface();
|
|
@@ -241,6 +241,25 @@ const createNativeFileService = ({
|
|
|
241
241
|
} as const,
|
|
242
242
|
};
|
|
243
243
|
};
|
|
244
|
+
|
|
245
|
+
createRecordFilePath(customExtension = 'm4a'): { recordFilePath: string; uri: string } {
|
|
246
|
+
const filename = `record-${Date.now()}.${customExtension}`;
|
|
247
|
+
const path = `${fsModule.Dirs.CacheDir}/${filename}`;
|
|
248
|
+
return Platform.select({
|
|
249
|
+
ios: {
|
|
250
|
+
uri: path,
|
|
251
|
+
recordFilePath: filename,
|
|
252
|
+
},
|
|
253
|
+
android: {
|
|
254
|
+
uri: path.startsWith('file://') ? path : 'file://' + path,
|
|
255
|
+
recordFilePath: path,
|
|
256
|
+
},
|
|
257
|
+
default: {
|
|
258
|
+
uri: path,
|
|
259
|
+
recordFilePath: path,
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
}
|
|
244
263
|
}
|
|
245
264
|
|
|
246
265
|
return new NativeFileService();
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type * as ExpoAV from 'expo-av';
|
|
2
|
+
|
|
3
|
+
import { matchesOneOf } from '@sendbird/uikit-utils';
|
|
4
|
+
|
|
5
|
+
import expoPermissionGranted from '../utils/expoPermissionGranted';
|
|
6
|
+
import type { PlayerServiceInterface, Unsubscribe } from './types';
|
|
7
|
+
|
|
8
|
+
type Modules = {
|
|
9
|
+
avModule: typeof ExpoAV;
|
|
10
|
+
};
|
|
11
|
+
type PlaybackListener = Parameters<PlayerServiceInterface['addPlaybackListener']>[number];
|
|
12
|
+
type StateListener = Parameters<PlayerServiceInterface['addStateListener']>[number];
|
|
13
|
+
const createExpoPlayerService = ({ avModule }: Modules): PlayerServiceInterface => {
|
|
14
|
+
const sound = new avModule.Audio.Sound();
|
|
15
|
+
|
|
16
|
+
class VoicePlayer implements PlayerServiceInterface {
|
|
17
|
+
uri?: string;
|
|
18
|
+
state: PlayerServiceInterface['state'] = 'idle';
|
|
19
|
+
|
|
20
|
+
private readonly playbackSubscribers = new Set<PlaybackListener>();
|
|
21
|
+
private readonly stateSubscribers = new Set<StateListener>();
|
|
22
|
+
|
|
23
|
+
private setState = (state: PlayerServiceInterface['state']) => {
|
|
24
|
+
this.state = state;
|
|
25
|
+
this.stateSubscribers.forEach((callback) => {
|
|
26
|
+
callback(state);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
private setListener = () => {
|
|
31
|
+
sound.setProgressUpdateIntervalAsync(100);
|
|
32
|
+
sound.setOnPlaybackStatusUpdate((status) => {
|
|
33
|
+
if (status.isLoaded) {
|
|
34
|
+
if (status.didJustFinish) this.stop();
|
|
35
|
+
if (status.isPlaying) {
|
|
36
|
+
this.playbackSubscribers.forEach((callback) => {
|
|
37
|
+
callback({
|
|
38
|
+
currentTime: status.positionMillis,
|
|
39
|
+
duration: status.durationMillis ?? 0,
|
|
40
|
+
stopped: status.didJustFinish,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
private removeListener = () => {
|
|
49
|
+
sound.setOnPlaybackStatusUpdate(null);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
public requestPermission = async (): Promise<boolean> => {
|
|
53
|
+
const status = await avModule.Audio.getPermissionsAsync();
|
|
54
|
+
if (expoPermissionGranted([status])) {
|
|
55
|
+
return true;
|
|
56
|
+
} else {
|
|
57
|
+
const status = await avModule.Audio.requestPermissionsAsync();
|
|
58
|
+
return expoPermissionGranted([status]);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
public addPlaybackListener = (callback: PlaybackListener): Unsubscribe => {
|
|
63
|
+
this.playbackSubscribers.add(callback);
|
|
64
|
+
return () => {
|
|
65
|
+
this.playbackSubscribers.delete(callback);
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
public addStateListener = (callback: (state: PlayerServiceInterface['state']) => void): Unsubscribe => {
|
|
70
|
+
this.stateSubscribers.add(callback);
|
|
71
|
+
return () => {
|
|
72
|
+
this.stateSubscribers.delete(callback);
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
private prepare = async (uri: string) => {
|
|
77
|
+
this.setState('preparing');
|
|
78
|
+
await sound.loadAsync({ uri }, { shouldPlay: false }, true);
|
|
79
|
+
this.uri = uri;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
public play = async (uri: string): Promise<void> => {
|
|
83
|
+
if (matchesOneOf(this.state, ['idle', 'stopped'])) {
|
|
84
|
+
try {
|
|
85
|
+
await this.prepare(uri);
|
|
86
|
+
this.setListener();
|
|
87
|
+
await sound.playAsync();
|
|
88
|
+
this.setState('playing');
|
|
89
|
+
} catch (e) {
|
|
90
|
+
this.setState('idle');
|
|
91
|
+
this.uri = undefined;
|
|
92
|
+
this.removeListener();
|
|
93
|
+
throw e;
|
|
94
|
+
}
|
|
95
|
+
} else if (matchesOneOf(this.state, ['paused']) && this.uri === uri) {
|
|
96
|
+
try {
|
|
97
|
+
this.setListener();
|
|
98
|
+
await sound.playAsync();
|
|
99
|
+
this.setState('playing');
|
|
100
|
+
} catch (e) {
|
|
101
|
+
this.removeListener();
|
|
102
|
+
throw e;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
public pause = async (): Promise<void> => {
|
|
108
|
+
if (matchesOneOf(this.state, ['playing'])) {
|
|
109
|
+
await sound.pauseAsync();
|
|
110
|
+
this.removeListener();
|
|
111
|
+
this.setState('paused');
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
public stop = async (): Promise<void> => {
|
|
116
|
+
if (matchesOneOf(this.state, ['playing', 'paused'])) {
|
|
117
|
+
await sound.stopAsync();
|
|
118
|
+
await sound.unloadAsync();
|
|
119
|
+
this.removeListener();
|
|
120
|
+
this.setState('stopped');
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
public reset = async (): Promise<void> => {
|
|
125
|
+
await this.stop();
|
|
126
|
+
this.setState('idle');
|
|
127
|
+
this.uri = undefined;
|
|
128
|
+
this.playbackSubscribers.clear();
|
|
129
|
+
this.stateSubscribers.clear();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
public seek = async (time: number): Promise<void> => {
|
|
133
|
+
if (matchesOneOf(this.state, ['playing', 'paused'])) {
|
|
134
|
+
await sound.playFromPositionAsync(time);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return new VoicePlayer();
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export default createExpoPlayerService;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import type * as RNAudioRecorder from 'react-native-audio-recorder-player';
|
|
3
|
+
import * as Permissions from 'react-native-permissions';
|
|
4
|
+
|
|
5
|
+
import { matchesOneOf, sleep } from '@sendbird/uikit-utils';
|
|
6
|
+
|
|
7
|
+
import type { PlayerServiceInterface, Unsubscribe } from './types';
|
|
8
|
+
|
|
9
|
+
type Modules = {
|
|
10
|
+
audioRecorderModule: typeof RNAudioRecorder;
|
|
11
|
+
permissionModule: typeof Permissions;
|
|
12
|
+
};
|
|
13
|
+
type PlaybackListener = Parameters<PlayerServiceInterface['addPlaybackListener']>[number];
|
|
14
|
+
type StateListener = Parameters<PlayerServiceInterface['addStateListener']>[number];
|
|
15
|
+
const createNativePlayerService = ({ audioRecorderModule, permissionModule }: Modules): PlayerServiceInterface => {
|
|
16
|
+
const module = new audioRecorderModule.default();
|
|
17
|
+
|
|
18
|
+
class VoicePlayer implements PlayerServiceInterface {
|
|
19
|
+
uri?: string;
|
|
20
|
+
state: PlayerServiceInterface['state'] = 'idle';
|
|
21
|
+
|
|
22
|
+
private readonly playbackSubscribers = new Set<PlaybackListener>();
|
|
23
|
+
private readonly stateSubscribers = new Set<StateListener>();
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
module.setSubscriptionDuration(0.1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private setState = (state: PlayerServiceInterface['state']) => {
|
|
30
|
+
this.state = state;
|
|
31
|
+
this.stateSubscribers.forEach((callback) => {
|
|
32
|
+
callback(state);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
private setListener = () => {
|
|
37
|
+
module.addPlayBackListener((data) => {
|
|
38
|
+
const stopped = data.currentPosition >= data.duration;
|
|
39
|
+
|
|
40
|
+
if (stopped) this.stop();
|
|
41
|
+
if (this.state === 'playing') {
|
|
42
|
+
this.playbackSubscribers.forEach((callback) => {
|
|
43
|
+
callback({ currentTime: data.currentPosition, duration: data.duration, stopped });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
private removeListener = () => {
|
|
50
|
+
module.removePlayBackListener();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
public requestPermission = async (): Promise<boolean> => {
|
|
54
|
+
if (Platform.OS === 'android') {
|
|
55
|
+
const { READ_MEDIA_AUDIO, READ_EXTERNAL_STORAGE } = permissionModule.PERMISSIONS.ANDROID;
|
|
56
|
+
const permission = Platform.Version > 32 ? READ_MEDIA_AUDIO : READ_EXTERNAL_STORAGE;
|
|
57
|
+
|
|
58
|
+
const status = await permissionModule.check(permission);
|
|
59
|
+
if (status === 'granted') {
|
|
60
|
+
return true;
|
|
61
|
+
} else {
|
|
62
|
+
const status = await permissionModule.request(permission);
|
|
63
|
+
return status === 'granted';
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
public addPlaybackListener = (callback: PlaybackListener): Unsubscribe => {
|
|
71
|
+
this.playbackSubscribers.add(callback);
|
|
72
|
+
return () => {
|
|
73
|
+
this.playbackSubscribers.delete(callback);
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
public addStateListener = (callback: (state: PlayerServiceInterface['state']) => void): Unsubscribe => {
|
|
78
|
+
this.stateSubscribers.add(callback);
|
|
79
|
+
return () => {
|
|
80
|
+
this.stateSubscribers.delete(callback);
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
public play = async (uri: string): Promise<void> => {
|
|
85
|
+
if (matchesOneOf(this.state, ['idle', 'stopped'])) {
|
|
86
|
+
try {
|
|
87
|
+
this.setState('preparing');
|
|
88
|
+
this.uri = uri;
|
|
89
|
+
this.setListener();
|
|
90
|
+
|
|
91
|
+
// FIXME: Workaround, `module.startPlayer()` caused a significant frame-drop and prevented the 'preparing' UI transition.
|
|
92
|
+
await sleep(0);
|
|
93
|
+
await module.startPlayer(uri);
|
|
94
|
+
|
|
95
|
+
this.setState('playing');
|
|
96
|
+
} catch (e) {
|
|
97
|
+
this.setState('idle');
|
|
98
|
+
this.uri = undefined;
|
|
99
|
+
this.removeListener();
|
|
100
|
+
throw e;
|
|
101
|
+
}
|
|
102
|
+
} else if (matchesOneOf(this.state, ['paused']) && this.uri === uri) {
|
|
103
|
+
try {
|
|
104
|
+
this.setListener();
|
|
105
|
+
await module.resumePlayer();
|
|
106
|
+
this.setState('playing');
|
|
107
|
+
} catch (e) {
|
|
108
|
+
this.removeListener();
|
|
109
|
+
throw e;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
public pause = async (): Promise<void> => {
|
|
115
|
+
if (matchesOneOf(this.state, ['playing'])) {
|
|
116
|
+
await module.pausePlayer();
|
|
117
|
+
this.removeListener();
|
|
118
|
+
this.setState('paused');
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
public stop = async (): Promise<void> => {
|
|
123
|
+
if (matchesOneOf(this.state, ['preparing', 'playing', 'paused'])) {
|
|
124
|
+
await module.stopPlayer();
|
|
125
|
+
this.removeListener();
|
|
126
|
+
this.setState('stopped');
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
public reset = async (): Promise<void> => {
|
|
131
|
+
await this.stop();
|
|
132
|
+
this.setState('idle');
|
|
133
|
+
this.uri = undefined;
|
|
134
|
+
this.playbackSubscribers.clear();
|
|
135
|
+
this.stateSubscribers.clear();
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
public seek = async (time: number): Promise<void> => {
|
|
139
|
+
if (matchesOneOf(this.state, ['playing', 'paused'])) {
|
|
140
|
+
await module.seekToPlayer(time);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return new VoicePlayer();
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export default createNativePlayerService;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as ExpoAV from 'expo-av';
|
|
2
|
+
import type { RecordingOptions } from 'expo-av/build/Audio/Recording.types';
|
|
3
|
+
import { Platform } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { matchesOneOf, sleep } from '@sendbird/uikit-utils';
|
|
6
|
+
|
|
7
|
+
import VoiceMessageConfig from '../libs/VoiceMessageConfig';
|
|
8
|
+
import expoPermissionGranted from '../utils/expoPermissionGranted';
|
|
9
|
+
import type { RecorderServiceInterface, Unsubscribe } from './types';
|
|
10
|
+
|
|
11
|
+
type RecordingListener = Parameters<RecorderServiceInterface['addRecordingListener']>[number];
|
|
12
|
+
type StateListener = Parameters<RecorderServiceInterface['addStateListener']>[number];
|
|
13
|
+
type Modules = {
|
|
14
|
+
avModule: typeof ExpoAV;
|
|
15
|
+
};
|
|
16
|
+
const createExpoRecorderService = ({ avModule }: Modules): RecorderServiceInterface => {
|
|
17
|
+
class VoiceRecorder implements RecorderServiceInterface {
|
|
18
|
+
public uri: RecorderServiceInterface['uri'] = undefined;
|
|
19
|
+
public state: RecorderServiceInterface['state'] = 'idle';
|
|
20
|
+
public options: RecorderServiceInterface['options'] = {
|
|
21
|
+
minDuration: VoiceMessageConfig.DEFAULT.RECORDER.MIN_DURATION,
|
|
22
|
+
maxDuration: VoiceMessageConfig.DEFAULT.RECORDER.MAX_DURATION,
|
|
23
|
+
extension: VoiceMessageConfig.DEFAULT.RECORDER.EXTENSION,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// NOTE: In Android, even when startRecorder() is awaited, if stop() is executed immediately afterward, an error occurs
|
|
27
|
+
private _recordStartedAt = 0;
|
|
28
|
+
private _getRecorderStopSafeBuffer = () => {
|
|
29
|
+
const minWaitingTime = 500;
|
|
30
|
+
const elapsedTime = Date.now() - this._recordStartedAt;
|
|
31
|
+
if (elapsedTime > minWaitingTime) return 0;
|
|
32
|
+
else return minWaitingTime - elapsedTime;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
private _recorder = new avModule.Audio.Recording();
|
|
36
|
+
private readonly _recordingSubscribers = new Set<RecordingListener>();
|
|
37
|
+
private readonly _stateSubscribers = new Set<StateListener>();
|
|
38
|
+
private readonly _audioSettings = {
|
|
39
|
+
sampleRate: VoiceMessageConfig.DEFAULT.RECORDER.SAMPLE_RATE,
|
|
40
|
+
bitRate: VoiceMessageConfig.DEFAULT.RECORDER.BIT_RATE,
|
|
41
|
+
numberOfChannels: VoiceMessageConfig.DEFAULT.RECORDER.CHANNELS,
|
|
42
|
+
// encoding: mpeg4_aac
|
|
43
|
+
};
|
|
44
|
+
private readonly _audioOptions: RecordingOptions = {
|
|
45
|
+
android: {
|
|
46
|
+
...this._audioSettings,
|
|
47
|
+
extension: `.${this.options.extension}`,
|
|
48
|
+
audioEncoder: avModule.Audio.AndroidAudioEncoder.AAC,
|
|
49
|
+
outputFormat: avModule.Audio.AndroidOutputFormat.MPEG_4,
|
|
50
|
+
},
|
|
51
|
+
ios: {
|
|
52
|
+
...this._audioSettings,
|
|
53
|
+
extension: `.${this.options.extension}`,
|
|
54
|
+
outputFormat: avModule.Audio.IOSOutputFormat.MPEG4AAC,
|
|
55
|
+
audioQuality: avModule.Audio.IOSAudioQuality.HIGH,
|
|
56
|
+
},
|
|
57
|
+
web: {},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
private prepare = async () => {
|
|
61
|
+
this.setState('preparing');
|
|
62
|
+
if (Platform.OS === 'ios') {
|
|
63
|
+
await avModule.Audio.setAudioModeAsync({ allowsRecordingIOS: true, playsInSilentModeIOS: true });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (this._recorder._isDoneRecording) {
|
|
67
|
+
this._recorder = new avModule.Audio.Recording();
|
|
68
|
+
}
|
|
69
|
+
this._recorder.setProgressUpdateInterval(100);
|
|
70
|
+
this._recorder.setOnRecordingStatusUpdate((status) => {
|
|
71
|
+
const completed = status.durationMillis >= this.options.maxDuration;
|
|
72
|
+
if (completed) this.stop();
|
|
73
|
+
if (status.isRecording) {
|
|
74
|
+
this._recordingSubscribers.forEach((callback) => {
|
|
75
|
+
callback({ currentTime: status.durationMillis, completed: completed });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
await this._recorder.prepareToRecordAsync(this._audioOptions);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
private setState = (state: RecorderServiceInterface['state']) => {
|
|
83
|
+
this.state = state;
|
|
84
|
+
this._stateSubscribers.forEach((callback) => {
|
|
85
|
+
callback(state);
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
public requestPermission = async (): Promise<boolean> => {
|
|
90
|
+
const status = await avModule.Audio.getPermissionsAsync();
|
|
91
|
+
if (expoPermissionGranted([status])) {
|
|
92
|
+
return true;
|
|
93
|
+
} else {
|
|
94
|
+
const status = await avModule.Audio.requestPermissionsAsync();
|
|
95
|
+
return expoPermissionGranted([status]);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
public addRecordingListener = (callback: RecordingListener): Unsubscribe => {
|
|
100
|
+
this._recordingSubscribers.add(callback);
|
|
101
|
+
return () => {
|
|
102
|
+
this._recordingSubscribers.delete(callback);
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
public addStateListener = (callback: StateListener): Unsubscribe => {
|
|
107
|
+
this._stateSubscribers.add(callback);
|
|
108
|
+
return () => {
|
|
109
|
+
this._stateSubscribers.delete(callback);
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
public record = async (): Promise<void> => {
|
|
114
|
+
if (matchesOneOf(this.state, ['idle', 'completed'])) {
|
|
115
|
+
try {
|
|
116
|
+
await this.prepare();
|
|
117
|
+
await this._recorder.startAsync();
|
|
118
|
+
|
|
119
|
+
if (Platform.OS === 'android') {
|
|
120
|
+
this._recordStartedAt = Date.now();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const uri = this._recorder.getURI();
|
|
124
|
+
if (uri) this.uri = uri;
|
|
125
|
+
this.setState('recording');
|
|
126
|
+
} catch (e) {
|
|
127
|
+
this.setState('idle');
|
|
128
|
+
throw e;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
public stop = async (): Promise<void> => {
|
|
134
|
+
if (matchesOneOf(this.state, ['recording'])) {
|
|
135
|
+
if (Platform.OS === 'android') {
|
|
136
|
+
const buffer = this._getRecorderStopSafeBuffer();
|
|
137
|
+
if (buffer > 0) await sleep(buffer);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await this._recorder.stopAndUnloadAsync();
|
|
141
|
+
if (Platform.OS === 'ios') {
|
|
142
|
+
await avModule.Audio.setAudioModeAsync({ allowsRecordingIOS: false, playsInSilentModeIOS: false });
|
|
143
|
+
}
|
|
144
|
+
this.setState('completed');
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
public reset = async (): Promise<void> => {
|
|
149
|
+
await this.stop();
|
|
150
|
+
this.uri = undefined;
|
|
151
|
+
this._recordingSubscribers.clear();
|
|
152
|
+
this._recorder = new avModule.Audio.Recording();
|
|
153
|
+
this.setState('idle');
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return new VoiceRecorder();
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export default createExpoRecorderService;
|