Skip to content

Commit

Permalink
[WIP] Audio message functionality (#247)
Browse files Browse the repository at this point in the history
* [NEW] Add module react-native-audio

* [WIP] Audio message basic UI

* [NEW] Record audio message
  • Loading branch information
kb0304 authored and ggazzo committed Mar 7, 2018
1 parent f71ab39 commit 523387f
Show file tree
Hide file tree
Showing 14 changed files with 278 additions and 22 deletions.
1 change: 1 addition & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ android {
}

dependencies {
compile project(':react-native-audio')
compile project(":reactnativekeyboardinput")
compile project(':react-native-splash-screen')
compile project(':react-native-video')
Expand Down
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.brentvatne.react.ReactVideoPackage;
import com.remobile.toast.RCTToastPackage;
import com.wix.reactnativekeyboardinput.KeyboardInputPackage;
import com.rnim.rn.audio.ReactNativeAudioPackage;

import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -44,6 +45,7 @@ protected List<ReactPackage> getPackages() {
new ReactVideoPackage(),
new SplashScreenReactPackage(),
new RCTToastPackage(),
new ReactNativeAudioPackage(),
new KeyboardInputPackage(MainApplication.this),
new RocketChatNativePackage()
);
Expand Down
2 changes: 1 addition & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
# org.gradle.parallel=true

android.useDeprecatedNdk=true
VERSIONCODE=999999999
# VERSIONCODE=999999999
2 changes: 2 additions & 0 deletions android/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
rootProject.name = 'RocketChatRN'
include ':react-native-audio'
project(':react-native-audio').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-audio/android')
include ':reactnativekeyboardinput'
project(':reactnativekeyboardinput').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keyboard-input/lib/android')
include ':react-native-splash-screen'
Expand Down
127 changes: 127 additions & 0 deletions app/containers/MessageBox/Recording.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React from 'react';
import PropTypes from 'prop-types';
import { View, SafeAreaView, Platform, PermissionsAndroid, Text } from 'react-native';
import { AudioRecorder, AudioUtils } from 'react-native-audio';
import Icon from 'react-native-vector-icons/MaterialIcons';
import styles from './styles';

export const _formatTime = function(seconds) {
let minutes = Math.floor(seconds / 60);
seconds %= 60;
if (minutes < 10) { minutes = `0${ minutes }`; }
if (seconds < 10) { seconds = `0${ seconds }`; }
return `${ minutes }:${ seconds }`;
};

export default class extends React.PureComponent {
static propTypes = {
onFinish: PropTypes.func.isRequired
}

static async permission() {
if (Platform.OS !== 'android') {
return true;
}

const rationale = {
title: 'Microphone Permission',
message: 'Rocket Chat needs access to your microphone so you can send audio message.'
};

const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, rationale);
return result === true || result === PermissionsAndroid.RESULTS.GRANTED;
}

constructor() {
super();

this.recordingCanceled = false;
this.state = {
currentTime: '00:00'
};
}

componentDidMount() {
const audioPath = `${ AudioUtils.CachesDirectoryPath }/${ Date.now() }.aac`;

AudioRecorder.prepareRecordingAtPath(audioPath, {
SampleRate: 22050,
Channels: 1,
AudioQuality: 'Low',
AudioEncoding: 'aac'
});

AudioRecorder.onProgress = (data) => {
this.setState({
currentTime: _formatTime(Math.floor(data.currentTime))
});
};
//
AudioRecorder.onFinished = (data) => {
if (!this.recordingCanceled && Platform.OS === 'ios') {
this._finishRecording(data.status === 'OK', data.audioFileURL);
}
};
AudioRecorder.startRecording();
}

_finishRecording(didSucceed, filePath) {
if (!didSucceed) {
return this.props.onFinish && this.props.onFinish(didSucceed);
}

const path = filePath.startsWith('file://') ? filePath.split('file://')[1] : filePath;
const fileInfo = {
type: 'audio/aac',
store: 'Uploads',
path
};
return this.props.onFinish && this.props.onFinish(fileInfo);
}

finishAudioMessage = async() => {
try {
const filePath = await AudioRecorder.stopRecording();
if (Platform.OS === 'android') {
this._finishRecording(true, filePath);
}
} catch (err) {
this._finishRecording(false);
console.error(err);
}
}

cancelAudioMessage = async() => {
this.recordingCanceled = true;
await AudioRecorder.stopRecording();
return this._finishRecording(false);
}

render() {
return (
<SafeAreaView
key='messagebox'
style={styles.textBox}
>
<View style={[styles.textArea, { backgroundColor: '#F6F7F9' }]}>
<Icon
style={[styles.actionButtons, { color: 'red' }]}
name='clear'
key='clear'
accessibilityLabel='Cancel recording'
accessibilityTraits='button'
onPress={this.cancelAudioMessage}
/>
<Text key='currentTime' style={[styles.textBoxInput, { width: 50, height: 60 }]}>{this.state.currentTime}</Text>
<Icon
style={[styles.actionButtons, { color: 'green' }]}
name='check'
key='check'
accessibilityLabel='Finish recording'
accessibilityTraits='button'
onPress={this.finishAudioMessage}
/>
</View>
</SafeAreaView>);
}
}
49 changes: 39 additions & 10 deletions app/containers/MessageBox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ImagePicker from 'react-native-image-picker';
import { connect } from 'react-redux';
import { emojify } from 'react-emojione';
import { KeyboardAccessoryView } from 'react-native-keyboard-input';

import { userTyping, layoutAnimation } from '../../actions/room';
import RocketChat from '../../lib/rocketchat';
import { editRequest, editCancel, clearInput } from '../../actions/messages';
Expand All @@ -15,8 +16,10 @@ import database from '../../lib/realm';
import Avatar from '../Avatar';
import CustomEmoji from '../EmojiPicker/CustomEmoji';
import { emojis } from '../../emojis';
import Recording from './Recording';
import './EmojiKeyboard';


const MENTIONS_TRACKING_TYPE_USERS = '@';
const MENTIONS_TRACKING_TYPE_EMOJIS = ':';

Expand Down Expand Up @@ -57,7 +60,7 @@ export default class MessageBox extends React.PureComponent {
mentions: [],
showMentionsContainer: false,
showEmojiKeyboard: false,
trackingType: ''
recording: false
};
this.users = [];
this.rooms = [];
Expand Down Expand Up @@ -138,16 +141,23 @@ export default class MessageBox extends React.PureComponent {
accessibilityTraits='button'
onPress={() => this.submit(this.state.text)}
/>);
} else {
icons.push(<MyIcon
style={[styles.actionButtons, { color: '#2F343D', fontSize: 16 }]}
name='plus'
key='fileIcon'
accessibilityLabel='Message actions'
accessibilityTraits='button'
onPress={() => this.addFile()}
/>);
return icons;
}
icons.push(<Icon
style={[styles.actionButtons, { color: '#1D74F5', paddingHorizontal: 10 }]}
name='mic'
accessibilityLabel='Send audio message'
accessibilityTraits='button'
onPress={() => this.recordAudioMessage()}
/>);
icons.push(<MyIcon
style={[styles.actionButtons, { color: '#2F343D', fontSize: 16 }]}
name='plus'
key='fileIcon'
accessibilityLabel='Message actions'
accessibilityTraits='button'
onPress={() => this.addFile()}
/>);
return icons;
}

Expand Down Expand Up @@ -188,9 +198,25 @@ export default class MessageBox extends React.PureComponent {
showEmojiKeyboard: true
});
}

async recordAudioMessage() {
const recording = await Recording.permission();
this.setState({ recording });
}

finishAudioMessage = async(fileInfo) => {
if (fileInfo) {
RocketChat.sendFileMessage(this.props.rid, fileInfo);
}
this.setState({
recording: false
});
}

closeEmoji() {
this.setState({ showEmojiKeyboard: false });
}

submit(message) {
this.setState({ text: '' });
this.closeEmoji();
Expand Down Expand Up @@ -446,6 +472,9 @@ export default class MessageBox extends React.PureComponent {
);

renderContent() {
if (this.state.recording) {
return (<Recording onFinish={this.finishAudioMessage} />);
}
return (
[
this.renderMentions(),
Expand Down
2 changes: 1 addition & 1 deletion app/containers/message/Audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export default class Audio extends React.PureComponent {
}

onLoad(data) {
this.setState({ duration: data.duration });
this.setState({ duration: data.duration > 0 ? data.duration : 0 });
}

onProgress(data) {
Expand Down
3 changes: 3 additions & 0 deletions app/lib/realm.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ class DB {
deleteAll(...args) {
return this.database.write(() => this.database.deleteAll(...args));
}
delete(...args) {
return this.database.delete(...args);
}
write(...args) {
return this.database.write(...args);
}
Expand Down
22 changes: 16 additions & 6 deletions app/lib/rocketchat.js
Original file line number Diff line number Diff line change
Expand Up @@ -519,10 +519,16 @@ const RocketChat = {
return call('sendFileMessage', rid, null, data, msg);
},
async sendFileMessage(rid, fileInfo, data) {
const placeholder = RocketChat.getMessage(rid, 'Sending an image');
const placeholder = RocketChat.getMessage(rid, 'Sending a file');
try {
const result = await RocketChat._ufsCreate({ ...fileInfo, rid });
if (!data) {
data = await RNFetchBlob.wrap(fileInfo.path);
const fileStat = await RNFetchBlob.fs.stat(fileInfo.path);
fileInfo.size = fileStat.size;
fileInfo.name = fileStat.filename;
}

const result = await RocketChat._ufsCreate({ ...fileInfo, rid });
await RNFetchBlob.fetch('POST', result.url, {
'Content-Type': 'application/octet-stream'
}, data);
Expand All @@ -539,10 +545,14 @@ const RocketChat = {
} catch (e) {
return e;
} finally {
database.write(() => {
const msg = database.objects('messages').filtered('_id = $0', placeholder._id);
database.delete(msg);
});
try {
database.write(() => {
const msg = database.objects('messages').filtered('_id = $0', placeholder._id);
database.delete(msg);
});
} catch (e) {
console.error(e);
}
}
},
async getRooms() {
Expand Down
Loading

0 comments on commit 523387f

Please sign in to comment.