import React, { useEffect, useRef } from 'react'; import { Animated } from 'react-native'; import { Box, Icon, PressBox, ProgressBar, Text, createStyleSheet, useUIKitTheme, } from '@sendbird/uikit-react-native-foundation'; import { Logger, conditionChaining, millsToMMSS, useSafeAreaPadding } from '@sendbird/uikit-utils'; import { useLocalization } from '../../hooks/useContext'; import useVoiceMessageInput from '../../hooks/useVoiceMessageInput'; import type { FileType } from '../../platform/types'; export type VoiceMessageInputProps = { onClose: () => Promise; onSend: (params: { file: FileType; duration: number }) => void; }; const VoiceMessageInput = ({ onClose, onSend }: VoiceMessageInputProps) => { const { STRINGS } = useLocalization(); const { colors } = useUIKitTheme(); const safeArea = useSafeAreaPadding(['bottom']); const { actions, state } = useVoiceMessageInput({ onSend: (file, duration) => onSend({ file, duration }), onClose, }); const uiColors = colors.ui.voiceMessageInput.default[state.status !== 'idle' ? 'active' : 'inactive']; const onPressCancel = async () => { await actions .cancel() .catch((error) => { Logger.warn('Failed to cancel voice message input', error); }) .finally(() => { onClose(); }); }; const onPressSend = async () => { await actions .send() .catch((error) => { Logger.warn('Failed to send voice message', error); }) .finally(() => { onClose(); }); }; const onPressVoiceMessageAction = async () => { try { switch (state.status) { case 'idle': await actions.startRecording(); break; case 'recording': if (lessThanMinimumDuration) { await actions.cancel(); } else { await actions.stopRecording(); } break; case 'recording_completed': case 'playing_paused': await actions.playPlayer(); break; case 'playing': await actions.pausePlayer(); break; } } catch (error) { Logger.warn('Failed to run voice message action.', state, error); } }; const renderActionIcon = () => { switch (state.status) { case 'idle': return ; case 'recording': return ; case 'recording_completed': case 'playing_paused': return ; case 'playing': return ; } }; const isRecorderState = state.status === 'recording' || state.status === 'recording_completed'; const lessThanMinimumDuration = state.recordingTime.currentTime < state.recordingTime.minDuration; const remainingTime = state.playingTime.duration - state.playingTime.currentTime; return ( {/** Progress bar **/} {millsToMMSS(isRecorderState ? state.recordingTime.currentTime : remainingTime)} } /> {/** Cancel / Send **/} {/** Record / Stop / Play / Pause **/} {renderActionIcon()} ); }; const RecordingLight = (props: { visible: boolean }) => { const { colors } = useUIKitTheme(); const value = useRef(new Animated.Value(0)).current; const animation = useRef( Animated.loop( Animated.sequence([ Animated.timing(value, { toValue: 1, duration: 500, useNativeDriver: true }), Animated.timing(value, { toValue: 0, duration: 500, useNativeDriver: true }), ]), ), ).current; useEffect(() => { if (props.visible) animation.start(); return () => { animation.reset(); }; }, [props.visible]); if (!props.visible) return null; return ( ); }; const CancelButton = (props: { onPress: () => void; label: string }) => { const { colors } = useUIKitTheme(); return ( {props.label} ); }; const SendButton = (props: { onPress: () => void; disabled: boolean }) => { const { colors } = useUIKitTheme(); const uiColors = colors.ui.voiceMessageInput.default[props.disabled ? 'inactive' : 'active']; return ( ); }; const styles = createStyleSheet({ container: { borderTopStartRadius: 8, borderTopEndRadius: 8, }, progressBar: { height: 36, marginBottom: 16, borderRadius: 18, }, }); export default VoiceMessageInput;