@cometchat/chat-uikit-react-native
Version:
Ready-to-use Chat UI Components for React Native
263 lines • 12.3 kB
JavaScript
import React, { useEffect, useMemo, useRef } from "react";
import { ActivityIndicator, Alert, Linking, NativeModules, PermissionsAndroid, Platform, TouchableOpacity, View, } from "react-native";
import { localize } from "../..";
import { useTheme } from "../../../theme";
import { Icon } from "../../icons/Icon";
import { AnimatingMic } from "./AnimatingMic";
import { Timer } from "./Timer";
import { CometChatAudioPreview } from "./CometChatAudioPreview/CometChatAudioPreview";
import { deepMerge } from "../../helper/helperFunctions";
export const CometChatMediaRecorder = (props) => {
const { onClose, onPause, onPlay, onSend, onStop, onStart, style } = props;
const [time, setTime] = React.useState(0);
const [recordedFile, setRecordedFile] = React.useState("");
const [recordingState, setRecordedState] = React.useState("initial");
const recordingTimeRef = useRef({
recordingStartedAt: 0,
recordingTotalDuration: 0,
});
const theme = useTheme();
const mergedStyle = useMemo(() => {
return deepMerge(theme.mediaRecorderStyle, style ?? {});
}, [theme, style]);
const _audioBubbleStyle = theme.messageListStyles.outgoingMessageBubbleStyles?.audioBubbleStyles;
useEffect(() => {
setRecordedState("loading"); // Set loading state before recording starts
recordingInitiator(); // Automatically start recording
}, []);
useEffect(() => {
return () => {
if (Platform.OS === "android") {
PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO).then((res) => {
if (!res)
return;
deleteFile();
});
}
else {
deleteFile();
}
};
}, []);
function deleteFile() {
NativeModules.FileManager.deleteFile((success) => console.log("Filepath delete", success));
NativeModules.FileManager.releaseMediaResources((result) => { });
setRecordedFile("");
// Stop playing audio here
}
function permissionAlert() {
Alert.alert('', localize("MICROPHONE_PERMISSION"), [
{
style: "cancel",
text: localize("CANCEL"),
},
{
style: "default",
text: localize("SETTINGS"),
onPress: () => {
Linking.openSettings();
},
},
]);
}
const recordingInitiator = async () => {
let microphonePermission = Platform.select({
ios: true,
android: await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO),
});
if (microphonePermission) {
NativeModules.FileManager.startRecording((filepath) => {
recordingTimeRef.current.recordingStartedAt = Date.now();
recordingTimeRef.current.recordingTotalDuration = 0;
setRecordedState("recording");
if (Platform.OS === "ios") {
try {
let resObj = JSON.parse(filepath);
if (resObj?.granted === false) {
permissionAlert();
onClose && onClose();
}
}
catch (error) {
permissionAlert();
onClose && onClose();
}
}
});
}
else {
permissionAlert();
onClose && onClose();
}
};
const _onStop = () => {
NativeModules.FileManager.releaseMediaResources((result) => {
setRecordedState("stopped");
recordingTimeRef.current.recordingTotalDuration +=
Date.now() - recordingTimeRef.current.recordingStartedAt;
if (Platform.OS === "ios") {
recordingTimeRef.current.recordingTotalDuration = JSON.parse(result).duration * 1000;
}
setRecordedFile(JSON.parse(result)?.file);
onStop && onStop(JSON.parse(result)?.file);
});
};
const startRecording = () => {
// TODO: Set time to 0
setRecordedState("loading");
setRecordedFile("");
NativeModules.FileManager.deleteFile((success) => {
NativeModules.FileManager.startRecording((result) => {
setRecordedState("recording");
});
});
onStart && onStart();
};
// const _onPause = () => {
// NativeModules.FileManager.pausePlaying((filepath) => {
// console.log("Filepath onRecorderAudioPaused", filepath);
// onPause && onPause();
// setRecordedPlaying(false);
// // clearTimeout(stopRecordingIntervalId);
// console.log("timeout cleared", stopRecordingIntervalId);
// });
// };
// const _onClose = () => {
// _onPause();
// setRecordedFile("");
// setRecordedPlaying(false);
// NativeModules.FileManager.releaseMediaResources((filepath) => {
// console.log("Filepath onClose", filepath);
// });
// onClose && onClose();
// };
const _onDelete = () => {
// Stop playing
setRecordedFile("");
setRecordedState("initial");
NativeModules.FileManager.releaseMediaResources((filepath) => {
//console.log("Filepath onClose", filepath);
});
onClose && onClose();
};
const _onSend = () => {
NativeModules.FileManager.releaseMediaResources((result) => {
//console.log("Filepath _stopRecorderAudio", result);
});
onSend && onSend(recordedFile);
onClose && onClose();
};
const pressableIconStyle = useMemo(() => {
return {
padding: 8,
backgroundColor: theme.color.background1,
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.23,
shadowRadius: 2.62,
elevation: 4,
};
}, [theme]);
return (<View style={{
paddingHorizontal: theme.spacing.padding.p5,
backgroundColor: theme.color.background1,
flex: 1,
}}>
{recordingState !== "stopped" && (<AnimatingMic isAnimating={recordingState === "recording"} style={mergedStyle?.animationStyle}/>)}
{(recordingState === "recording" || recordingState === "paused") && (<Timer startTime={recordingTimeRef.current && recordingTimeRef.current.recordingStartedAt
? recordingTimeRef.current.recordingStartedAt
: Date.now()} paused={["paused"].includes(recordingState)}/>)}
{recordingState === "stopped" && (<View style={{
backgroundColor: theme.color.primary,
padding: theme.spacing.padding.p2,
borderRadius: theme.spacing.radius.r4,
width: "100%",
marginBottom: theme.spacing.padding.p5,
}}>
<CometChatAudioPreview audioUrl={recordedFile} playViewContainerStyle={{
width: "100%",
height: 32,
gap: theme.spacing.padding.p3,
flexDirection: "row",
}} playIconStyle={_audioBubbleStyle?.playIconStyle} playIconContainerStyle={_audioBubbleStyle?.playIconContainerStyle} waveStyle={_audioBubbleStyle?.waveStyle} waveContainerStyle={{
flexDirection: "row",
alignItems: "center",
height: 30,
overflow: "hidden",
width: "100%",
flex: 1,
marginRight: theme.spacing.margin.m2,
}} playProgressTextStyle={_audioBubbleStyle?.playProgressTextStyle}/>
</View>)}
<View style={{
paddingVertical: 20,
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
gap: 20,
}}>
{["paused", "stopped", "recording"].includes(recordingState) && (<TouchableOpacity onPress={_onDelete}>
<Icon name='delete-fill' height={theme.spacing.spacing.s6} width={theme.spacing.spacing.s6} color={mergedStyle?.deleteIconStyle?.iconStyle?.tintColor ?? theme.color.iconSecondary} icon={mergedStyle?.deleteIconStyle?.icon} containerStyle={mergedStyle?.deleteIconStyle?.containerStyle}/>
</TouchableOpacity>)}
<TouchableOpacity disabled={recordingState === "loading"} onPress={() => {
if (recordingState === "initial") {
setRecordedState("loading");
recordingInitiator();
}
else if (recordingState === "recording") {
NativeModules.FileManager.pauseRecording().then((result) => {
setRecordedState("paused");
recordingTimeRef.current.recordingTotalDuration +=
Date.now() - recordingTimeRef.current.recordingStartedAt;
});
// _onStop();
}
else if (recordingState === "paused") {
NativeModules.FileManager.resumeRecording().then((result) => {
setRecordedState("recording");
});
}
else if (recordingState === "stopped") {
_onSend();
}
}}>
{recordingState === "loading" ? (<ActivityIndicator size='small' color={theme.color.primary}/>) : (<Icon name={recordingState === "initial" || recordingState === "paused"
? "mic-fill"
: recordingState === "stopped"
? "send-fill"
: "pause-fill"} height={theme.spacing.spacing.s8} width={theme.spacing.spacing.s8} color={recordingState === "initial" || recordingState === "paused"
? mergedStyle?.recordIconStyle?.iconStyle?.tintColor
: recordingState === "stopped"
? mergedStyle?.sendIconStyle?.iconStyle?.tintColor ?? theme.color.primary
: mergedStyle?.pauseIconStyle?.iconStyle?.tintColor ?? theme.color.error} icon={recordingState === "initial" || recordingState === "paused"
? mergedStyle?.recordIconStyle?.icon
: recordingState === "stopped"
? mergedStyle?.sendIconStyle?.icon
: mergedStyle?.pauseIconStyle?.icon} containerStyle={recordingState === "initial" || recordingState === "paused"
? mergedStyle?.recordIconStyle?.containerStyle
: recordingState === "stopped"
? mergedStyle?.sendIconStyle?.containerStyle
: mergedStyle?.pauseIconStyle?.containerStyle} imageStyle={recordingState === "initial" || recordingState === "paused"
? mergedStyle?.recordIconStyle?.iconStyle
: recordingState === "stopped"
? mergedStyle?.sendIconStyle?.iconStyle
: mergedStyle?.pauseIconStyle?.iconStyle}/>)}
</TouchableOpacity>
{(recordingState === "recording" || recordingState === "paused") && (<TouchableOpacity onPress={() => {
setRecordedState("loading");
_onStop();
}}>
<Icon name='stop-fill' height={theme.spacing.spacing.s6} width={theme.spacing.spacing.s6} color={mergedStyle?.stopIconStyle?.iconStyle?.tintColor ?? theme.color.iconSecondary} icon={mergedStyle?.stopIconStyle?.icon} containerStyle={mergedStyle?.stopIconStyle?.containerStyle} imageStyle={mergedStyle?.stopIconStyle?.iconStyle}/>
</TouchableOpacity>)}
{recordingState === "stopped" && (<TouchableOpacity onPress={() => {
startRecording();
}}>
<Icon name='mic-fill' height={theme.spacing.spacing.s6} width={theme.spacing.spacing.s6} color={mergedStyle?.recordIconStyle?.iconStyle?.tintColor ?? theme.color.iconSecondary} icon={mergedStyle?.recordIconStyle?.icon} containerStyle={mergedStyle?.recordIconStyle?.containerStyle} imageStyle={mergedStyle?.recordIconStyle?.iconStyle}/>
</TouchableOpacity>)}
</View>
</View>);
};
//# sourceMappingURL=CometChatMediaRecorder.js.map