Commit 6d0e8e50 authored by Diego Mello's avatar Diego Mello Committed by Guilherme Gazzo

Bug fixes (#261)

* Layout fixes

* RoomsListView's SafeAreaView

* Unhandled promise rejection fix

* Prevent navigation from opening scenes twice

* Create channel fixes
parent 0e8b9fe8
import React from 'react';
import { View, StyleSheet, Platform } from 'react-native';
import PropTypes from 'prop-types';
import { SafeAreaView } from 'react-navigation';
import SafeAreaView from 'react-native-safe-area-view';
let platformContainerStyles;
if (Platform.OS === 'ios') {
......@@ -21,15 +21,16 @@ if (Platform.OS === 'ios') {
};
}
const appBarHeight = Platform.OS === 'ios' ? 44 : 56;
const height = Platform.OS === 'ios' ? 44 : 56;
const backgroundColor = Platform.OS === 'ios' ? '#F7F7F7' : '#FFF';
const styles = StyleSheet.create({
container: {
backgroundColor: Platform.OS === 'ios' ? '#F7F7F7' : '#FFF',
height: appBarHeight,
backgroundColor,
...platformContainerStyles
},
appBar: {
flex: 1
height,
backgroundColor
}
});
......@@ -40,7 +41,7 @@ export default class Header extends React.PureComponent {
render() {
return (
<SafeAreaView style={styles.container}>
<SafeAreaView forceInset={{ bottom: 'never' }} style={styles.container}>
<View style={styles.appBar}>
{this.props.subview}
</View>
......
import React from 'react';
import PropTypes from 'prop-types';
import { View, TextInput, SafeAreaView, FlatList, Text, TouchableOpacity } from 'react-native';
import { View, TextInput, FlatList, Text, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
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 { isIphoneX } from 'react-native-iphone-x-helper';
import { userTyping, layoutAnimation } from '../../actions/room';
import RocketChat from '../../lib/rocketchat';
......@@ -479,45 +480,44 @@ export default class MessageBox extends React.PureComponent {
return (
[
this.renderMentions(),
<SafeAreaView
key='messagebox'
style={[styles.textBox, (this.props.editing ? styles.editing : null)]}
>
<View style={styles.textArea}>
{this.leftButtons}
<TextInput
ref={component => this.component = component}
style={styles.textBoxInput}
returnKeyType='default'
keyboardType='twitter'
blurOnSubmit={false}
placeholder='New Message'
onChangeText={text => this.onChangeText(text)}
value={this.state.text}
underlineColorAndroid='transparent'
defaultValue=''
multiline
placeholderTextColor='#9EA2A8'
/>
{this.rightButtons}
</View>
</SafeAreaView>
<View key='messagebox' style={[styles.textArea, this.props.editing && styles.editing]}>
{this.leftButtons}
<TextInput
ref={component => this.component = component}
style={styles.textBoxInput}
returnKeyType='default'
keyboardType='twitter'
blurOnSubmit={false}
placeholder='New Message'
onChangeText={text => this.onChangeText(text)}
value={this.state.text}
underlineColorAndroid='transparent'
defaultValue=''
multiline
placeholderTextColor='#9EA2A8'
/>
{this.rightButtons}
</View>
]
);
}
render() {
return (
<KeyboardAccessoryView
renderContent={() => this.renderContent()}
kbInputRef={this.component}
kbComponent={this.state.showEmojiKeyboard ? 'EmojiKeyboard' : null}
onKeyboardResigned={() => this.onKeyboardResigned()}
onItemSelected={this._onEmojiSelected}
trackInteractive
// revealKeyboardInteractive
requiresSameParentToManageScrollView
/>
[
<KeyboardAccessoryView
key='input'
renderContent={() => this.renderContent()}
kbInputRef={this.component}
kbComponent={this.state.showEmojiKeyboard ? 'EmojiKeyboard' : null}
onKeyboardResigned={() => this.onKeyboardResigned()}
onItemSelected={this._onEmojiSelected}
trackInteractive
// revealKeyboardInteractive
requiresSameParentToManageScrollView
/>,
isIphoneX() ? <View key='iphonex-area' style={styles.iphoneXArea} /> : null
]
);
}
}
......@@ -11,14 +11,11 @@ export default StyleSheet.create({
borderTopColor: '#D8D8D8',
zIndex: 2
},
safeAreaView: {
flexDirection: 'row',
alignItems: 'center'
},
textArea: {
flexDirection: 'row',
alignItems: 'center',
flexGrow: 0
flexGrow: 0,
backgroundColor: '#fff'
},
textBoxInput: {
textAlignVertical: 'center',
......@@ -41,29 +38,6 @@ export default StyleSheet.create({
paddingHorizontal: 21,
flex: 0
},
actionRow: {
flexDirection: 'row',
alignItems: 'center',
alignContent: 'center'
},
actionContent: {
borderBottomWidth: 1,
borderBottomColor: '#ECECEC',
borderTopWidth: 1,
borderTopColor: '#ECECEC',
backgroundColor: '#F7F8FA'
},
actionTitle: {
flex: 1,
fontSize: 17,
padding: 14,
textAlign: 'right',
borderBottomWidth: 1,
borderBottomColor: '#ECECEC',
color: '#2F343D'
},
mentionList: {
maxHeight: MENTION_HEIGHT * 4,
borderTopColor: '#ECECEC',
......@@ -79,12 +53,6 @@ export default StyleSheet.create({
flexDirection: 'row',
alignItems: 'center'
},
emojiContainer: {
height: 200,
borderTopColor: '#ECECEC',
borderTopWidth: 1,
backgroundColor: '#fff'
},
mentionItemCustomEmoji: {
margin: 8,
width: 30,
......@@ -105,5 +73,13 @@ export default StyleSheet.create({
flex: 1,
borderTopColor: '#ECECEC',
borderTopWidth: 1
},
iphoneXArea: {
height: 50,
backgroundColor: '#fff',
position: 'absolute',
bottom: 0,
left: 0,
right: 0
}
});
......@@ -67,7 +67,7 @@ export default class Sidebar extends Component {
}
onItemPress = ({ route, focused }) => {
this.props.navigation.navigate('DrawerClose');
this.props.navigation.navigate({ key: 'DrawerClose', routeName: 'DrawerClose' });
if (!focused) {
this.props.navigation.navigate(route.routeName, undefined);
}
......@@ -75,7 +75,7 @@ export default class Sidebar extends Component {
onPressItem = (item) => {
this.props.selectServer(item.id);
this.props.navigation.navigate('DrawerClose');
this.props.navigation.navigate({ key: 'DrawerClose', routeName: 'DrawerClose' });
}
getState = () => ({
......
......@@ -10,7 +10,7 @@ export function setNavigator(nav) {
export function navigate(routeName, params) {
if (config.navigator && routeName) {
const action = NavigationActions.navigate({ routeName, params });
const action = NavigationActions.navigate({ key: routeName, routeName, params });
config.navigator.dispatch(action);
}
}
......@@ -26,7 +26,7 @@ export function goRoomsList() {
if (config.navigator) {
const action = NavigationActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'RoomsList' })]
actions: [NavigationActions.navigate({ key: 'RoomsList', routeName: 'RoomsList' })]
});
config.navigator.dispatch(action);
}
......@@ -44,8 +44,8 @@ export function goRoom({ rid, name }, counter = 0) {
const action = NavigationActions.reset({
index: 1,
actions: [
NavigationActions.navigate({ routeName: 'RoomsList' }),
NavigationActions.navigate({ routeName: 'Room', params: { room: { rid, name }, rid, name } })
NavigationActions.navigate({ key: 'RoomsList', routeName: 'RoomsList' }),
NavigationActions.navigate({ key: `Room-${ rid }`, routeName: 'Room', params: { room: { rid, name }, rid, name } })
]
});
......
......@@ -21,7 +21,7 @@ const PublicRoutes = StackNavigator(
title: 'Servers',
headerRight: (
<TouchableOpacity
onPress={() => navigation.navigate('AddServer')}
onPress={() => navigation.navigate({ key: 'AddServer', routeName: 'AddServer' })}
style={{ width: 50, alignItems: 'center' }}
accessibilityLabel='Add server'
accessibilityTraits='button'
......
......@@ -3,12 +3,13 @@ import { select, put, call, take, takeLatest } from 'redux-saga/effects';
import { CREATE_CHANNEL, LOGIN } from '../actions/actionsTypes';
import { createChannelSuccess, createChannelFailure } from '../actions/createChannel';
import RocketChat from '../lib/rocketchat';
import { goRoom } from '../containers/routes/NavigationService';
const create = function* create(data) {
return yield RocketChat.createChannel(data);
};
const get = function* get({ data }) {
const handleRequest = function* handleRequest({ data }) {
try {
yield delay(1000);
const auth = yield select(state => state.login.isAuthenticated);
......@@ -16,14 +17,15 @@ const get = function* get({ data }) {
yield take(LOGIN.SUCCESS);
}
const result = yield call(create, data);
const { rid, name } = result;
goRoom({ rid, name });
yield put(createChannelSuccess(result));
} catch (err) {
yield put(createChannelFailure(err));
}
};
const getData = function* getData() {
yield takeLatest(CREATE_CHANNEL.REQUEST, get);
const root = function* root() {
yield takeLatest(CREATE_CHANNEL.REQUEST, handleRequest);
};
export default getData;
export default root;
......@@ -63,7 +63,9 @@ const saveToken = function* saveToken() {
yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token);
yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user));
const token = yield AsyncStorage.getItem('pushId');
yield token && RocketChat.registerPushToken(user.user.id, token);
if (token) {
RocketChat.registerPushToken(user.user.id, token);
}
Answers.logLogin('Email', true, { server });
};
......
......@@ -2,6 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { TextInput, View, Text, Switch, TouchableOpacity, SafeAreaView } from 'react-native';
import Spinner from 'react-native-loading-spinner-overlay';
import LoggedView from './View';
import { createChannelRequest } from '../actions/createChannel';
......@@ -10,11 +11,11 @@ import KeyboardView from '../presentation/KeyboardView';
@connect(
state => ({
result: state.createChannel,
createChannel: state.createChannel,
users: state.createChannel.users
}),
dispatch => ({
createChannel: data => dispatch(createChannelRequest(data))
create: data => dispatch(createChannelRequest(data))
})
)
export default class CreateChannelView extends LoggedView {
......@@ -22,34 +23,22 @@ export default class CreateChannelView extends LoggedView {
title: 'Create a New Channel'
});
static propTypes = {
createChannel: PropTypes.func.isRequired,
result: PropTypes.object.isRequired,
create: PropTypes.func.isRequired,
createChannel: PropTypes.object.isRequired,
users: PropTypes.array.isRequired,
navigation: PropTypes.object.isRequired
};
constructor(props) {
super('CreateChannelView', props);
this.default = {
this.state = {
channelName: '',
type: true
};
this.state = this.default;
}
componentDidUpdate() {
if (!this.adding) {
return;
}
if (this.props.result.result && !this.props.result.failure) {
this.props.navigation.navigate('Room', { room: this.props.result.result });
this.adding = false;
}
}
submit() {
this.adding = true;
if (!this.state.channelName.trim() || this.props.result.isFetching) {
if (!this.state.channelName.trim() || this.props.createChannel.isFetching) {
return;
}
const { channelName, type = true } = this.state;
......@@ -59,19 +48,21 @@ export default class CreateChannelView extends LoggedView {
users = users.map(user => user.name);
// create channel
this.props.createChannel({ name: channelName, users, type });
this.props.create({ name: channelName, users, type });
}
renderChannelNameError() {
if (
!this.props.result.failure ||
this.props.result.error.error !== 'error-duplicate-channel-name'
!this.props.createChannel.failure ||
this.props.createChannel.error.error !== 'error-duplicate-channel-name'
) {
return null;
}
return (
<Text style={[styles.label_white, styles.label_error]}>{this.props.result.error.reason}</Text>
<Text style={[styles.label_white, styles.label_error]}>
{this.props.createChannel.error.reason}
</Text>
);
}
......@@ -106,7 +97,6 @@ export default class CreateChannelView extends LoggedView {
returnKeyType='done'
autoCapitalize='none'
autoFocus
// onSubmitEditing={() => this.textInput.focus()}
placeholder='Type the channel name here'
/>
{this.renderChannelNameError()}
......@@ -130,18 +120,16 @@ export default class CreateChannelView extends LoggedView {
</Text>
<TouchableOpacity
onPress={() => this.submit()}
style={[
styles.buttonContainer_white,
this.state.channelName.length === 0 || this.props.result.isFetching
? styles.disabledButton
: styles.enabledButton
]}
style={[styles.buttonContainer_white, styles.enabledButton]}
>
<Text style={styles.button_white}>
{this.props.result.isFetching ? 'LOADING' : 'CREATE'}!
</Text>
<Text style={styles.button_white}>CREATE</Text>
</TouchableOpacity>
</SafeAreaView>
<Spinner
visible={this.props.createChannel.isFetching}
textContent='Loading...'
textStyle={{ color: '#FFF' }}
/>
</KeyboardView>
);
}
......
......@@ -102,7 +102,7 @@ export default class ListServerView extends LoggedView {
!this.props.login.token &&
!this.redirected) {
this.redirected = true;
this.props.navigation.navigate('Login');
this.props.navigation.navigate({ key: 'Login', routeName: 'Login' });
} else if (!this.props.connected) {
this.redirected = false;
}
......
......@@ -175,19 +175,19 @@ export default class LoginView extends React.Component {
}
register = () => {
this.props.navigation.navigate('Register');
this.props.navigation.navigate({ key: 'Register', routeName: 'Register' });
}
termsService = () => {
this.props.navigation.navigate('TermsService');
this.props.navigation.navigate({ key: 'TermsService', routeName: 'TermsService' });
}
privacyPolicy = () => {
this.props.navigation.navigate('PrivacyPolicy');
this.props.navigation.navigate({ key: 'PrivacyPolicy', routeName: 'PrivacyPolicy' });
}
forgotPassword = () => {
this.props.navigation.navigate('ForgotPassword');
this.props.navigation.navigate({ key: 'ForgotPassword', routeName: 'ForgotPassword' });
}
closeOAuth = () => {
......
......@@ -54,7 +54,7 @@ export default class RoomActionsView extends LoggedView {
onPressTouchable = (item) => {
if (item.route) {
return this.props.navigation.navigate(item.route, item.params);
return this.props.navigation.navigate({ key: item.route, routeName: item.route, params: item.params });
}
if (item.event) {
return item.event();
......
......@@ -44,7 +44,7 @@ export default class RoomInfoView extends LoggedView {
return {
headerRight: (
<Touch
onPress={() => navigation.navigate('RoomInfoEdit', { rid: navigation.state.params.rid })}
onPress={() => navigation.navigate({ key: 'RoomInfoEdit', routeName: 'RoomInfoEdit', params: { rid: navigation.state.params.rid } })}
underlayColor='#ffffff'
activeOpacity={0.5}
accessibilityLabel='edit'
......
......@@ -91,7 +91,7 @@ export default class RoomHeaderView extends React.PureComponent {
style={styles.titleContainer}
accessibilityLabel={accessibilityLabel}
accessibilityTraits='header'
onPress={() => this.props.navigation.navigate('RoomInfo', { rid: this.rid })}
onPress={() => this.props.navigation.navigate({ key: 'RoomInfo', routeName: 'RoomInfo', params: { rid: this.rid } })}
>
{this.isDirect() ?
<View style={[styles.status, { backgroundColor: STATUS_COLORS[this.getUserStatus()] }]} />
......@@ -135,7 +135,7 @@ export default class RoomHeaderView extends React.PureComponent {
</Touch>
<TouchableOpacity
style={styles.headerButton}
onPress={() => this.props.navigation.navigate('RoomActions', { rid: this.room[0].rid })}
onPress={() => this.props.navigation.navigate({ key: 'RoomActions', routeName: 'RoomActions', params: { rid: this.room[0].rid } })}
accessibilityLabel='Room actions'
accessibilityTraits='button'
>
......
......@@ -67,7 +67,7 @@ export class List extends React.Component {
render() {
return (<ListView
enableEmptySections
style={styles.list}
style={[styles.list]}
data={this.data}
onEndReachedThreshold={0.5}
renderFooter={this.props.renderFooter}
......
......@@ -73,7 +73,7 @@ export default class RoomView extends LoggedView {
this.state = {
loaded: true,
joined: typeof props.rid === 'undefined',
room: this.rooms[0]
room: {}
};
this.onReactionPress = this.onReactionPress.bind(this);
}
......@@ -180,7 +180,7 @@ export default class RoomView extends LoggedView {
</View>
);
}
return <MessageBox ref={box => (this.box = box)} onSubmit={this.sendMessage} rid={this.rid} />;
return <MessageBox onSubmit={this.sendMessage} rid={this.rid} />;
};
renderHeader = () => {
......
......@@ -77,7 +77,7 @@ export default class RoomsListHeaderView extends React.PureComponent {
}
createChannel() {
this.props.navigation.navigate('SelectUsers');
this.props.navigation.navigate({ key: 'SelectUsers', routeName: 'SelectUsers' });
}
renderLeft() {
......@@ -89,7 +89,7 @@ export default class RoomsListHeaderView extends React.PureComponent {
<View style={styles.left} accessible accessibilityLabel="Server's list" accessibilityTraits='button'>
<TouchableOpacity
style={styles.headerButton}
onPress={() => this.props.navigation.navigate('DrawerOpen')}
onPress={() => this.props.navigation.navigate({ key: 'DrawerOpen', routeName: 'DrawerOpen' })}
>
<CachedImage
style={styles.serverImage}
......@@ -159,7 +159,8 @@ export default class RoomsListHeaderView extends React.PureComponent {
size={24}
backgroundColor='transparent'
/>
</TouchableOpacity> : null}
</TouchableOpacity> : null
}
</View>
);
}
......
......@@ -3,7 +3,7 @@ import { ListView } from 'realm/react-native';
import React from 'react';
import PropTypes from 'prop-types';
import Icon from 'react-native-vector-icons/Ionicons';
import { Platform, View, TextInput, SafeAreaView, FlatList } from 'react-native';
import { Platform, View, TextInput, FlatList } from 'react-native';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import * as server from '../../actions/connect';
......@@ -135,7 +135,7 @@ export default class RoomsListView extends React.Component {
_onPressItem = async(item = {}) => {
// if user is using the search we need first to join/create room
if (!item.search) {
return this.props.navigation.navigate({ routeName: 'Room', params: { room: item, ...item } });
return this.props.navigation.navigate({ key: `Room-${ item._id }`, routeName: 'Room', params: { room: item, ...item } });
}
if (item.t === 'd') {
const sub = await RocketChat.createDirectMessageAndWait(item.username);
......@@ -145,7 +145,7 @@ export default class RoomsListView extends React.Component {
}
_createChannel() {
this.props.navigation.navigate('SelectUsers');
this.props.navigation.navigate({ key: 'SelectUsers', routeName: 'SelectUsers' });
}
_keyExtractor(item) {
......@@ -210,9 +210,7 @@ export default class RoomsListView extends React.Component {
render = () => (
<View style={styles.container}>
<Banner />
<SafeAreaView style={styles.safeAreaView}>
{this.renderList()}
{Platform.OS === 'android' && this.renderCreateButtons()}