From 62ef277069a057bb4e5ee6331b2bcb9320bb6c7b Mon Sep 17 00:00:00 2001 From: bang9 Date: Fri, 8 Sep 2023 10:33:51 +0900 Subject: [PATCH] fix: await onClose for voice message input before displaying permission alert --- .../src/components/ChannelInput/SendInput.tsx | 19 ++++++++------ .../ChannelInput/VoiceMessageInput.tsx | 7 ++++-- .../src/hooks/useVoiceMessageInput.ts | 8 +++++- .../uikit-utils/src/hooks/react-native.ts | 25 ++++++++++++++++++- 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/packages/uikit-react-native/src/components/ChannelInput/SendInput.tsx b/packages/uikit-react-native/src/components/ChannelInput/SendInput.tsx index c6b8743ec..0c3e0cb0c 100644 --- a/packages/uikit-react-native/src/components/ChannelInput/SendInput.tsx +++ b/packages/uikit-react-native/src/components/ChannelInput/SendInput.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useState } from 'react'; +import React, { forwardRef } from 'react'; import { NativeSyntheticEvent, Platform, @@ -18,7 +18,7 @@ import { useToast, useUIKitTheme, } from '@sendbird/uikit-react-native-foundation'; -import { Logger, useIIFE } from '@sendbird/uikit-utils'; +import { Logger, useDeferredModalState, useIIFE } from '@sendbird/uikit-utils'; import { VOICE_MESSAGE_META_ARRAY_DURATION_KEY, VOICE_MESSAGE_META_ARRAY_MESSAGE_TYPE_KEY } from '../../constants'; import { useChannelInputItems } from '../../hooks/useChannelInputItems'; @@ -60,7 +60,12 @@ const SendInput = forwardRef(function SendInput( const { openSheet } = useBottomSheet(); const toast = useToast(); - const [voiceMessageInputVisible, setVoiceMessageInputVisible] = useState(false); + const { + onClose, + onDismiss, + visible: voiceMessageInputVisible, + setVisible: setVoiceMessageInputVisible, + } = useDeferredModalState(); const messageReplyParams = useIIFE(() => { const { groupChannel } = sbOptions.uikit; @@ -187,15 +192,13 @@ const SendInput = forwardRef(function SendInput( {voiceMessageEnabled && VoiceMessageInput && ( setVoiceMessageInputVisible(false)} + onClose={onClose} + onDismiss={onDismiss} backgroundStyle={{ justifyContent: 'flex-end' }} visible={voiceMessageInputVisible} type={'slide-no-gesture'} > - setVoiceMessageInputVisible(false)} - onSend={({ file, duration }) => sendVoiceMessage(file, duration)} - /> + sendVoiceMessage(file, duration)} /> )} diff --git a/packages/uikit-react-native/src/components/ChannelInput/VoiceMessageInput.tsx b/packages/uikit-react-native/src/components/ChannelInput/VoiceMessageInput.tsx index 384adfed5..1ee1c8161 100644 --- a/packages/uikit-react-native/src/components/ChannelInput/VoiceMessageInput.tsx +++ b/packages/uikit-react-native/src/components/ChannelInput/VoiceMessageInput.tsx @@ -17,14 +17,17 @@ import useVoiceMessageInput from '../../hooks/useVoiceMessageInput'; import type { FileType } from '../../platform/types'; export type VoiceMessageInputProps = { - onClose: () => void; // stop playing, recording, hide view + onClose: () => Promise; // stop playing, recording, hide view onSend: (params: { file: FileType; duration: number }) => void; }; const VoiceMessageInput = ({ onClose, onSend }: VoiceMessageInputProps) => { const { STRINGS } = useLocalization(); const { colors } = useUIKitTheme(); - const { actions, state } = useVoiceMessageInput((file, duration) => onSend({ file, duration })); + const { actions, state } = useVoiceMessageInput({ + onSend: (file, duration) => onSend({ file, duration }), + onClose, + }); const uiColors = colors.ui.voiceMessageInput.default[state.status !== 'idle' ? 'active' : 'inactive']; diff --git a/packages/uikit-react-native/src/hooks/useVoiceMessageInput.ts b/packages/uikit-react-native/src/hooks/useVoiceMessageInput.ts index 0444892c5..aeb1e8803 100644 --- a/packages/uikit-react-native/src/hooks/useVoiceMessageInput.ts +++ b/packages/uikit-react-native/src/hooks/useVoiceMessageInput.ts @@ -55,7 +55,12 @@ export interface VoiceMessageInputResult { state: State; } -const useVoiceMessageInput = (onSend: (voiceFile: FileType, duration: number) => void): VoiceMessageInputResult => { +type Props = { + onClose: () => Promise; + onSend: (voiceFile: FileType, duration: number) => void; +}; + +const useVoiceMessageInput = ({ onSend, onClose }: Props): VoiceMessageInputResult => { const { alert } = useAlert(); const { STRINGS } = useLocalization(); const { recorderService, playerService, fileService } = usePlatformService(); @@ -109,6 +114,7 @@ const useVoiceMessageInput = (onSend: (voiceFile: FileType, duration: number) => async startRecording() { const granted = await recorderService.requestPermission(); if (!granted) { + await onClose(); alert({ title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE, message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE( diff --git a/packages/uikit-utils/src/hooks/react-native.ts b/packages/uikit-utils/src/hooks/react-native.ts index 5675f6944..e9fa8f7d0 100644 --- a/packages/uikit-utils/src/hooks/react-native.ts +++ b/packages/uikit-utils/src/hooks/react-native.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { AppState, AppStateEvent, AppStateStatus } from 'react-native'; import { EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -44,3 +44,26 @@ export const useAppState = (type: AppStateEvent, listener: AppStateListener) => }; }, []); }; + +/** + * To display a new modal in React-Native, you should ensure that a new modal is opened only after the existing modal has been dismissed to avoid conflicts. + * To achieve this, you can use a deferred onClose that can be awaited until the onDismiss is called. + * */ +export const useDeferredModalState = () => { + const resolveRef = useRef<(value: void) => void>(); + const [visible, setVisible] = useState(false); + + return { + onClose: () => { + return new Promise((resolve) => { + resolveRef.current = resolve; + setVisible(false); + }); + }, + onDismiss: () => { + resolveRef.current?.(); + }, + visible, + setVisible, + }; +};