diff --git a/.circleci/config.yml b/.circleci/config.yml index 845b8b1d1..ab9141960 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -147,7 +147,7 @@ jobs: name: Install NPM modules command: | rm -rf node_modules - npm install --save react-native@0.51 + # npm install --save react-native@0.51 npm install # npm install react-native @@ -185,7 +185,7 @@ jobs: ios-testflight: macos: - xcode: "8.3.3" + xcode: "9.0" steps: - checkout diff --git a/android/app/build.gradle b/android/app/build.gradle index e6c65008c..fc2f40982 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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') diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 494507756..0137ae666 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + diff --git a/android/app/src/main/java/com/rocketchatrn/MainApplication.java b/android/app/src/main/java/com/rocketchatrn/MainApplication.java index 986de10f3..051a8fdc7 100644 --- a/android/app/src/main/java/com/rocketchatrn/MainApplication.java +++ b/android/app/src/main/java/com/rocketchatrn/MainApplication.java @@ -17,6 +17,7 @@ import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage; 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; @@ -44,6 +45,7 @@ public class MainApplication extends Application implements ReactApplication { new ReactVideoPackage(), new SplashScreenReactPackage(), new RCTToastPackage(), + new ReactNativeAudioPackage(), new KeyboardInputPackage(MainApplication.this), new RocketChatNativePackage() ); diff --git a/android/gradle.properties b/android/gradle.properties index d94d12020..55422fd77 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -17,4 +17,4 @@ # org.gradle.parallel=true android.useDeprecatedNdk=true -VERSIONCODE=999999999 +# VERSIONCODE=999999999 diff --git a/android/settings.gradle b/android/settings.gradle index 236de741b..5c628bbf4 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -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' diff --git a/app/ReactotronConfig.js b/app/ReactotronConfig.js index 3a4b6b009..c6d66e7a3 100644 --- a/app/ReactotronConfig.js +++ b/app/ReactotronConfig.js @@ -1,20 +1,13 @@ /* eslint-disable */ -import Reactotron, { asyncStorage, networking, openInEditor } from 'reactotron-react-native'; -import { NativeModules } from 'react-native'; - +import Reactotron from 'reactotron-react-native'; import { reactotronRedux } from 'reactotron-redux'; +import sagaPlugin from 'reactotron-redux-saga' -// uncomment the following lines to test on a device -// let scriptHostname; -// if (__DEV__) { -// const scriptURL = NativeModules.SourceCode.scriptURL; -// scriptHostname = scriptURL.split('://')[1].split(':')[0]; -// } - -Reactotron.configure(/*{ host: scriptHostname }*/) // controls connection & communication settings - .useReactNative() // add all built-in react native plugins - .use(reactotronRedux()) - .use(asyncStorage()) - .use(networking()) - .use(openInEditor()) - .connect(); +if (__DEV__) { + Reactotron + .configure() + .useReactNative() + .use(reactotronRedux()) + .use(sagaPlugin()) + .connect(); +} diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index aa3ec69bc..2f1f95a30 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -38,6 +38,7 @@ export const ROOM = createRequestTypes('ROOM', [ 'SOMEONE_TYPING', 'OPEN', 'CLOSE', + 'LEAVE', 'USER_TYPING', 'MESSAGE_RECEIVED', 'SET_LAST_OPEN', @@ -96,6 +97,8 @@ export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'REQUEST' export const STARRED_MESSAGES = createRequestTypes('STARRED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNSTARRED']); export const PINNED_MESSAGES = createRequestTypes('PINNED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNPINNED']); export const MENTIONED_MESSAGES = createRequestTypes('MENTIONED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED']); +export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED']); +export const ROOM_FILES = createRequestTypes('ROOM_FILES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED']); export const INCREMENT = 'INCREMENT'; export const DECREMENT = 'DECREMENT'; diff --git a/app/actions/room.js b/app/actions/room.js index 1b3a67e1f..533fb4ab3 100644 --- a/app/actions/room.js +++ b/app/actions/room.js @@ -35,6 +35,13 @@ export function closeRoom() { }; } +export function leaveRoom(rid) { + return { + type: types.ROOM.LEAVE, + rid + }; +} + export function userTyping(status = true) { return { type: types.ROOM.USER_TYPING, diff --git a/app/actions/roomFiles.js b/app/actions/roomFiles.js new file mode 100644 index 000000000..e8c674b7b --- /dev/null +++ b/app/actions/roomFiles.js @@ -0,0 +1,21 @@ +import * as types from './actionsTypes'; + +export function openRoomFiles(rid) { + return { + type: types.ROOM_FILES.OPEN, + rid + }; +} + +export function closeRoomFiles() { + return { + type: types.ROOM_FILES.CLOSE + }; +} + +export function roomFilesReceived(messages) { + return { + type: types.ROOM_FILES.MESSAGES_RECEIVED, + messages + }; +} diff --git a/app/actions/snippetedMessages.js b/app/actions/snippetedMessages.js new file mode 100644 index 000000000..54c6edeea --- /dev/null +++ b/app/actions/snippetedMessages.js @@ -0,0 +1,21 @@ +import * as types from './actionsTypes'; + +export function openSnippetedMessages(rid) { + return { + type: types.SNIPPETED_MESSAGES.OPEN, + rid + }; +} + +export function closeSnippetedMessages() { + return { + type: types.SNIPPETED_MESSAGES.CLOSE + }; +} + +export function snippetedMessagesReceived(messages) { + return { + type: types.SNIPPETED_MESSAGES.MESSAGES_RECEIVED, + messages + }; +} diff --git a/app/containers/MessageBox/Recording.js b/app/containers/MessageBox/Recording.js new file mode 100644 index 000000000..10e32b898 --- /dev/null +++ b/app/containers/MessageBox/Recording.js @@ -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 ( + + + + {this.state.currentTime} + + + ); + } +} diff --git a/app/containers/MessageBox/index.js b/app/containers/MessageBox/index.js index 45886561f..313e7740e 100644 --- a/app/containers/MessageBox/index.js +++ b/app/containers/MessageBox/index.js @@ -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'; @@ -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 = ':'; @@ -57,7 +60,7 @@ export default class MessageBox extends React.PureComponent { mentions: [], showMentionsContainer: false, showEmojiKeyboard: false, - trackingType: '' + recording: false }; this.users = []; this.rooms = []; @@ -138,16 +141,24 @@ export default class MessageBox extends React.PureComponent { accessibilityTraits='button' onPress={() => this.submit(this.state.text)} />); - } else { - icons.push( this.addFile()} - />); + return icons; } + icons.push( this.recordAudioMessage()} + />); + icons.push( this.addFile()} + />); return icons; } @@ -188,9 +199,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(); @@ -446,6 +473,9 @@ export default class MessageBox extends React.PureComponent { ); renderContent() { + if (this.state.recording) { + return (); + } return ( [ this.renderMentions(), diff --git a/app/containers/message/Audio.js b/app/containers/message/Audio.js index df17fa11c..d9db162a2 100644 --- a/app/containers/message/Audio.js +++ b/app/containers/message/Audio.js @@ -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) { diff --git a/app/containers/message/Image.js b/app/containers/message/Image.js index 4a88d1f82..942676dcc 100644 --- a/app/containers/message/Image.js +++ b/app/containers/message/Image.js @@ -13,15 +13,11 @@ const styles = StyleSheet.create({ borderWidth: 1, borderRadius: 6 }, - imageContainer: { - flex: 1, - alignItems: 'center', - justifyContent: 'center' - }, image: { - width: 256, - height: 256, - resizeMode: 'cover' + flex: 1, + height: undefined, + width: undefined, + resizeMode: 'contain' }, labelContainer: { height: 62, @@ -46,14 +42,7 @@ export default class Image extends React.PureComponent { user: PropTypes.object.isRequired } - constructor(props) { - super(props); - const { baseUrl, file, user } = props; - this.state = { - modalVisible: false, - img: `${ baseUrl }${ file.image_url }?rc_uid=${ user.id }&rc_token=${ user.token }` - }; - } + state = { modalVisible: false }; getDescription() { if (this.props.file.description) { @@ -68,18 +57,18 @@ export default class Image extends React.PureComponent { } render() { + const { baseUrl, file, user } = this.props; + const img = `${ baseUrl }${ file.image_url }?rc_uid=${ user.id }&rc_token=${ user.token }`; return ( this._onPressButton()} style={styles.button} > - - - + {this.props.file.title} {this.getDescription()} @@ -87,7 +76,7 @@ export default class Image extends React.PureComponent { this.setState({ modalVisible: false })} /> diff --git a/app/containers/message/Video.js b/app/containers/message/Video.js index e3c3114df..0b8a019ab 100644 --- a/app/containers/message/Video.js +++ b/app/containers/message/Video.js @@ -34,15 +34,7 @@ export default class Video extends React.PureComponent { user: PropTypes.object.isRequired } - constructor(props) { - super(props); - const { baseUrl, file, user } = props; - this.state = { - isVisible: false, - uri: `${ baseUrl }${ file.video_url }?rc_uid=${ user.id }&rc_token=${ user.token }` - }; - } - + state = { isVisible: false }; toggleModal() { this.setState({ @@ -58,8 +50,10 @@ export default class Video extends React.PureComponent { } render() { - const { isVisible, uri } = this.state; - const { description } = this.props.file; + const { isVisible } = this.state; + const { video_url, description } = this.props.file; + const { baseUrl, user } = this.props; + const uri = `${ baseUrl }${ video_url }?rc_uid=${ user.id }&rc_token=${ user.token }`; return ( this.toggleModal()} > {}, + _updatedAt: new Date() + } + constructor(props) { super(props); this.state = { reactionsModal: false }; @@ -90,26 +95,32 @@ export default class Message extends React.Component { getInfoMessage() { let message = ''; - const messageType = this.props.item.t; + const { + t, role, msg, u + } = this.props.item; - if (messageType === 'rm') { + if (t === 'rm') { message = 'Message removed'; - } else if (messageType === 'uj') { + } else if (t === 'uj') { message = 'Has joined the channel.'; - } else if (messageType === 'r') { - message = `Room name changed to: ${ this.props.item.msg } by ${ this.props.item.u.username }`; - } else if (messageType === 'message_pinned') { + } else if (t === 'r') { + message = `Room name changed to: ${ msg } by ${ u.username }`; + } else if (t === 'message_pinned') { message = 'Message pinned'; - } else if (messageType === 'ul') { + } else if (t === 'ul') { message = 'Has left the channel.'; - } else if (messageType === 'ru') { - message = `User ${ this.props.item.msg } removed by ${ this.props.item.u.username }`; - } else if (messageType === 'au') { - message = `User ${ this.props.item.msg } added by ${ this.props.item.u.username }`; - } else if (messageType === 'user-muted') { - message = `User ${ this.props.item.msg } muted by ${ this.props.item.u.username }`; - } else if (messageType === 'user-unmuted') { - message = `User ${ this.props.item.msg } unmuted by ${ this.props.item.u.username }`; + } else if (t === 'ru') { + message = `User ${ msg } removed by ${ u.username }`; + } else if (t === 'au') { + message = `User ${ msg } added by ${ u.username }`; + } else if (t === 'user-muted') { + message = `User ${ msg } muted by ${ u.username }`; + } else if (t === 'user-unmuted') { + message = `User ${ msg } unmuted by ${ u.username }`; + } else if (t === 'subscription-role-added') { + message = `${ msg } was set ${ role } by ${ u.username }`; + } else if (t === 'subscription-role-removed') { + message = `${ msg } is no longer ${ role } by ${ u.username }`; } return message; @@ -118,7 +129,9 @@ export default class Message extends React.Component { parseMessage = () => JSON.parse(JSON.stringify(this.props.item)); isInfoMessage() { - return ['r', 'au', 'ru', 'ul', 'uj', 'rm', 'user-muted', 'user-unmuted', 'message_pinned'].includes(this.props.item.t); + return [ + 'r', 'au', 'ru', 'ul', 'uj', 'rm', 'user-muted', 'user-unmuted', 'message_pinned', 'subscription-role-added', 'subscription-role-removed' + ].includes(this.props.item.t); } isDeleted() { diff --git a/app/containers/routes/AuthRoutes.js b/app/containers/routes/AuthRoutes.js index c3e80262b..9dd5cab43 100644 --- a/app/containers/routes/AuthRoutes.js +++ b/app/containers/routes/AuthRoutes.js @@ -11,6 +11,8 @@ import NewServerView from '../../views/NewServerView'; import StarredMessagesView from '../../views/StarredMessagesView'; import PinnedMessagesView from '../../views/PinnedMessagesView'; import MentionedMessagesView from '../../views/MentionedMessagesView'; +import SnippetedMessagesView from '../../views/SnippetedMessagesView'; +import RoomFilesView from '../../views/RoomFilesView'; import RoomMembersView from '../../views/RoomMembersView'; const AuthRoutes = StackNavigator( @@ -67,6 +69,20 @@ const AuthRoutes = StackNavigator( headerTintColor: '#292E35' } }, + SnippetedMessages: { + screen: SnippetedMessagesView, + navigationOptions: { + title: 'Snippet Messages', + headerTintColor: '#292E35' + } + }, + RoomFiles: { + screen: RoomFilesView, + navigationOptions: { + title: 'Room Files', + headerTintColor: '#292E35' + } + }, RoomMembers: { screen: RoomMembersView, navigationOptions: { diff --git a/app/containers/routes/NavigationService.js b/app/containers/routes/NavigationService.js index a43320585..21f973538 100644 --- a/app/containers/routes/NavigationService.js +++ b/app/containers/routes/NavigationService.js @@ -22,6 +22,15 @@ export function goBack() { } } +export function goRoomsList() { + if (config.navigator) { + const action = NavigationActions.reset({ + index: 0, + actions: [NavigationActions.navigate({ routeName: 'RoomsList' })] + }); + config.navigator.dispatch(action); + } +} export function goRoom({ rid, name }, counter = 0) { // about counter: we can call this method before navigator be set. so we have to wait, if we tried a lot, we give up ... diff --git a/app/lib/createStore.js b/app/lib/createStore.js index 27f75b927..288b64a31 100644 --- a/app/lib/createStore.js +++ b/app/lib/createStore.js @@ -1,40 +1,37 @@ -import { createStore, applyMiddleware, compose } from 'redux'; +import { createStore as reduxCreateStore, applyMiddleware, compose } from 'redux'; import createSagaMiddleware from 'redux-saga'; import logger from 'redux-logger'; -import { composeWithDevTools } from 'remote-redux-devtools'; import applyAppStateListener from 'redux-enhancer-react-native-appstate'; +import Reactotron from 'reactotron-react-native'; // eslint-disable-line import reducers from '../reducers'; import sagas from '../sagas'; -const sagaMiddleware = createSagaMiddleware(); -let enhacers; +const createStore = __DEV__ ? Reactotron.createStore : reduxCreateStore; +let sagaMiddleware; +let enhancers; if (__DEV__) { /* eslint-disable global-require */ const reduxImmutableStateInvariant = require('redux-immutable-state-invariant').default(); + sagaMiddleware = createSagaMiddleware({ + sagaMonitor: Reactotron.createSagaMonitor() + }); - const devComposer = composeWithDevTools({ hostname: 'localhost', port: 8000 }); - enhacers = devComposer( + enhancers = compose( applyAppStateListener(), applyMiddleware(reduxImmutableStateInvariant), applyMiddleware(sagaMiddleware), applyMiddleware(logger) ); } else { - enhacers = compose( + sagaMiddleware = createSagaMiddleware(); + enhancers = compose( applyAppStateListener(), applyMiddleware(sagaMiddleware) ); } -// uncomment the following lines to integrate reactotron with redux -// const store = Reactotron.createStore( -// reducers, -// enhacers(createStore)(reducers), -// ); - - -const store = enhacers(createStore)(reducers); +const store = createStore(reducers, enhancers); sagaMiddleware.run(sagas); if (module.hot && typeof module.hot.accept === 'function') { diff --git a/app/lib/realm.js b/app/lib/realm.js index dde3ab765..ec0fa3824 100644 --- a/app/lib/realm.js +++ b/app/lib/realm.js @@ -88,7 +88,8 @@ const subscriptionSchema = { lastMessage: { type: 'messages', optional: true }, description: { type: 'string', optional: true }, announcement: { type: 'string', optional: true }, - topic: { type: 'string', optional: true } + topic: { type: 'string', optional: true }, + blocked: { type: 'bool', optional: true } } }; @@ -200,7 +201,8 @@ const messagesSchema = { pinned: { type: 'bool', optional: true }, starred: { type: 'bool', optional: true }, editedBy: 'messagesEditedBy', - reactions: { type: 'list', objectType: 'messagesReactions' } + reactions: { type: 'list', objectType: 'messagesReactions' }, + role: { type: 'string', optional: true } } }; @@ -267,6 +269,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); } diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index d1df2baa9..1ea5c6d60 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -16,6 +16,8 @@ import { requestActiveUser } from '../actions/activeUsers'; import { starredMessagesReceived, starredMessageUnstarred } from '../actions/starredMessages'; import { pinnedMessagesReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages'; import { mentionedMessagesReceived } from '../actions/mentionedMessages'; +import { snippetedMessagesReceived } from '../actions/snippetedMessages'; +import { roomFilesReceived } from '../actions/roomFiles'; import Ddp from './ddp'; export { Accounts } from 'react-native-meteor'; @@ -153,6 +155,11 @@ const RocketChat = { if (data.roles) { data.roles = data.roles.map(role => ({ value: role })); } + if (data.blocker) { + data.blocked = true; + } else { + data.blocked = false; + } database.write(() => { database.create('subscriptions', data, true); }); @@ -241,6 +248,71 @@ const RocketChat = { } }); + this.ddp.on('rocketchat_snippeted_message', (ddpMessage) => { + if (ddpMessage.msg === 'added') { + this.snippetedMessages = this.snippetedMessages || []; + + if (this.snippetedMessagesTimer) { + clearTimeout(this.snippetedMessagesTimer); + this.snippetedMessagesTimer = null; + } + + this.snippetedMessagesTimer = setTimeout(() => { + reduxStore.dispatch(snippetedMessagesReceived(this.snippetedMessages)); + this.snippetedMessagesTimer = null; + return this.snippetedMessages = []; + }, 1000); + const message = ddpMessage.fields; + message._id = ddpMessage.id; + const snippetedMessage = this._buildMessage(message); + this.snippetedMessages = [...this.snippetedMessages, snippetedMessage]; + } + }); + + this.ddp.on('room_files', (ddpMessage) => { + if (ddpMessage.msg === 'added') { + this.roomFiles = this.roomFiles || []; + + if (this.roomFilesTimer) { + clearTimeout(this.roomFilesTimer); + this.roomFilesTimer = null; + } + + this.roomFilesTimer = setTimeout(() => { + reduxStore.dispatch(roomFilesReceived(this.roomFiles)); + this.roomFilesTimer = null; + return this.roomFiles = []; + }, 1000); + const { fields } = ddpMessage; + const message = { + _id: ddpMessage.id, + ts: fields.uploadedAt, + msg: fields.description, + status: 0, + attachments: [{ + title: fields.name + }], + urls: [], + reactions: [], + u: { + username: fields.user.username + } + }; + const fileUrl = `/file-upload/${ ddpMessage.id }/${ fields.name }`; + if (/image/.test(fields.type)) { + message.attachments[0].image_type = fields.type; + message.attachments[0].image_url = fileUrl; + } else if (/audio/.test(fields.type)) { + message.attachments[0].audio_type = fields.type; + message.attachments[0].audio_url = fileUrl; + } else if (/video/.test(fields.type)) { + message.attachments[0].video_type = fields.type; + message.attachments[0].video_url = fileUrl; + } + this.roomFiles = [...this.roomFiles, message]; + } + }); + this.ddp.on('meteor_accounts_loginServiceConfiguration', (ddpMessage) => { if (ddpMessage.msg === 'added') { this.loginServices = this.loginServices || {}; @@ -519,10 +591,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); @@ -539,10 +617,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() { @@ -740,6 +822,15 @@ const RocketChat = { }, getRoomMembers(rid, allUsers) { return call('getUsersOfRoom', rid, allUsers); + }, + toggleBlockUser(rid, blocked, block) { + if (block) { + return call('blockUser', { rid, blocked }); + } + return call('unblockUser', { rid, blocked }); + }, + leaveRoom(rid) { + return call('leaveRoom', rid); } }; diff --git a/app/reducers/index.js b/app/reducers/index.js index 1725778a4..e7603c54c 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -15,6 +15,8 @@ import activeUsers from './activeUsers'; import starredMessages from './starredMessages'; import pinnedMessages from './pinnedMessages'; import mentionedMessages from './mentionedMessages'; +import snippetedMessages from './snippetedMessages'; +import roomFiles from './roomFiles'; export default combineReducers({ settings, @@ -32,5 +34,7 @@ export default combineReducers({ activeUsers, starredMessages, pinnedMessages, - mentionedMessages + mentionedMessages, + snippetedMessages, + roomFiles }); diff --git a/app/reducers/roomFiles.js b/app/reducers/roomFiles.js new file mode 100644 index 000000000..c27382d4d --- /dev/null +++ b/app/reducers/roomFiles.js @@ -0,0 +1,19 @@ +import { ROOM_FILES } from '../actions/actionsTypes'; + +const initialState = { + messages: [] +}; + +export default function server(state = initialState, action) { + switch (action.type) { + case ROOM_FILES.MESSAGES_RECEIVED: + return { + ...state, + messages: [...state.messages, ...action.messages] + }; + case ROOM_FILES.CLOSE: + return initialState; + default: + return state; + } +} diff --git a/app/reducers/snippetedMessages.js b/app/reducers/snippetedMessages.js new file mode 100644 index 000000000..839ff7d1b --- /dev/null +++ b/app/reducers/snippetedMessages.js @@ -0,0 +1,19 @@ +import { SNIPPETED_MESSAGES } from '../actions/actionsTypes'; + +const initialState = { + messages: [] +}; + +export default function server(state = initialState, action) { + switch (action.type) { + case SNIPPETED_MESSAGES.MESSAGES_RECEIVED: + return { + ...state, + messages: [...state.messages, ...action.messages] + }; + case SNIPPETED_MESSAGES.CLOSE: + return initialState; + default: + return state; + } +} diff --git a/app/sagas/index.js b/app/sagas/index.js index 757ac74dc..39aa6836e 100644 --- a/app/sagas/index.js +++ b/app/sagas/index.js @@ -12,6 +12,8 @@ import activeUsers from './activeUsers'; import starredMessages from './starredMessages'; import pinnedMessages from './pinnedMessages'; import mentionedMessages from './mentionedMessages'; +import snippetedMessages from './snippetedMessages'; +import roomFiles from './roomFiles'; const root = function* root() { yield all([ @@ -27,7 +29,9 @@ const root = function* root() { activeUsers(), starredMessages(), pinnedMessages(), - mentionedMessages() + mentionedMessages(), + snippetedMessages(), + roomFiles() ]); }; diff --git a/app/sagas/roomFiles.js b/app/sagas/roomFiles.js new file mode 100644 index 000000000..a2be1c530 --- /dev/null +++ b/app/sagas/roomFiles.js @@ -0,0 +1,14 @@ +import { take, takeLatest } from 'redux-saga/effects'; +import * as types from '../actions/actionsTypes'; +import RocketChat from '../lib/rocketchat'; + +const watchRoomFiles = function* watchRoomFiles({ rid }) { + const sub = yield RocketChat.subscribe('roomFiles', rid, 50); + yield take(types.ROOM_FILES.CLOSE); + sub.unsubscribe().catch(e => alert(e)); +}; + +const root = function* root() { + yield takeLatest(types.ROOM_FILES.OPEN, watchRoomFiles); +}; +export default root; diff --git a/app/sagas/rooms.js b/app/sagas/rooms.js index e8944fc40..a3017655c 100644 --- a/app/sagas/rooms.js +++ b/app/sagas/rooms.js @@ -1,3 +1,4 @@ +import { Alert } from 'react-native'; import { put, call, takeLatest, take, select, race, fork, cancel, takeEvery } from 'redux-saga/effects'; import { delay } from 'redux-saga'; import { FOREGROUND, BACKGROUND } from 'redux-enhancer-react-native-appstate'; @@ -7,6 +8,9 @@ import { addUserTyping, removeUserTyping, setLastOpen } from '../actions/room'; import { messagesRequest } from '../actions/messages'; import RocketChat from '../lib/rocketchat'; import database from '../lib/realm'; +import * as NavigationService from '../containers/routes/NavigationService'; + +const leaveRoom = rid => RocketChat.leaveRoom(rid); const getRooms = function* getRooms() { return yield RocketChat.getRooms(); @@ -117,6 +121,26 @@ const updateLastOpen = function* updateLastOpen() { yield put(setLastOpen()); }; +const handleLeaveRoom = function* handleLeaveRoom({ rid }) { + try { + yield call(leaveRoom, rid); + NavigationService.goRoomsList(); + yield delay(1000); + database.write(() => { + const messages = database.objects('messages').filtered('rid = $0', rid); + database.delete(messages); + const subscription = database.objects('subscriptions').filtered('rid = $0', rid); + database.delete(subscription); + }); + } catch (e) { + if (e.error === 'error-you-are-last-owner') { + Alert.alert('You are the last owner. Please set new owner before leaving the room.'); + } else { + Alert.alert(e); + } + } +}; + const root = function* root() { yield takeLatest(types.ROOM.USER_TYPING, watchuserTyping); yield takeLatest(types.LOGIN.SUCCESS, watchRoomsRequest); @@ -125,5 +149,6 @@ const root = function* root() { yield takeLatest(FOREGROUND, updateRoom); yield takeLatest(FOREGROUND, watchRoomsRequest); yield takeLatest(BACKGROUND, updateLastOpen); + yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom); }; export default root; diff --git a/app/sagas/snippetedMessages.js b/app/sagas/snippetedMessages.js new file mode 100644 index 000000000..081c0f90f --- /dev/null +++ b/app/sagas/snippetedMessages.js @@ -0,0 +1,14 @@ +import { take, takeLatest } from 'redux-saga/effects'; +import * as types from '../actions/actionsTypes'; +import RocketChat from '../lib/rocketchat'; + +const watchSnippetedMessagesRoom = function* watchSnippetedMessagesRoom({ rid }) { + const sub = yield RocketChat.subscribe('snippetedMessages', rid, 50); + yield take(types.SNIPPETED_MESSAGES.CLOSE); + sub.unsubscribe().catch(e => alert(e)); +}; + +const root = function* root() { + yield takeLatest(types.SNIPPETED_MESSAGES.OPEN, watchSnippetedMessagesRoom); +}; +export default root; diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 287c98b10..7eb6ea585 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, SectionList, Text } from 'react-native'; +import { View, SectionList, Text, Alert } from 'react-native'; import Icon from 'react-native-vector-icons/Ionicons'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import { connect } from 'react-redux'; @@ -10,14 +10,20 @@ import Avatar from '../../containers/Avatar'; import Touch from '../../utils/touch'; import database from '../../lib/realm'; import RocketChat from '../../lib/rocketchat'; +import { leaveRoom } from '../../actions/room'; @connect(state => ({ - baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' + baseUrl: state.settings.Site_Url || state.server ? state.server.server : '', + user: state.login.user +}), dispatch => ({ + leaveRoom: rid => dispatch(leaveRoom(rid)) })) export default class RoomActionsView extends React.PureComponent { static propTypes = { baseUrl: PropTypes.string, - navigation: PropTypes.object + user: PropTypes.object, + navigation: PropTypes.object, + leaveRoom: PropTypes.func } constructor(props) { @@ -26,24 +32,53 @@ export default class RoomActionsView extends React.PureComponent { this.rooms = database.objects('subscriptions').filtered('rid = $0', rid); this.state = { sections: [], - room: {} + room: {}, + members: [] }; } - componentDidMount() { - this.updateRoom(); - this.updateSections(); + async componentDidMount() { + await this.updateRoom(); + this.updateRoomMembers(); this.rooms.addListener(this.updateRoom); } - updateRoom = () => { + componentWillUnmount() { + this.rooms.removeAllListeners(); + } + + onPressTouchable = (item) => { + if (item.route) { + return this.props.navigation.navigate(item.route, item.params); + } + if (item.event) { + return item.event(); + } + } + + getRoomTitle = room => (room.t === 'd' ? room.fname : room.name); + + updateRoomMembers = async() => { + let members; + try { + const membersResult = await RocketChat.getRoomMembers(this.state.room.rid, false); + members = membersResult.records; + } catch (error) { + return; + } + this.setState({ members }); + this.updateSections(); + } + + updateRoom = async() => { const [room] = this.rooms; - this.setState({ room }); + await this.setState({ room }); this.updateSections(); } updateSections = async() => { - const { rid, t } = this.state.room; + const { rid, t, blocked } = this.state.room; + const { members } = this.state; const sections = [{ data: [{ icon: 'ios-star', name: 'USER' }], renderItem: this.renderRoomInfo @@ -55,7 +90,12 @@ export default class RoomActionsView extends React.PureComponent { renderItem: this.renderItem }, { data: [ - { icon: 'ios-attach', name: 'Files' }, + { + icon: 'ios-attach', + name: 'Files', + route: 'RoomFiles', + params: { rid } + }, { icon: 'ios-at-outline', name: 'Mentions', @@ -76,7 +116,12 @@ export default class RoomActionsView extends React.PureComponent { route: 'PinnedMessages', params: { rid } }, - { icon: 'ios-code', name: 'Snippets' }, + { + icon: 'ios-code', + name: 'Snippets', + route: 'SnippetedMessages', + params: { rid } + }, { icon: 'ios-notifications-outline', name: 'Notifications preferences' } ], renderItem: this.renderItem @@ -85,25 +130,34 @@ export default class RoomActionsView extends React.PureComponent { sections.push({ data: [ { icon: 'ios-volume-off', name: 'Mute user' }, - { icon: 'block', name: 'Block user', type: 'danger' } + { + icon: 'block', + name: `${ blocked ? 'Unblock' : 'Block' } user`, + type: 'danger', + event: () => this.toggleBlockUser() + } ], renderItem: this.renderItem }); } else if (t === 'c' || t === 'p') { - const membersResult = await RocketChat.getRoomMembers(rid, false); - const members = membersResult.records; - - sections[2].data.unshift({ - icon: 'ios-people', - name: 'Members', - description: (members.length === 1 ? `${ members.length } member` : `${ members.length } members`), - route: 'RoomMembers', - params: { rid, members } - }); + if (members.length > 0) { + sections[2].data.unshift({ + icon: 'ios-people', + name: 'Members', + description: (members.length === 1 ? `${ members.length } member` : `${ members.length } members`), + route: 'RoomMembers', + params: { rid, members } + }); + } sections.push({ data: [ { icon: 'ios-volume-off', name: 'Mute channel' }, - { icon: 'block', name: 'Leave channel', type: 'danger' } + { + icon: 'block', + name: 'Leave channel', + type: 'danger', + event: () => this.leaveChannel() + } ], renderItem: this.renderItem }); @@ -111,10 +165,37 @@ export default class RoomActionsView extends React.PureComponent { this.setState({ sections }); } + toggleBlockUser = () => { + const { rid, blocked } = this.state.room; + const { members } = this.state; + const member = members.find(m => m.id !== this.props.user.id); + RocketChat.toggleBlockUser(rid, member._id, !blocked); + } + + leaveChannel = () => { + const { room } = this.state; + Alert.alert( + 'Are you sure?', + `Are you sure you want to leave the room ${ this.getRoomTitle(room) }?`, + [ + { + text: 'Cancel', + style: 'cancel' + }, + { + text: 'Yes, leave it!', + style: 'destructive', + onPress: async() => { + this.props.leaveRoom(room.rid); + } + } + ] + ); + } + renderRoomInfo = ({ item }) => { - const { - fname, name, t, topic - } = this.state.room; + const { room } = this.state; + const { name, t, topic } = room; return ( this.renderTouchableItem([ , - {t === 'd' ? fname : name} + { this.getRoomTitle(room) } {t === 'd' ? `@${ name }` : topic} , @@ -136,7 +217,7 @@ export default class RoomActionsView extends React.PureComponent { renderTouchableItem = (subview, item) => ( item.route && this.props.navigation.navigate(item.route, item.params)} + onPress={() => this.onPressTouchable(item)} underlayColor='#FFFFFF' activeOpacity={0.5} accessibilityLabel={item.name} diff --git a/app/views/RoomFilesView/index.js b/app/views/RoomFilesView/index.js new file mode 100644 index 000000000..6fca8129c --- /dev/null +++ b/app/views/RoomFilesView/index.js @@ -0,0 +1,69 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FlatList, Text, View } from 'react-native'; +import { connect } from 'react-redux'; + +import { openRoomFiles, closeRoomFiles } from '../../actions/roomFiles'; +import styles from './styles'; +import Message from '../../containers/message'; + +@connect( + state => ({ + messages: state.roomFiles.messages, + user: state.login.user, + baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' + }), + dispatch => ({ + openRoomFiles: rid => dispatch(openRoomFiles(rid)), + closeRoomFiles: () => dispatch(closeRoomFiles()) + }) +) +export default class RoomFilesView extends React.PureComponent { + static propTypes = { + navigation: PropTypes.object, + messages: PropTypes.array, + user: PropTypes.object, + baseUrl: PropTypes.string, + openRoomFiles: PropTypes.func, + closeRoomFiles: PropTypes.func + } + + componentDidMount() { + this.props.openRoomFiles(this.props.navigation.state.params.rid); + } + + componentWillUnmount() { + this.props.closeRoomFiles(); + } + + renderEmpty = () => ( + + No files + + ) + + renderItem = ({ item }) => ( + + ) + + render() { + if (this.props.messages.length === 0) { + return this.renderEmpty(); + } + return ( + item._id} + /> + ); + } +} diff --git a/app/views/RoomFilesView/styles.js b/app/views/RoomFilesView/styles.js new file mode 100644 index 000000000..33a5e8d11 --- /dev/null +++ b/app/views/RoomFilesView/styles.js @@ -0,0 +1,17 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + list: { + flex: 1, + backgroundColor: '#ffffff' + }, + message: { + transform: [{ scaleY: 1 }] + }, + listEmptyContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#ffffff' + } +}); diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index 0377adcd5..ebe8c221a 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -53,7 +53,6 @@ export default class RoomView extends React.Component { loading: PropTypes.bool, actionMessage: PropTypes.object, toggleReactionPicker: PropTypes.func.isRequired, - // layoutAnimation: PropTypes.instanceOf(Date), actionsShow: PropTypes.func }; @@ -92,16 +91,10 @@ export default class RoomView extends React.Component { this.rooms.addListener(this.updateRoom); } - // componentWillReceiveProps(nextProps) { - // // if (this.props.layoutAnimation !== nextProps.layoutAnimation) { - // // LayoutAnimation.spring(); - // // } - // } shouldComponentUpdate(nextProps, nextState) { - return !(equal(this.props, nextProps) && equal(this.state, nextState)); + return !(equal(this.props, nextProps) && equal(this.state, nextState) && this.state.room.ro === nextState.room.ro); } componentWillUnmount() { - clearTimeout(this.timer); this.rooms.removeAllListeners(); this.props.editCancel(); } @@ -136,6 +129,7 @@ export default class RoomView extends React.Component { updateRoom = () => { this.setState({ room: this.rooms[0] }); + this.forceUpdate(); } sendMessage = (message) => { diff --git a/app/views/SnippetedMessagesView/index.js b/app/views/SnippetedMessagesView/index.js new file mode 100644 index 000000000..a659d20b3 --- /dev/null +++ b/app/views/SnippetedMessagesView/index.js @@ -0,0 +1,71 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FlatList, Text, View } from 'react-native'; +import { connect } from 'react-redux'; + +import { openSnippetedMessages, closeSnippetedMessages } from '../../actions/snippetedMessages'; +import styles from './styles'; +import Message from '../../containers/message'; + +@connect( + state => ({ + messages: state.snippetedMessages.messages, + user: state.login.user, + baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' + }), + dispatch => ({ + openSnippetedMessages: rid => dispatch(openSnippetedMessages(rid)), + closeSnippetedMessages: () => dispatch(closeSnippetedMessages()) + }) +) +export default class SnippetedMessagesView extends React.PureComponent { + static propTypes = { + navigation: PropTypes.object, + messages: PropTypes.array, + user: PropTypes.object, + baseUrl: PropTypes.string, + openSnippetedMessages: PropTypes.func, + closeSnippetedMessages: PropTypes.func + } + + componentDidMount() { + this.props.openSnippetedMessages(this.props.navigation.state.params.rid); + } + + componentWillUnmount() { + this.props.closeSnippetedMessages(); + } + + renderEmpty = () => ( + + No snippet messages + + ) + + renderItem = ({ item }) => ( + {}} + /> + ) + + render() { + if (this.props.messages.length === 0) { + return this.renderEmpty(); + } + return ( + item._id} + /> + ); + } +} diff --git a/app/views/SnippetedMessagesView/styles.js b/app/views/SnippetedMessagesView/styles.js new file mode 100644 index 000000000..33a5e8d11 --- /dev/null +++ b/app/views/SnippetedMessagesView/styles.js @@ -0,0 +1,17 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + list: { + flex: 1, + backgroundColor: '#ffffff' + }, + message: { + transform: [{ scaleY: 1 }] + }, + listEmptyContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#ffffff' + } +}); diff --git a/index.android.js b/index.android.js index f258d0e19..87ef488bf 100644 --- a/index.android.js +++ b/index.android.js @@ -2,13 +2,10 @@ import 'babel-polyfill'; import 'regenerator-runtime/runtime'; import { AppRegistry, UIManager } from 'react-native'; +import './app/ReactotronConfig'; import './app/push'; import RocketChat from './app/index'; UIManager.setLayoutAnimationEnabledExperimental(true); -// import './app/ReactotronConfig'; -// import { AppRegistry } from 'react-native'; -// import Routes from './app/routes'; -// AppRegistry.registerComponent('RocketChatRN', () => RocketChat); diff --git a/index.ios.js b/index.ios.js index bbe64aa5e..cad115bde 100644 --- a/index.ios.js +++ b/index.ios.js @@ -2,11 +2,8 @@ import 'babel-polyfill'; import 'regenerator-runtime/runtime'; import { AppRegistry } from 'react-native'; - +import './app/ReactotronConfig'; import './app/push'; import RocketChat from './app/index'; -// import { AppRegistry } from 'react-native'; -// import Routes from './app/routes'; -// import './app/ReactotronConfig'; -// + AppRegistry.registerComponent('RocketChatRN', () => RocketChat); diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index 58e3e6371..3b7d702e7 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -5,7 +5,6 @@ }; objectVersion = 46; objects = { - /* Begin PBXBuildFile section */ 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; @@ -47,6 +46,7 @@ 5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; }; 647660C6B6A340C7BD4D1099 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A18EFC3B0CFE40E0918A8F0C /* EvilIcons.ttf */; }; 70A8D9B456894EFFAF027CAB /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7A30DA4B2D474348824CD05B /* FontAwesome.ttf */; }; + 74815BBCB91147C08C8F7B3D /* libRNAudio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1142E3442BA94B19BCF52814 /* libRNAudio.a */; }; 77C35F50C01C43668188886C /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A0EEFAF8AB14F5B9E796CDD /* libRNVectorIcons.a */; }; 7A430E4F20238C46008F55BC /* libRCTCustomInputController.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A430E1E20238C02008F55BC /* libRCTCustomInputController.a */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; @@ -334,6 +334,27 @@ remoteGlobalIDString = 58B5119B1A9E6C1200147676; remoteInfo = RCTText; }; + A9A6C934204DD556006B7D9D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = EBF21BDC1FC498900052F4D5; + remoteInfo = jsinspector; + }; + A9A6C936204DD556006B7D9D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = EBF21BFA1FC4989A0052F4D5; + remoteInfo = "jsinspector-tvOS"; + }; + A9A6C944204DD557006B7D9D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C21010507E5B4B37BA0E4C9D /* RNAudio.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 42F559BA1CFC90C400DC3F84; + remoteInfo = RNAudio; + }; B810DF90203B10490010C331 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 4019A5E1911B4C61944FBCEC /* SafariViewManager.xcodeproj */; @@ -431,6 +452,7 @@ 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* RocketChatRNTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RocketChatRNTests.m; sourceTree = ""; }; 06BB44DD4855498082A744AD /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + 1142E3442BA94B19BCF52814 /* libRNAudio.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNAudio.a; sourceTree = ""; }; 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* RocketChatRN.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RocketChatRN.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -474,6 +496,7 @@ B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = KeyboardTrackingView.xcodeproj; path = "../node_modules/react-native-keyboard-tracking-view/lib/KeyboardTrackingView.xcodeproj"; sourceTree = ""; }; B8C682611FD84CEF003A12C8 /* icomoon.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = icomoon.ttf; path = ../resources/fonts/icomoon.ttf; sourceTree = ""; }; BAAE4B947F5D44959F0A9D5A /* libRNZeroconf.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNZeroconf.a; sourceTree = ""; }; + C21010507E5B4B37BA0E4C9D /* RNAudio.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNAudio.xcodeproj; path = "../node_modules/react-native-audio/ios/RNAudio.xcodeproj"; sourceTree = ""; }; C23AEF1D9EBE4A38A1A6B97B /* RNSVG.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSVG.xcodeproj; path = "../node_modules/react-native-svg/ios/RNSVG.xcodeproj"; sourceTree = ""; }; DA50CE47374C4C35BE6D9D58 /* libRNSVG.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSVG.a; sourceTree = ""; }; DC6EE17B5550465E98C70FF0 /* Entypo.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Entypo.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"; sourceTree = ""; }; @@ -520,6 +543,7 @@ 8ECBD927DDAC4987B98E102E /* libRCTVideo.a in Frameworks */, 0F026E58B8A6427D9A204D89 /* libSplashScreen.a in Frameworks */, 2C800DF680F8451599E80AF1 /* libSafariViewManager.a in Frameworks */, + 74815BBCB91147C08C8F7B3D /* libRNAudio.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -653,6 +677,8 @@ 3DAD3EAB1DF850E9000B6D8A /* libcxxreact.a */, 3DAD3EAD1DF850E9000B6D8A /* libjschelpers.a */, 3DAD3EAF1DF850E9000B6D8A /* libjschelpers.a */, + A9A6C935204DD556006B7D9D /* libjsinspector.a */, + A9A6C937204DD556006B7D9D /* libjsinspector-tvOS.a */, 607D610D1F325B7E00F639C4 /* libthird-party.a */, 607D610F1F325B7E00F639C4 /* libthird-party.a */, 607D61111F325B7E00F639C4 /* libdouble-conversion.a */, @@ -756,6 +782,7 @@ AD0379F2BCE84C968538CDAF /* RCTVideo.xcodeproj */, 30FCE1B6376C423E94C9FBB0 /* SplashScreen.xcodeproj */, 4019A5E1911B4C61944FBCEC /* SafariViewManager.xcodeproj */, + C21010507E5B4B37BA0E4C9D /* RNAudio.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -795,6 +822,14 @@ name = Products; sourceTree = ""; }; + A9A6C941204DD556006B7D9D /* Products */ = { + isa = PBXGroup; + children = ( + A9A6C945204DD557006B7D9D /* libRNAudio.a */, + ); + name = Products; + sourceTree = ""; + }; AF5E16F0398347E6A80C8CBE /* Resources */ = { isa = PBXGroup; children = ( @@ -850,6 +885,7 @@ 20CE3E407E0D4D9E8C9885F2 /* libRCTVideo.a */, B2607FA180F14E6584301101 /* libSplashScreen.a */, 1D3BB00B9ABF44EA9BD71318 /* libSafariViewManager.a */, + 1142E3442BA94B19BCF52814 /* libRNAudio.a */, ); name = "Recovered References"; sourceTree = ""; @@ -1075,6 +1111,10 @@ ProductGroup = 607D60ED1F325B7D00F639C4 /* Products */; ProjectRef = 5A8684E7C27E426C9206E980 /* RealmReact.xcodeproj */; }, + { + ProductGroup = A9A6C941204DD556006B7D9D /* Products */; + ProjectRef = C21010507E5B4B37BA0E4C9D /* RNAudio.xcodeproj */; + }, { ProductGroup = B8E79A881F3CCC6C005B464F /* Products */; ProjectRef = 4CD38E4891ED4601B7481448 /* RNFetchBlob.xcodeproj */; @@ -1367,6 +1407,27 @@ remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + A9A6C935204DD556006B7D9D /* libjsinspector.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libjsinspector.a; + remoteRef = A9A6C934204DD556006B7D9D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A9A6C937204DD556006B7D9D /* libjsinspector-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libjsinspector-tvOS.a"; + remoteRef = A9A6C936204DD556006B7D9D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A9A6C945204DD557006B7D9D /* libRNAudio.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRNAudio.a; + remoteRef = A9A6C944204DD557006B7D9D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; B810DF91203B10490010C331 /* libSafariViewManager.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -1618,6 +1679,7 @@ "$(SRCROOT)/../node_modules/react-native-video/ios", "$(SRCROOT)/../node_modules/react-native-splash-screen/ios", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-audio/ios", ); INFOPLIST_FILE = RocketChatRNTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1630,6 +1692,7 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1659,6 +1722,7 @@ "$(SRCROOT)/../node_modules/react-native-video/ios", "$(SRCROOT)/../node_modules/react-native-splash-screen/ios", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-audio/ios", ); INFOPLIST_FILE = RocketChatRNTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1671,6 +1735,7 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1705,6 +1770,7 @@ "$(SRCROOT)/../node_modules/react-native-video/ios", "$(SRCROOT)/../node_modules/react-native-splash-screen/ios", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-audio/ios", ); INFOPLIST_FILE = RocketChatRN/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1744,6 +1810,7 @@ "$(SRCROOT)/../node_modules/react-native-video/ios", "$(SRCROOT)/../node_modules/react-native-splash-screen/ios", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-audio/ios", ); INFOPLIST_FILE = RocketChatRN/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1786,6 +1853,7 @@ "$(SRCROOT)/../node_modules/react-native-video/ios", "$(SRCROOT)/../node_modules/react-native-splash-screen/ios", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-audio/ios", ); INFOPLIST_FILE = "RocketChatRN-tvOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1797,6 +1865,7 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1836,6 +1905,7 @@ "$(SRCROOT)/../node_modules/react-native-video/ios", "$(SRCROOT)/../node_modules/react-native-splash-screen/ios", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-audio/ios", ); INFOPLIST_FILE = "RocketChatRN-tvOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1847,6 +1917,7 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1882,6 +1953,7 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.RocketChatRN-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1913,6 +1985,7 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.RocketChatRN-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/ios/RocketChatRN/Images.xcassets/AppIcon.appiconset/Contents.json b/ios/RocketChatRN/Images.xcassets/AppIcon.appiconset/Contents.json index 7dbe1f67b..036ea96c5 100644 --- a/ios/RocketChatRN/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/RocketChatRN/Images.xcassets/AppIcon.appiconset/Contents.json @@ -145,8 +145,9 @@ "scale" : "2x" }, { - "idiom" : "ios-marketing", "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "icon-1024.png", "scale" : "1x" } ], diff --git a/ios/RocketChatRN/Images.xcassets/AppIcon.appiconset/icon-1024.png b/ios/RocketChatRN/Images.xcassets/AppIcon.appiconset/icon-1024.png new file mode 100644 index 000000000..01fd22e7d Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/AppIcon.appiconset/icon-1024.png differ diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index e17929b79..0fca665fc 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -22,6 +22,8 @@ ???? CFBundleVersion 100 + NSMicrophoneUsageDescription + This app uses the microphone to record audio message. ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index b9513cd89..9b32e9756 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -36,8 +36,7 @@ platform :ios do desc "This will also make sure the profile is up to date" lane :beta do match( - type: "appstore", - git_url: "git@github.com:RocketChat/Rocket.Chat.ReactNative.FastLane.git" + type: "appstore" ) # more information: https://codesigning.guide pem() gym(scheme: "RocketChatRN") # Build your app - more options available @@ -52,8 +51,7 @@ platform :ios do # match(type: "appstore") # snapshot match( - type: "appstore", - git_url: "git@github.com:RocketChat/Rocket.Chat.ReactNative.FastLane.git" + type: "appstore" ) # more information: https://codesigning.guide pem() gym(scheme: "RocketChatRN") # Build your app - more options available diff --git a/ios/fastlane/Matchfile b/ios/fastlane/Matchfile new file mode 100644 index 000000000..9826f288e --- /dev/null +++ b/ios/fastlane/Matchfile @@ -0,0 +1,9 @@ +git_url("https://github.com/RocketChat/Rocket.Chat.Cordova.Fastlane") + +type("development") # The default type, can be: appstore, adhoc, enterprise or development + +# app_identifier(["tools.fastlane.app", "tools.fastlane.app2"]) +# username("user@fastlane.tools") # Your Apple Developer Portal username + +# For all available options run `fastlane match --help` +# Remove the # in the beginning of the line to enable the other options diff --git a/package-lock.json b/package-lock.json index 0059f8a70..20ddb63e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13575,6 +13575,11 @@ "prop-types": "15.6.1" } }, + "react-native-audio": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/react-native-audio/-/react-native-audio-4.0.0.tgz", + "integrity": "sha512-wJWiXgJk8vFfZPSP4wC9NaCquv2wYOb+K76oPytYxJlRGplKrtD9JY0bIkXp7jbIIDubj3yebb5f8xpYhirIpg==" + }, "react-native-compat": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/react-native-compat/-/react-native-compat-1.0.0.tgz", @@ -13875,9 +13880,9 @@ } }, "react-navigation": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/react-navigation/-/react-navigation-1.2.1.tgz", - "integrity": "sha512-3TblYWMqu8nvihXHP5x0nxktLuAz1uLiGZaAX3YobX8aGIWxPdSrPS9PGAoSkSalMG3BtGEwUKYqH2RSY1pi3A==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/react-navigation/-/react-navigation-1.4.0.tgz", + "integrity": "sha512-jm9dYLARRq7T7AQ4MKNsl+ZAFnHwl+UIl7be4grBnPycJJrjXVmpng/GqTjeRfNHjzKhXO/mXWKuAoOW1SiO4A==", "requires": { "clamp": "1.0.1", "hoist-non-react-statics": "2.5.0", diff --git a/package.json b/package.json index 141801f34..da87961ac 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "react-native-action-button": "^2.8.3", "react-native-actionsheet": "^2.3.0", "react-native-animatable": "^1.2.4", + "react-native-audio": "^4.0.0", "react-native-easy-markdown": "git+https://github.com/diegolmello/react-native-easy-markdown.git", "react-native-fetch-blob": "^0.10.8", "react-native-image-picker": "^0.26.7", @@ -74,7 +75,6 @@ "redux-logger": "^3.0.6", "redux-saga": "^0.16.0", "regenerator-runtime": "^0.11.1", - "remote-redux-devtools": "^0.5.12", "simple-markdown": "^0.3.1", "snyk": "^1.61.1", "strip-ansi": "^4.0.0" @@ -98,7 +98,9 @@ "jest-cli": "^22.0.3", "react-dom": "^16.2.0", "react-test-renderer": "^16.2.0", - "reactotron-react-native": "^1.14.0" + "reactotron-react-native": "^1.14.0", + "reactotron-redux": "^1.13.0", + "reactotron-redux-saga": "^1.13.0" }, "jest": { "preset": "react-native", diff --git a/yarn.lock b/yarn.lock index ac07cac4a..95c937ead 100644 --- a/yarn.lock +++ b/yarn.lock @@ -568,15 +568,15 @@ after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" -ajv-keywords@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" +ajv-keywords@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" ajv-keywords@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.1.0.tgz#ac2b27939c543e95d2c06e7f7f5c27be4aa543be" -ajv@^4.7.0, ajv@^4.9.1: +ajv@^4.9.1: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" dependencies: @@ -592,7 +592,7 @@ ajv@^5.0.0: json-schema-traverse "^0.3.0" json-stable-stringify "^1.0.1" -ajv@^5.1.0, ajv@^5.3.0: +ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" dependencies: @@ -938,7 +938,7 @@ babel-core@^6.0.0, babel-core@^6.24.1, babel-core@^6.26.0, babel-core@^6.7.2: slash "^1.0.0" source-map "^0.5.6" -babel-eslint@^8.2.2: +babel-eslint@^8.0.2: version "8.2.2" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.2.2.tgz#1102273354c6f0b29b4ea28a65f97d122296b68b" dependencies: @@ -2566,10 +2566,6 @@ clone-stats@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" -clone@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" - clone@^1.0.0, clone@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" @@ -2698,10 +2694,6 @@ component-emitter@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3" -component-emitter@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.0.tgz#ccd113a86388d06482d03de3fc7df98526ba8efe" - component-emitter@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" @@ -3651,7 +3643,7 @@ eslint-module-utils@^2.1.1: debug "^2.6.8" pkg-dir "^1.0.0" -eslint-plugin-import@^2.9.0: +eslint-plugin-import@^2.8.0: version "2.9.0" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.9.0.tgz#26002efbfca5989b7288ac047508bd24f217b169" dependencies: @@ -3688,7 +3680,7 @@ eslint-plugin-react-native@^3.2.0: dependencies: eslint-plugin-react-native-globals "^0.1.1" -eslint-plugin-react@^7.7.0: +eslint-plugin-react@^7.5.1: version "7.7.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.7.0.tgz#f606c719dbd8a1a2b3d25c16299813878cca0160" dependencies: @@ -3712,9 +3704,9 @@ eslint-visitor-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" -eslint@^4.18.1: - version "4.18.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.18.1.tgz#b9138440cb1e98b2f44a0d578c6ecf8eae6150b0" +eslint@^4.12.0: + version "4.18.2" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.18.2.tgz#0f81267ad1012e7d2051e186a9004cc2267b8d45" dependencies: ajv "^5.3.0" babel-code-frame "^6.22.0" @@ -3751,7 +3743,7 @@ eslint@^4.18.1: semver "^5.3.0" strip-ansi "^4.0.0" strip-json-comments "~2.0.1" - table "^4.0.1" + table "4.0.2" text-table "~0.2.0" espree@^3.5.2: @@ -4322,10 +4314,6 @@ get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" -get-params@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/get-params/-/get-params-0.1.2.tgz#bae0dfaba588a0c60d7834c0d8dc2ff60eeef2fe" - get-stream@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" @@ -5565,10 +5553,6 @@ js-yaml@~3.7.0: argparse "^1.0.7" esprima "^2.6.0" -jsan@^3.1.0, jsan@^3.1.5: - version "3.1.9" - resolved "https://registry.yarnpkg.com/jsan/-/jsan-3.1.9.tgz#2705676c1058f0a7d9ac266ad036a5769cfa7c96" - jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -5776,10 +5760,6 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -linked-list@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/linked-list/-/linked-list-0.1.0.tgz#798b0ff97d1b92a4fd08480f55aea4e9d49d37bf" - load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -5906,14 +5886,6 @@ lodash.escape@^3.0.0: dependencies: lodash._root "^3.0.0" -lodash.every@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.every/-/lodash.every-4.6.0.tgz#eb89984bebc4364279bb3aefbbd1ca19bfa6c6a7" - -lodash.filter@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" - lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -5922,22 +5894,10 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - -lodash.isfunction@^3.0.8: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" - lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - lodash.keys@^3.0.0, lodash.keys@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -5946,10 +5906,6 @@ lodash.keys@^3.0.0, lodash.keys@^3.1.2: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" -lodash.keys@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" - lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -6011,10 +5967,6 @@ lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" -lodash.union@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" - lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -7694,6 +7646,10 @@ react-native-animatable@^1.2.4: dependencies: prop-types "^15.5.10" +react-native-audio@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/react-native-audio/-/react-native-audio-4.0.0.tgz#e263c7711150de37b013741eeb2ede37941d3011" + react-native-compat@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/react-native-compat/-/react-native-compat-1.0.0.tgz#491dbd8a0105ac061b8d0d926463ce6a3dff33bc" @@ -7743,23 +7699,23 @@ react-native-iphone-x-helper@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.0.2.tgz#7dbca530930f7c1ce8633cc8fd13ba94102992e1" -react-native-keyboard-aware-scroll-view@^0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/react-native-keyboard-aware-scroll-view/-/react-native-keyboard-aware-scroll-view-0.4.3.tgz#206e197730902ce01d026910ff8409a2a363e1ac" +react-native-keyboard-aware-scroll-view@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/react-native-keyboard-aware-scroll-view/-/react-native-keyboard-aware-scroll-view-0.4.4.tgz#116d08975a84482e4ee64dd0b526a8f04138b0a7" dependencies: prop-types "^15.6.0" react-native-iphone-x-helper "^1.0.1" "react-native-keyboard-input@git+https://github.com/RocketChat/react-native-keyboard-input.git": - version "5.0.0" - resolved "git+https://github.com/RocketChat/react-native-keyboard-input.git#38273b0513f69a5e6e0719f65a675f9f2b5ee883" + version "5.1.1" + resolved "git+https://github.com/RocketChat/react-native-keyboard-input.git#5bc63ae8b80a4ca5a3714a81dd058ee6bbe00a30" dependencies: lodash "^4.17.4" react-native-keyboard-tracking-view "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git" "react-native-keyboard-tracking-view@git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git": - version "5.1.1" - resolved "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git#3a4084f0a1063e23ae6435facdf1f79152558d15" + version "5.3.2" + resolved "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git#151979f82a5aba8755993e7001ba608c135de6b7" react-native-loading-spinner-overlay@^0.5.2: version "0.5.2" @@ -7879,9 +7835,9 @@ react-native-zeroconf@^0.8.3: dependencies: events "^1.1.0" -react-native@0.54.0-rc.4: - version "0.54.0-rc.4" - resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.54.0-rc.4.tgz#d678ac7ae69345060c3dea639fd3665adb4016fd" +react-native@0.54: + version "0.54.0" + resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.54.0.tgz#1582cc2b2a639d46e39c44bcc50e051d0061820a" dependencies: absolute-path "^0.0.0" art "^0.10.0" @@ -7942,9 +7898,9 @@ react-native@0.54.0-rc.4: xmldoc "^0.4.0" yargs "^9.0.0" -react-navigation@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/react-navigation/-/react-navigation-1.2.1.tgz#06cb2c97eb1b2e20bdb4ff7aee1acfa218a1561b" +react-navigation@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/react-navigation/-/react-navigation-1.4.0.tgz#2e1ec70a5d37af78d4b2cab588caf179b2b16859" dependencies: clamp "^1.0.1" hoist-non-react-statics "^2.2.0" @@ -8056,6 +8012,25 @@ reactotron-react-native@^1.14.0: rn-host-detect "^1.1.3" socket.io-client "~1.7.3" +reactotron-redux-saga@^1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/reactotron-redux-saga/-/reactotron-redux-saga-1.13.0.tgz#f6bb75c16806f2cc09be97e4973743f41a157a18" + dependencies: + ramda "^0.24.1" + ramdasauce "^2.0.0" + reactotron-core-client "^1.13.0" + redux "^3.7.1" + redux-saga "^0.15.3" + +reactotron-redux@^1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/reactotron-redux/-/reactotron-redux-1.13.0.tgz#20f8ce98cbb5e310e041dc85fc4dc4f904aa5dcc" + dependencies: + ramda "^0.24.1" + ramdasauce "^2.0.0" + reactotron-core-client "^1.13.0" + redux "^3.7.1" + read-all-stream@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" @@ -8209,13 +8184,6 @@ reduce-function-call@^1.0.1: dependencies: balanced-match "^0.4.2" -redux-devtools-instrument@^1.3.3: - version "1.8.2" - resolved "https://registry.yarnpkg.com/redux-devtools-instrument/-/redux-devtools-instrument-1.8.2.tgz#5e91cfe402e790dae3fd2f0d235f7b7d84b09ffe" - dependencies: - lodash "^4.2.0" - symbol-observable "^1.0.2" - redux-enhancer-react-native-appstate@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/redux-enhancer-react-native-appstate/-/redux-enhancer-react-native-appstate-0.3.0.tgz#fb917093758ce09f432cd0b2b563d1361e8f576f" @@ -8233,11 +8201,15 @@ redux-logger@^3.0.6: dependencies: deep-diff "^0.3.5" +redux-saga@^0.15.3: + version "0.15.6" + resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.15.6.tgz#8638dc522de6c6c0a496fe8b2b5466287ac2dc4d" + redux-saga@^0.16.0: version "0.16.0" resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.16.0.tgz#0a231db0a1489301dd980f6f2f88d8ced418f724" -redux@^3.7.2: +redux@^3.7.1, redux@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" dependencies: @@ -8326,33 +8298,6 @@ relateurl@0.2.x: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" -remote-redux-devtools@^0.5.12: - version "0.5.12" - resolved "https://registry.yarnpkg.com/remote-redux-devtools/-/remote-redux-devtools-0.5.12.tgz#42cb95dfa9e54c1d9671317c5e7bba41e68caec2" - dependencies: - jsan "^3.1.5" - querystring "^0.2.0" - redux-devtools-instrument "^1.3.3" - remotedev-utils "^0.1.1" - rn-host-detect "^1.0.1" - socketcluster-client "^5.3.1" - -remotedev-serialize@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/remotedev-serialize/-/remotedev-serialize-0.1.0.tgz#074768e98cb7aa806f45994eeb0c8af95120ee32" - dependencies: - jsan "^3.1.0" - -remotedev-utils@^0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/remotedev-utils/-/remotedev-utils-0.1.4.tgz#643700819a943678073c75eb185e81d96620b348" - dependencies: - get-params "^0.1.2" - jsan "^3.1.5" - lodash "^4.0.0" - remotedev-serialize "^0.1.0" - shortid "^2.2.6" - remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -8555,7 +8500,7 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^2.0.0" inherits "^2.0.1" -rn-host-detect@^1.0.1, rn-host-detect@^1.1.3: +rn-host-detect@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.1.3.tgz#242d76e2fa485c48d751416e65b7cce596969e91" @@ -8615,26 +8560,6 @@ sax@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/sax/-/sax-1.1.6.tgz#5d616be8a5e607d54e114afae55b7eaf2fcc3240" -sc-channel@~1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/sc-channel/-/sc-channel-1.0.6.tgz#b38bd47a993e78290fbc53467867f6b2a0a08639" - dependencies: - sc-emitter "1.x.x" - -sc-emitter@1.x.x, sc-emitter@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/sc-emitter/-/sc-emitter-1.1.0.tgz#ef119d4222f4c64f887b486964ef11116cdd0e75" - dependencies: - component-emitter "1.2.0" - -sc-errors@~1.3.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/sc-errors/-/sc-errors-1.3.3.tgz#c00bc4c766a970cc8d5937d08cd58e931d7dae05" - -sc-formatter@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/sc-formatter/-/sc-formatter-3.0.0.tgz#c91b1fe56c260abd5a6a2e6af98c724bc7998a38" - schema-utils@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" @@ -8802,10 +8727,6 @@ shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" -shortid@^2.2.6: - version "2.2.8" - resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131" - signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -8836,9 +8757,11 @@ slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" -slice-ansi@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" +slice-ansi@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + dependencies: + is-fullwidth-code-point "^2.0.0" slide@^1.1.5: version "1.1.6" @@ -9038,20 +8961,6 @@ socket.io-parser@2.3.1: isarray "0.0.1" json3 "3.3.2" -socketcluster-client@^5.3.1: - version "5.5.2" - resolved "https://registry.yarnpkg.com/socketcluster-client/-/socketcluster-client-5.5.2.tgz#9d4369e0e722ff7e55e5422c2d44f5afe1aff128" - dependencies: - base-64 "0.1.0" - clone "2.1.1" - linked-list "0.1.0" - querystring "0.2.0" - sc-channel "~1.0.6" - sc-emitter "~1.1.0" - sc-errors "~1.3.0" - sc-formatter "~3.0.0" - ws "3.0.0" - sort-keys@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" @@ -9317,7 +9226,7 @@ svgo@^0.7.0: sax "~1.2.1" whet.extend "~0.9.9" -symbol-observable@^1.0.2, symbol-observable@^1.0.3: +symbol-observable@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" @@ -9333,16 +9242,16 @@ sync-request@^3.0.1: http-response-object "^1.0.1" then-request "^2.0.1" -table@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/table/-/table-4.0.1.tgz#a8116c133fac2c61f4a420ab6cdf5c4d61f0e435" +table@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" dependencies: - ajv "^4.7.0" - ajv-keywords "^1.0.0" - chalk "^1.1.1" - lodash "^4.0.0" - slice-ansi "0.0.4" - string-width "^2.0.0" + ajv "^5.2.3" + ajv-keywords "^2.1.0" + chalk "^2.1.0" + lodash "^4.17.4" + slice-ansi "1.0.0" + string-width "^2.1.1" tapable@^0.2.7: version "0.2.8" @@ -9986,20 +9895,6 @@ which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0: dependencies: isexe "^2.0.0" -why-did-you-update@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/why-did-you-update/-/why-did-you-update-0.1.1.tgz#c73d361511fecd899056e9954ca9b1ab760d3097" - dependencies: - lodash.every "^4.6.0" - lodash.filter "^4.6.0" - lodash.isequal "^4.5.0" - lodash.isfunction "^3.0.8" - lodash.isstring "^4.0.1" - lodash.keys "^4.2.0" - lodash.pick "^4.4.0" - lodash.some "^4.6.0" - lodash.union "^4.6.0" - wide-align@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" @@ -10086,13 +9981,6 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" -ws@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.0.0.tgz#98ddb00056c8390cb751e7788788497f99103b6c" - dependencies: - safe-buffer "~5.0.1" - ultron "~1.1.0" - ws@^1.1.0: version "1.1.4" resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.4.tgz#57f40d036832e5f5055662a397c4de76ed66bf61"