From 93d8ddd941c18e59d8e6e421bba40e9d5a1f6b4c Mon Sep 17 00:00:00 2001 From: Sakul Date: Thu, 26 Nov 2020 21:48:26 +0545 Subject: [PATCH] feat: record and play functionality Signed-off-by: Sakul --- js/ios/info.yaml | 1 + .../berty-i18n/locale/en/messages.json | 1 + .../chat/file-uploads/AddFileMenu.tsx | 336 +++++++++++++++++- .../custom-icons-svgs/pause-player.svg | 13 + .../custom-icons-svgs/play-player.svg | 11 + js/packages/messenger-app/custom-icons.tsx | 4 + 6 files changed, 348 insertions(+), 18 deletions(-) create mode 100644 js/packages/messenger-app/custom-icons-svgs/pause-player.svg create mode 100644 js/packages/messenger-app/custom-icons-svgs/play-player.svg diff --git a/js/ios/info.yaml b/js/ios/info.yaml index 89c1361056..78c71416f7 100644 --- a/js/ios/info.yaml +++ b/js/ios/info.yaml @@ -30,6 +30,7 @@ targets: NSLocalNetworkUsageDescription: Used for proximity communications NSLocationWhenInUseUsageDescription: "" NSPhotoLibraryUsageDescription: Used to personalize the app and send photos + NSMicrophoneUsageDescription: Used for voice recording Shake: APIClientID: "$(SHAKE_API_ID)" APIClientSecret: "$(SHAKE_API_SECRET)" diff --git a/js/packages/berty-i18n/locale/en/messages.json b/js/packages/berty-i18n/locale/en/messages.json index bc4dcaf32d..fd836e4c64 100644 --- a/js/packages/berty-i18n/locale/en/messages.json +++ b/js/packages/berty-i18n/locale/en/messages.json @@ -404,6 +404,7 @@ }, "files": { "record-sound":"Record a sound message", + "recording":"Recording...", "media":"Access to Media Gallery", "emojis":"Emojis & Bertyzzz !" diff --git a/js/packages/components/chat/file-uploads/AddFileMenu.tsx b/js/packages/components/chat/file-uploads/AddFileMenu.tsx index 0340885022..3c6421d911 100644 --- a/js/packages/components/chat/file-uploads/AddFileMenu.tsx +++ b/js/packages/components/chat/file-uploads/AddFileMenu.tsx @@ -1,9 +1,24 @@ -import React from 'react' -import { TouchableOpacity, View, StyleSheet } from 'react-native' +import React, { useEffect, useState } from 'react' +import { + TouchableOpacity, + TouchableWithoutFeedback, + View, + StyleSheet, + Animated, +} from 'react-native' import { useStyles } from '@berty-tech/styles' import { useTranslation } from 'react-i18next' import { Text, Icon } from '@ui-kitten/components' import DocumentPicker from 'react-native-document-picker' +import { Player, Recorder } from '@react-native-community/audio-toolkit' +import moment from 'moment' + +enum RecorderState { + Default, + Recording, + Recorded, +} + const ListItem: React.FC<{ title: string onPress: () => void @@ -15,7 +30,7 @@ const ListItem: React.FC<{ pack?: string } }> = ({ title, iconProps, onPress }) => { - const [{ padding, margin }] = useStyles() + const [{ padding, text }] = useStyles() return ( - {title} + {title} ) } +let player = new Player('tempVoiceClip.aac') +let recorder: Recorder + export const AddFileMenu: React.FC<{ close: () => void }> = ({ close }) => { - const [{ color, border }] = useStyles() - const { t } = useTranslation() + const [{ color, border, padding, text, margin }, { windowWidth }] = useStyles() + const { t }: { t: any } = useTranslation() + // const [recorderFilePath, setRecorderFilePath] = useState('') + const [recorderState, setRecorderState] = useState(RecorderState.Default) + const [recordStartTime, setRecordStartTime] = useState(moment()) + const [recordStopTime, setRecordStopTime] = useState(moment()) + const [refresh, setRefresh] = useState(0) + const [intervalId, setIntervalId] = useState() + const [animatedWidth] = useState(new Animated.Value(0)) + + const startTimer = () => { + setIntervalId(setInterval(() => setRefresh((timer) => timer + 1), 500)) + } + + useEffect(() => { + if (player?.isPlaying) { + Animated.timing(animatedWidth, { + toValue: + ((player.currentTime === -1 ? player.duration : player.currentTime) / player.duration) * + windowWidth, + duration: 200, + useNativeDriver: false, + }).start() + } else if (!recorder?.isRecording && player?.isStopped) { + Animated.timing(animatedWidth, { + toValue: windowWidth, + duration: 100, + useNativeDriver: false, + }).start(() => { + Animated.timing(animatedWidth, { + toValue: 0, + duration: 100, + useNativeDriver: false, + }).start() + }) + console.log('[l;ayer stopped') + intervalId && clearInterval(intervalId) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [refresh]) + + const recordingDuration = recordStartTime && (recordStopTime || moment()).diff(recordStartTime) const LIST_CONFIG = [ - { - iconProps: { - name: 'microphone', - fill: '#C7C8D8', - height: 40, - width: 40, - pack: 'custom', - }, - title: t('chat.files.record-sound'), - onPress: () => {}, - }, { iconProps: { name: 'add-picture', @@ -84,6 +131,43 @@ export const AddFileMenu: React.FC<{ close: () => void }> = ({ close }) => { }, ] + let RECORD_CONFIG = { + backgroundColor: 'white', + iconProps: { + name: 'microphone', + pack: 'custom', + fill: '#C7C8D8', + }, + } + + if (recorderState === RecorderState.Recording) { + RECORD_CONFIG = { + backgroundColor: '#F89A9A', + iconProps: { + name: 'microphone', + pack: 'custom', + fill: '#F65B77', + }, + } + } else if (recorderState === RecorderState.Recorded) { + RECORD_CONFIG = { + backgroundColor: '#6B80FF', + iconProps: { + name: 'paper-plane-outline', + fill: '#5360CC', + pack: '', + }, + } + } + + let duration = '00:00' + + if (recorderState === RecorderState.Recorded && !player.isStopped) { + duration = moment.utc(player.currentTime).format('mm:ss') + } else if (recordingDuration) { + duration = moment.utc(recordingDuration).format('mm:ss') + } + return ( void }> = ({ close }) => { ]} > - + void }> = ({ close }) => { border.shadow.big, ]} > + { + if (recorderState !== RecorderState.Default) { + return + } + + recorder = new Recorder('tempVoiceClip.aac').prepare((err, filePath) => { + if (err) { + console.log('recorder prepare error', err?.message) + } + console.log(filePath) + // setRecorderFilePath(filePath) + }) + recorder.record((err) => { + if (err) { + err && console.log('record err', err?.message) + } else { + setRecorderState(RecorderState.Recording) + setRecordStartTime(moment()) + setRecordStopTime(null) + startTimer() + } + }) + }} + onPressOut={() => { + if (recorderState !== RecorderState.Recording) { + return + } + recorder.stop(() => { + player.prepare() + recorder?.destroy() + }) + setRecorderState( + recorderState === RecorderState.Recording + ? RecorderState.Recorded + : RecorderState.Default, + ) + setRecordStopTime(moment()) + clearInterval(intervalId) + }} + > + + + + + + {recorderState !== RecorderState.Recorded && ( + + + {recorderState === RecorderState.Recording + ? t('chat.files.recording') + : t('chat.files.record-sound')} + + + )} + + {recorderState === RecorderState.Recorded && ( + + { + if (player?.isPlaying) { + player.pause() + } else if (player?.isPaused) { + player.seek(player.currentTime, (err) => { + if (err) { + console.log(err?.message) + } + player.playPause() + }) + } else { + player.play((err) => { + if (err) { + } else { + startTimer() + } + }) + } + }} + style={[ + padding.vertical.tiny, + padding.horizontal.big, + border.radius.small, + { + backgroundColor: '#4F58C0', + alignSelf: 'center', + alignItems: 'center', + justifyContent: 'center', + }, + ]} + > + + + + )} + + {recorderState !== RecorderState.Default && ( + + {duration} + + )} + {recorderState === RecorderState.Recorded && ( + { + setRecorderState(RecorderState.Default) + recorder?.destroy() + setRecordStartTime(null) + setRecordStopTime(null) + player?.stop() + }} + style={[padding.tiny, margin.left.small]} + > + + + )} + + {recorderState === RecorderState.Recorded && ( + + + + )} + + {LIST_CONFIG.map((listItem) => ( ))} diff --git a/js/packages/messenger-app/custom-icons-svgs/pause-player.svg b/js/packages/messenger-app/custom-icons-svgs/pause-player.svg new file mode 100644 index 0000000000..1755a1ce95 --- /dev/null +++ b/js/packages/messenger-app/custom-icons-svgs/pause-player.svg @@ -0,0 +1,13 @@ + + + Shape + + + + + + + + \ No newline at end of file diff --git a/js/packages/messenger-app/custom-icons-svgs/play-player.svg b/js/packages/messenger-app/custom-icons-svgs/play-player.svg new file mode 100644 index 0000000000..adfef242a4 --- /dev/null +++ b/js/packages/messenger-app/custom-icons-svgs/play-player.svg @@ -0,0 +1,11 @@ + + + Shape + + + + + + \ No newline at end of file diff --git a/js/packages/messenger-app/custom-icons.tsx b/js/packages/messenger-app/custom-icons.tsx index c9547bf24c..ecccca35db 100644 --- a/js/packages/messenger-app/custom-icons.tsx +++ b/js/packages/messenger-app/custom-icons.tsx @@ -19,6 +19,8 @@ import Berty from './custom-icons-svgs/berty_picto.svg' import Bertyzzz from './custom-icons-svgs/bertyzzz.svg' import AddPicture from './custom-icons-svgs/add-picture.svg' import Microphone from './custom-icons-svgs/microphone.svg' +import Play from './custom-icons-svgs/play-player.svg' +import Pause from './custom-icons-svgs/pause-player.svg' const iconsMap: { [key: string]: React.FC } = { fingerprint: Fingerprint, @@ -38,6 +40,8 @@ const iconsMap: { [key: string]: React.FC } = { bertyzzz: Bertyzzz, 'add-picture': AddPicture, microphone: Microphone, + play: Play, + pause: Pause, } const CustomIcon: React.FC<{