[NEW] Create channel layout (#420)

* RoomsListView layout

* Rooms list layout

* Sort component

* Header icons

* Default header colors

* Add server dropdown

* Close sort dropdown if server dropdown will open

* UserItem

* Room type icon

* Search working

* Tests updated

* Android layout

* Using realm queries instead of array iterates

* Animation duration

* Fixed render bug

* - NewMessageView
- backButtonTitle always empty
- SearchBox created

* New create channel layout

* Search refactored

* loginSuccess dismiss modal

* Tests working
This commit is contained in:
Diego Mello 2018-08-31 15:13:30 -03:00 committed by GitHub
parent dc6d60b28e
commit de1a63c815
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 893 additions and 470 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -84,7 +84,9 @@ export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']);
export const SERVER = createRequestTypes('SERVER', [
...defaultTypes,
'SELECT_SUCCESS',
'SELECT_REQUEST'
'SELECT_REQUEST',
'INIT_ADD',
'FINISH_ADD'
]);
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT', 'DISCONNECT_BY_USER']);
export const LOGOUT = 'LOGOUT'; // logout is always success

View File

@ -33,3 +33,15 @@ export function serverFailure(err) {
err
};
}
export function serverInitAdd() {
return {
type: SERVER.INIT_ADD
};
}
export function serverFinishAdd() {
return {
type: SERVER.FINISH_ADD
};
}

View File

@ -1,8 +1,8 @@
export const AVATAR_COLORS = ['#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B'];
export const ESLINT_FIX = null;
export const COLOR_DANGER = '#f5455c';
export const COLOR_BUTTON_PRIMARY = '#2D6AEA';
export const COLOR_TEXT = '#292E35';
export const COLOR_SEPARATOR = '#CBCED1';
export const STATUS_COLORS = {
online: '#2de0a5',
busy: COLOR_DANGER,

View File

@ -0,0 +1,62 @@
import React from 'react';
import { View, StyleSheet, Image, TextInput, Platform } from 'react-native';
import PropTypes from 'prop-types';
import I18n from '../i18n';
const styles = StyleSheet.create({
container: {
backgroundColor: Platform.OS === 'ios' ? '#F7F8FA' : '#54585E'
},
searchBox: {
alignItems: 'center',
backgroundColor: '#E1E5E8',
borderRadius: 10,
color: '#8E8E93',
flexDirection: 'row',
fontSize: 17,
height: 36,
margin: 16,
marginVertical: 10,
paddingHorizontal: 10
},
icon: {
width: 14,
height: 14
},
input: {
color: '#8E8E93',
flex: 1,
fontSize: 17,
marginLeft: 8,
paddingTop: 0,
paddingBottom: 0
}
});
const SearchBox = ({ onChangeText, testID }) => (
<View style={styles.container}>
<View style={styles.searchBox}>
<Image source={{ uri: 'textinput_search' }} style={styles.icon} />
<TextInput
autoCapitalize='none'
autoCorrect={false}
blurOnSubmit
clearButtonMode='while-editing'
placeholder={I18n.t('Search')}
returnKeyType='search'
style={styles.input}
testID={testID}
underlineColorAndroid='transparent'
onChangeText={onChangeText}
/>
</View>
</View>
);
SearchBox.propTypes = {
onChangeText: PropTypes.func.isRequired,
testID: PropTypes.string
};
export default SearchBox;

View File

@ -236,6 +236,7 @@ export default class Sidebar extends Component {
setTimeout(() => {
NavigationActions.push({
screen: 'NewServerView',
backButtonTitle: '',
passProps: {
server: item.id
},

View File

@ -1,6 +1,7 @@
export default {
'1_online_member': '1 online member',
'1_person_reacted': '1 person reacted',
'1_user': '1 user',
'error-action-not-allowed': '{{action}} is not allowed',
'error-application-not-found': 'Application not found',
'error-archived-duplicate-name': 'There\'s an archived channel with name {{room_name}}',
@ -110,6 +111,7 @@ export default {
Cancel_recording: 'Cancel recording',
Cancel: 'Cancel',
changing_avatar: 'changing avatar',
creating_channel: 'creating channel',
Channel_Name: 'Channel Name',
Channels: 'Channels',
Chats: 'Chats',
@ -160,6 +162,7 @@ export default {
Has_left_the_channel: 'Has left the channel',
I_have_an_account: 'I have an account',
Invisible: 'Invisible',
Invite: 'Invite',
is_a_valid_RocketChat_instance: 'is a valid Rocket.Chat instance',
is_not_a_valid_RocketChat_instance: 'is not a valid Rocket.Chat instance',
is_typing: 'is typing',
@ -188,13 +191,15 @@ export default {
muted: 'muted',
My_servers: 'My servers',
N_online_members: '{{n}} online members',
N_person_reacted: '{{n}} people reacted',
N_people_reacted: '{{n}} people reacted',
N_users: '{{n}} users',
name: 'name',
Name: 'Name',
New_in_RocketChat_question_mark: 'New in Rocket.Chat?',
New_Message: 'New Message',
New_Password: 'New Password',
New_Server: 'New Server',
Next: 'Next',
No_files: 'No files',
No_mentioned_messages: 'No mentioned messages',
No_pinned_messages: 'No pinned messages',

View File

@ -528,6 +528,56 @@ const RocketChat = {
return _sendMessageCall(JSON.parse(JSON.stringify(message)));
},
async search({ text, filterUsers = true, filterRooms = true }) {
const searchText = text.trim();
if (searchText === '') {
delete this.oldPromise;
return [];
}
let data = database.objects('subscriptions').filtered('name CONTAINS[c] $0', searchText);
if (filterUsers && !filterRooms) {
data = data.filtered('t = $0', 'd');
} else if (!filterUsers && filterRooms) {
data = data.filtered('t != $0', 'd');
}
data = data.slice(0, 7);
const usernames = data.map(sub => sub.name);
try {
if (data.length < 7) {
if (this.oldPromise) {
this.oldPromise('cancel');
}
const { users, rooms } = await Promise.race([
RocketChat.spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }),
new Promise((resolve, reject) => this.oldPromise = reject)
]);
data = data.concat(users.map(user => ({
...user,
rid: user.username,
name: user.username,
t: 'd',
search: true
})), rooms.map(room => ({
rid: room._id,
...room,
search: true
})));
delete this.oldPromise;
}
return data;
} catch (e) {
console.warn(e);
return [];
}
},
spotlight(search, usernames, type) {
return call('spotlight', search, usernames, type);
},

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Text, View, StyleSheet, Platform } from 'react-native';
import { Text, View, StyleSheet, Platform, ViewPropTypes, Image } from 'react-native';
import PropTypes from 'prop-types';
import Avatar from '../containers/Avatar';
@ -7,7 +7,8 @@ import Touch from '../utils/touch';
const styles = StyleSheet.create({
button: {
height: 54
height: 54,
backgroundColor: '#fff'
},
container: {
flexDirection: 'row'
@ -24,24 +25,33 @@ const styles = StyleSheet.create({
fontSize: 18,
color: '#0C0D0F',
marginTop: Platform.OS === 'ios' ? 6 : 3,
marginBottom: 1
marginBottom: 1,
textAlign: 'left'
},
username: {
fontSize: 14,
color: '#9EA2A8'
},
icon: {
width: 20,
height: 20,
marginHorizontal: 15,
resizeMode: 'contain',
alignSelf: 'center'
}
});
const UserItem = ({
name, username, onPress, testID, onLongPress
name, username, onPress, testID, onLongPress, style, icon
}) => (
<Touch onPress={onPress} onLongPress={onLongPress} style={styles.button} testID={testID}>
<View style={styles.container}>
<View style={[styles.container, style]}>
<Avatar text={username} size={30} type='d' style={styles.avatar} />
<View style={styles.textContainer}>
<Text style={styles.name}>{name}</Text>
<Text style={styles.username}>@{username}</Text>
</View>
{icon ? <Image source={{ uri: icon }} style={styles.icon} /> : null}
</View>
</Touch>
);
@ -51,7 +61,9 @@ UserItem.propTypes = {
username: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
testID: PropTypes.string.isRequired,
onLongPress: PropTypes.func
onLongPress: PropTypes.func,
style: ViewPropTypes.style,
icon: PropTypes.string
};
export default UserItem;

View File

@ -6,7 +6,7 @@ const initialState = {
failure: false,
server: '',
loading: true,
adding: true
adding: false
};
@ -16,16 +16,14 @@ export default function server(state = initialState, action) {
return {
...state,
connecting: true,
failure: false,
adding: true
failure: false
};
case SERVER.FAILURE:
return {
...state,
connecting: false,
connected: false,
failure: true,
adding: false
failure: true
};
case SERVER.SELECT_REQUEST:
return {
@ -43,6 +41,16 @@ export default function server(state = initialState, action) {
connected: true,
loading: false
};
case SERVER.INIT_ADD:
return {
...state,
adding: true
};
case SERVER.FINISH_ADD:
return {
...state,
adding: false
};
default:
return state;
}

View File

@ -12,25 +12,26 @@ const create = function* create(data) {
const handleRequest = function* handleRequest({ data }) {
try {
// yield delay(1000);
const auth = yield select(state => state.login.isAuthenticated);
if (!auth) {
yield take(LOGIN.SUCCESS);
}
const result = yield call(create, data);
yield put(createChannelSuccess(result));
yield delay(300);
const { rid, name } = result;
NavigationActions.popToRoot();
yield delay(1000);
NavigationActions.dismissModal();
yield delay(600);
NavigationActions.push({
screen: 'RoomView',
title: name,
backButtonTitle: '',
passProps: {
room: { rid, name },
rid,
name
}
});
yield put(createChannelSuccess(result));
} catch (err) {
yield put(createChannelFailure(err));
}

View File

@ -17,6 +17,7 @@ const navigate = function* go({ params, sameServer = true }) {
if (canOpenRoom) {
return NavigationActions.push({
screen: 'RoomView',
backButtonTitle: '',
passProps: {
rid: params.rid
}

View File

@ -4,6 +4,7 @@ import { put, call, take, takeLatest, select, all } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes';
import { appStart } from '../actions';
import { serverFinishAdd } from '../actions/server';
import {
// loginRequest,
// loginSubmit,
@ -38,14 +39,19 @@ const forgotPasswordCall = args => RocketChat.forgotPassword(args);
const handleLoginSuccess = function* handleLoginSuccess() {
try {
const user = yield select(getUser);
const adding = yield select(state => state.server.adding);
yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token);
if (!user.username || user.isRegistering) {
yield put(registerIncomplete());
} else {
yield delay(300);
if (adding) {
NavigationActions.dismissModal();
} else {
yield put(appStart('inside'));
}
yield put(serverFinishAdd());
}
} catch (e) {
log('handleLoginSuccess', e);
}

View File

@ -80,6 +80,7 @@ const goRoom = function* goRoom({ rid, name }) {
yield delay(1000);
NavigationActions.push({
screen: 'RoomView',
backButtonTitle: '',
passProps: {
room: { rid, name },
rid,

View File

@ -44,7 +44,7 @@ const handleSelectServer = function* handleSelectServer({ server }) {
const handleServerRequest = function* handleServerRequest({ server }) {
try {
yield call(validate, server);
yield call(NavigationActions.push, { screen: 'LoginSignupView', title: server });
yield call(NavigationActions.push, { screen: 'LoginSignupView', title: server, backButtonTitle: '' });
database.databases.serversDB.write(() => {
database.databases.serversDB.create('servers', { id: server, current: false }, true);
});

View File

@ -1,29 +1,85 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { View, Text, Switch, SafeAreaView, ScrollView, Platform } from 'react-native';
import { View, Text, Switch, SafeAreaView, ScrollView, TextInput, StyleSheet, FlatList, Platform } from 'react-native';
import RCTextInput from '../containers/TextInput';
import Loading from '../containers/Loading';
import LoggedView from './View';
import { createChannelRequest } from '../actions/createChannel';
import styles from './Styles';
import { removeUser } from '../actions/selectedUsers';
import sharedStyles from './Styles';
import KeyboardView from '../presentation/KeyboardView';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import Button from '../containers/Button';
import I18n from '../i18n';
import UserItem from '../presentation/UserItem';
import { showErrorAlert } from '../utils/info';
const styles = StyleSheet.create({
container: {
backgroundColor: '#f7f8fa'
},
list: {
width: '100%',
backgroundColor: '#FFFFFF'
},
separator: {
marginLeft: 60
},
formSeparator: {
marginLeft: 15
},
input: {
height: 54,
paddingHorizontal: 18,
color: '#9EA2A8',
backgroundColor: '#fff',
fontSize: 18
},
swithContainer: {
height: 54,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'space-between',
flexDirection: 'row',
paddingHorizontal: 18
},
label: {
color: '#0C0D0F',
fontSize: 18,
fontWeight: '500'
},
invitedHeader: {
marginTop: 18,
marginHorizontal: 15,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
},
invitedTitle: {
color: '#2F343D',
fontSize: 22,
fontWeight: 'bold',
lineHeight: 41
},
invitedCount: {
color: '#9EA2A8',
fontSize: 15
}
});
@connect(state => ({
createChannel: state.createChannel,
users: state.selectedUsers.users
}), dispatch => ({
create: data => dispatch(createChannelRequest(data))
create: data => dispatch(createChannelRequest(data)),
removeUser: user => dispatch(removeUser(user))
}))
/** @extends React.Component */
export default class CreateChannelView extends LoggedView {
static propTypes = {
navigator: PropTypes.object,
create: PropTypes.func.isRequired,
removeUser: PropTypes.func.isRequired,
createChannel: PropTypes.object.isRequired,
users: PropTypes.array.isRequired
};
@ -36,6 +92,7 @@ export default class CreateChannelView extends LoggedView {
readOnly: false,
broadcast: false
};
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
}
componentDidMount() {
@ -44,6 +101,36 @@ export default class CreateChannelView extends LoggedView {
}, 600);
}
componentDidUpdate(prevProps) {
if (this.props.createChannel.error && prevProps.createChannel.error !== this.props.createChannel.error) {
setTimeout(() => {
const msg = this.props.createChannel.error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') });
showErrorAlert(msg);
}, 300);
}
}
onChangeText = (channelName) => {
const rightButtons = [];
if (channelName.trim().length > 0) {
rightButtons.push({
id: 'create',
title: 'Create',
testID: 'create-channel-submit'
});
}
this.props.navigator.setButtons({ rightButtons });
this.setState({ channelName });
}
async onNavigatorEvent(event) {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'create') {
this.submit();
}
}
}
submit = () => {
if (!this.state.channelName.trim() || this.props.createChannel.isFetching) {
return;
@ -62,26 +149,18 @@ export default class CreateChannelView extends LoggedView {
});
}
renderChannelNameError() {
if (
!this.props.createChannel.failure ||
this.props.createChannel.error.error !== 'error-duplicate-channel-name'
) {
return null;
removeUser = (user) => {
if (this.props.users.length === 1) {
return;
}
return (
<Text style={[styles.label_white, styles.label_error]} testID='create-channel-error'>
{this.props.createChannel.error.reason}
</Text>
);
this.props.removeUser(user);
}
renderSwitch = ({
id, value, label, description, onValueChange, disabled = false
id, value, label, onValueChange, disabled = false
}) => (
<View style={{ marginBottom: 15 }}>
<View style={styles.switchContainer}>
<View style={styles.swithContainer}>
<Text style={styles.label}>{I18n.t(label)}</Text>
<Switch
value={value}
onValueChange={onValueChange}
@ -90,19 +169,15 @@ export default class CreateChannelView extends LoggedView {
tintColor={Platform.OS === 'android' ? '#f5455c' : null}
disabled={disabled}
/>
<Text style={styles.switchLabel}>{label}</Text>
</View>
<Text style={styles.switchDescription}>{description}</Text>
</View>
);
)
renderType() {
const { type } = this.state;
return this.renderSwitch({
id: 'type',
value: type,
label: type ? I18n.t('Private_Channel') : I18n.t('Public_Channel'),
description: type ? I18n.t('Just_invited_people_can_access_this_channel') : I18n.t('Everyone_can_access_this_channel'),
label: 'Private_Channel',
onValueChange: value => this.setState({ type: value })
});
}
@ -112,8 +187,7 @@ export default class CreateChannelView extends LoggedView {
return this.renderSwitch({
id: 'readonly',
value: readOnly,
label: I18n.t('Read_Only_Channel'),
description: readOnly ? I18n.t('Only_authorized_users_can_write_new_messages') : I18n.t('All_users_in_the_channel_can_write_new_messages'),
label: 'Read_Only_Channel',
onValueChange: value => this.setState({ readOnly: value }),
disabled: broadcast
});
@ -124,8 +198,7 @@ export default class CreateChannelView extends LoggedView {
return this.renderSwitch({
id: 'broadcast',
value: broadcast,
label: I18n.t('Broadcast_Channel'),
description: I18n.t('Broadcast_channel_Description'),
label: 'Broadcast_Channel',
onValueChange: (value) => {
this.setState({
broadcast: value,
@ -135,39 +208,70 @@ export default class CreateChannelView extends LoggedView {
});
}
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />
renderFormSeparator = () => <View style={[sharedStyles.separator, styles.formSeparator]} />
renderItem = ({ item }) => (
<UserItem
name={item.fname}
username={item.name}
onPress={() => this.removeUser(item)}
testID={`create-channel-view-item-${ item.name }`}
/>
)
renderInvitedList = () => (
<FlatList
data={this.props.users}
extraData={this.props.users}
keyExtractor={item => item._id}
style={[styles.list, sharedStyles.separatorVertical]}
renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator}
enableEmptySections
keyboardShouldPersistTaps='always'
/>
)
render() {
const userCount = this.props.users.length;
return (
<KeyboardView
contentContainerStyle={styles.container}
contentContainerStyle={[sharedStyles.container, styles.container]}
keyboardVerticalOffset={128}
>
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
<SafeAreaView testID='create-channel-view'>
<RCTextInput
inputRef={ref => this.channelNameRef = ref}
<ScrollView {...scrollPersistTaps}>
<View style={sharedStyles.separatorVertical}>
<TextInput
ref={ref => this.channelNameRef = ref}
style={styles.input}
label={I18n.t('Channel_Name')}
value={this.state.channelName}
onChangeText={channelName => this.setState({ channelName })}
placeholder={I18n.t('Type_the_channel_name_here')}
onChangeText={this.onChangeText}
placeholder={I18n.t('Channel_Name')}
returnKeyType='done'
testID='create-channel-name'
autoCorrect={false}
autoCapitalize='none'
underlineColorAndroid='transparent'
/>
{this.renderChannelNameError()}
{this.renderFormSeparator()}
{this.renderType()}
{this.renderFormSeparator()}
{this.renderReadOnly()}
{this.renderFormSeparator()}
{this.renderBroadcast()}
<View style={styles.alignItemsFlexStart}>
<Button
title={I18n.t('Create')}
type='primary'
onPress={this.submit}
disabled={this.state.channelName.length === 0 || this.props.createChannel.isFetching}
testID='create-channel-submit'
/>
</View>
<View style={styles.invitedHeader}>
<Text style={styles.invitedTitle}>{I18n.t('Invite')}</Text>
<Text style={styles.invitedCount}>{userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })}</Text>
</View>
{this.renderInvitedList()}
<Loading visible={this.props.createChannel.isFetching} />
</SafeAreaView>
</ScrollView>
</SafeAreaView>
</KeyboardView>
);
}

View File

@ -182,7 +182,7 @@ export default class LoginSignupView extends LoggedView {
this.props.navigator.push({
screen: 'LoginView',
title: this.props.server,
backButtonTitle: I18n.t('Welcome')
backButtonTitle: ''
});
}
@ -190,7 +190,7 @@ export default class LoginSignupView extends LoggedView {
this.props.navigator.push({
screen: 'RegisterView',
title: this.props.server,
backButtonTitle: I18n.t('Welcome')
backButtonTitle: ''
});
}

View File

@ -68,7 +68,7 @@ export default class LoginView extends LoggedView {
this.props.navigator.push({
screen: 'RegisterView',
title: this.props.server,
backButtonTitle: I18n.t('Login')
backButtonTitle: ''
});
}
@ -76,7 +76,7 @@ export default class LoginView extends LoggedView {
this.props.navigator.push({
screen: 'ForgotPasswordView',
title: I18n.t('Forgot_Password'),
backButtonTitle: I18n.t('Login')
backButtonTitle: ''
});
}

166
app/views/NewMessageView.js Normal file
View File

@ -0,0 +1,166 @@
import React from 'react';
import PropTypes from 'prop-types';
import { View, StyleSheet, SafeAreaView, FlatList, Text, Platform, Image } from 'react-native';
import database from '../lib/realm';
import RocketChat from '../lib/rocketchat';
import UserItem from '../presentation/UserItem';
import debounce from '../utils/debounce';
import LoggedView from './View';
import sharedStyles from './Styles';
import I18n from '../i18n';
import Touch from '../utils/touch';
import SearchBox from '../containers/SearchBox';
const styles = StyleSheet.create({
safeAreaView: {
flex: 1,
backgroundColor: Platform.OS === 'ios' ? '#F7F8FA' : '#E1E5E8'
},
separator: {
marginLeft: 60
},
createChannelButton: {
marginVertical: 25
},
createChannelContainer: {
height: 47,
backgroundColor: '#fff',
flexDirection: 'row',
alignItems: 'center'
},
createChannelIcon: {
width: 24,
height: 24,
marginHorizontal: 18
},
createChannelText: {
color: '#1D74F5',
fontSize: 18
}
});
/** @extends React.Component */
export default class SelectedUsersView extends LoggedView {
static navigatorButtons = {
leftButtons: [{
id: 'cancel',
title: I18n.t('Cancel')
}]
}
static propTypes = {
navigator: PropTypes.object,
onPressItem: PropTypes.func.isRequired
};
constructor(props) {
super('NewMessageView', props);
this.data = database.objects('subscriptions').filtered('t = $0', 'd').sorted('roomUpdatedAt', true);
this.state = {
search: []
};
this.data.addListener(this.updateState);
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
}
componentWillUnmount() {
this.updateState.stop();
this.data.removeAllListeners();
}
async onNavigatorEvent(event) {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'cancel') {
this.props.navigator.dismissModal();
}
}
}
onSearchChangeText(text) {
this.search(text);
}
onPressItem = (item) => {
this.props.navigator.dismissModal();
setTimeout(() => {
this.props.onPressItem(item);
}, 600);
}
updateState = debounce(() => {
this.forceUpdate();
}, 1000);
search = async(text) => {
const result = await RocketChat.search({ text, filterRooms: false });
this.setState({
search: result
});
}
createChannel = () => {
this.props.navigator.push({
screen: 'SelectedUsersView',
title: I18n.t('Select_Users'),
backButtonTitle: '',
passProps: {
nextAction: 'CREATE_CHANNEL'
}
});
}
renderHeader = () => (
<View>
<SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='new-message-view-search' />
<Touch onPress={this.createChannel} style={styles.createChannelButton} testID='new-message-view-create-channel'>
<View style={[sharedStyles.separatorVertical, styles.createChannelContainer]}>
<Image style={styles.createChannelIcon} source={{ uri: 'plus' }} />
<Text style={styles.createChannelText}>{I18n.t('Create_Channel')}</Text>
</View>
</Touch>
</View>
)
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />;
renderItem = ({ item, index }) => {
let style = {};
if (index === 0) {
style = { ...sharedStyles.separatorTop };
}
if (this.state.search.length > 0 && index === this.state.search.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
if (this.state.search.length === 0 && index === this.data.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
return (
<UserItem
name={item.search ? item.name : item.fname}
username={item.search ? item.username : item.name}
onPress={() => this.onPressItem(item)}
testID={`new-message-view-item-${ item.name }`}
style={style}
/>
);
}
renderList = () => (
<FlatList
data={this.state.search.length > 0 ? this.state.search : this.data}
extraData={this.state}
keyExtractor={item => item._id}
ListHeaderComponent={this.renderHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator}
keyboardShouldPersistTaps='always'
/>
)
render = () => (
<SafeAreaView style={styles.safeAreaView} testID='new-message-view'>
{this.renderList()}
</SafeAreaView>
);
}

View File

@ -1,9 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Text, ScrollView, Keyboard, SafeAreaView, Image, Alert, StyleSheet, Platform } from 'react-native';
import { Text, ScrollView, Keyboard, SafeAreaView, Image, Alert, StyleSheet } from 'react-native';
import { connect } from 'react-redux';
import { serverRequest } from '../actions/server';
import { serverRequest, selectServerRequest, serverInitAdd, serverFinishAdd } from '../actions/server';
import sharedStyles from './Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import Button from '../containers/Button';
@ -12,7 +12,6 @@ import LoggedView from './View';
import I18n from '../i18n';
import { scale, verticalScale, moderateScale } from '../utils/scaling';
import KeyboardView from '../presentation/KeyboardView';
import { iconsMap } from '../Icons';
const styles = StyleSheet.create({
image: {
@ -45,9 +44,13 @@ const defaultServer = 'https://open.rocket.chat';
@connect(state => ({
connecting: state.server.connecting,
failure: state.server.failure,
currentServer: state.server.server
currentServer: state.server.server,
adding: state.server.adding
}), dispatch => ({
connectServer: url => dispatch(serverRequest(url))
initAdd: () => dispatch(serverInitAdd()),
finishAdd: () => dispatch(serverFinishAdd()),
connectServer: server => dispatch(serverRequest(server)),
selectServer: server => dispatch(selectServerRequest(server))
}))
/** @extends React.Component */
export default class NewServerView extends LoggedView {
@ -55,10 +58,14 @@ export default class NewServerView extends LoggedView {
navigator: PropTypes.object,
server: PropTypes.string,
connecting: PropTypes.bool.isRequired,
adding: PropTypes.bool,
failure: PropTypes.bool.isRequired,
connectServer: PropTypes.func.isRequired,
selectServer: PropTypes.func.isRequired,
previousServer: PropTypes.string,
currentServer: PropTypes.string
currentServer: PropTypes.string,
initAdd: PropTypes.func,
finishAdd: PropTypes.func
}
constructor(props) {
@ -69,25 +76,8 @@ export default class NewServerView extends LoggedView {
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
}
componentWillMount() {
// if previousServer exists, New Server View is a modal
if (this.props.previousServer) {
const closeButton = {
id: 'close',
testID: 'new-server-close',
title: I18n.t('Close')
};
if (Platform.OS === 'android') {
closeButton.icon = iconsMap.close;
}
this.props.navigator.setButtons({
leftButtons: [closeButton]
});
}
}
componentDidMount() {
const { server } = this.props;
const { server, previousServer } = this.props;
if (server) {
this.props.connectServer(server);
this.setState({ text: server });
@ -96,6 +86,9 @@ export default class NewServerView extends LoggedView {
this.input.focus();
}, 600);
}
if (previousServer) {
this.props.initAdd();
}
}
componentWillReceiveProps(nextProps) {
@ -104,16 +97,22 @@ export default class NewServerView extends LoggedView {
}
}
componentWillUnmount() {
const {
selectServer, previousServer, currentServer, adding, finishAdd
} = this.props;
if (adding) {
if (previousServer !== currentServer) {
selectServer(previousServer);
}
finishAdd();
}
}
onNavigatorEvent(event) {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'close') {
const {
navigator, connectServer, previousServer, currentServer
} = this.props;
navigator.dismissModal();
if (previousServer !== currentServer) {
connectServer(previousServer);
}
if (event.id === 'cancel') {
this.props.navigator.dismissModal();
}
}
}

View File

@ -13,7 +13,7 @@ const userAgent = Platform.OS === 'ios' ? 'UserAgent' : userAgentAndroid;
@connect(state => ({
server: state.server.server
}))
export default class TermsServiceView extends React.PureComponent {
export default class OAuthView extends React.PureComponent {
static navigatorButtons = {
leftButtons: [{
id: 'close',

View File

@ -21,6 +21,7 @@ export default class OnboardingView extends LoggedView {
connectServer = () => {
this.props.navigator.push({
screen: 'NewServerView',
backButtonTitle: '',
navigatorStyle: {
navBarHidden: true
}
@ -30,6 +31,7 @@ export default class OnboardingView extends LoggedView {
joinCommunity = () => {
this.props.navigator.push({
screen: 'NewServerView',
backButtonTitle: '',
passProps: {
server: 'https://open.rocket.chat'
},

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { View, ScrollView, SafeAreaView, Keyboard, Platform, Dimensions } from 'react-native';
import { View, ScrollView, SafeAreaView, Keyboard, Dimensions } from 'react-native';
import { connect } from 'react-redux';
import Dialog from 'react-native-dialog';
import SHA256 from 'js-sha256';
@ -61,7 +61,7 @@ export default class ProfileView extends LoggedView {
componentWillMount() {
this.props.navigator.setButtons({
leftButtons: [{
id: 'sideMenu',
id: 'settings',
icon: { uri: 'settings', scale: Dimensions.get('window').scale }
}]
});
@ -91,7 +91,7 @@ export default class ProfileView extends LoggedView {
onNavigatorEvent(event) {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'sideMenu' && Platform.OS === 'ios') {
if (event.id === 'settings') {
this.props.navigator.toggleDrawer({
side: 'left'
});

View File

@ -93,7 +93,7 @@ export default class RegisterView extends LoggedView {
this.props.navigator.push({
screen: 'TermsServiceView',
title: I18n.t('Terms_of_Service'),
backButtonTitle: I18n.t('Sign_Up')
backButtonTitle: ''
});
}
@ -101,7 +101,7 @@ export default class RegisterView extends LoggedView {
this.props.navigator.push({
screen: 'PrivacyPolicyView',
title: I18n.t('Privacy_Policy'),
backButtonTitle: I18n.t('Sign_Up')
backButtonTitle: ''
});
}

View File

@ -65,7 +65,8 @@ export default class RoomActionsView extends LoggedView {
this.props.navigator.push({
screen: item.route,
title: item.name,
passProps: item.params
passProps: item.params,
backButtonTitle: ''
});
}
if (item.event) {

View File

@ -118,6 +118,7 @@ export default class RoomInfoView extends LoggedView {
this.props.navigator.push({
screen: 'RoomInfoEditView',
title: I18n.t('Room_Info_Edit'),
backButtonTitle: '',
passProps: {
rid: this.props.rid
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FlatList, View, TextInput, Vibration, SafeAreaView } from 'react-native';
import { FlatList, View, Vibration, SafeAreaView } from 'react-native';
import ActionSheet from 'react-native-actionsheet';
import LoggedView from '../View';
@ -12,6 +12,7 @@ import database from '../../lib/realm';
import { showToast } from '../../utils/info';
import log from '../../utils/log';
import I18n from '../../i18n';
import SearchBox from '../../containers/SearchBox';
/** @extends React.Component */
export default class RoomMembersView extends LoggedView {
@ -132,6 +133,7 @@ export default class RoomMembersView extends LoggedView {
this.props.navigator.push({
screen: 'RoomView',
title: name,
backButtonTitle: '',
passProps: {
room: { rid, name },
rid,
@ -162,20 +164,7 @@ export default class RoomMembersView extends LoggedView {
}
renderSearchBar = () => (
<View style={styles.searchBoxView}>
<TextInput
underlineColorAndroid='transparent'
style={styles.searchBox}
onChangeText={text => this.onSearchChangeText(text)}
returnKeyType='search'
placeholder={I18n.t('Search')}
clearButtonMode='while-editing'
blurOnSubmit
autoCorrect={false}
autoCapitalize='none'
testID='room-members-view-search'
/>
</View>
<SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='room-members-view-search' />
)
renderSeparator = () => <View style={styles.separator} />;

View File

@ -127,6 +127,7 @@ export default class RoomView extends LoggedView {
this.props.navigator.push({
screen: 'RoomActionsView',
title: I18n.t('Actions'),
backButtonTitle: '',
passProps: {
rid: this.state.room.rid
}

View File

@ -86,6 +86,13 @@ export default class ServerDropdown extends Component {
title: I18n.t('Add_Server'),
passProps: {
previousServer: this.props.server
},
navigatorButtons: {
leftButtons: [{
id: 'cancel',
testID: 'new-server-close',
title: I18n.t('Close')
}]
}
});
}, ANIMATION_DURATION);
@ -101,6 +108,7 @@ export default class ServerDropdown extends Component {
setTimeout(() => {
this.props.navigator.push({
screen: 'NewServerView',
backButtonTitle: '',
passProps: {
server
},

View File

@ -1,9 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Platform, View, TextInput, FlatList, BackHandler, ActivityIndicator, SafeAreaView, Text, Image, Dimensions, ScrollView, Keyboard } from 'react-native';
import { Platform, View, FlatList, BackHandler, ActivityIndicator, SafeAreaView, Text, Image, Dimensions, ScrollView, Keyboard } from 'react-native';
import { connect } from 'react-redux';
import { isEqual } from 'lodash';
import SearchBox from '../../containers/SearchBox';
import database from '../../lib/realm';
import RocketChat from '../../lib/rocketchat';
import RoomItem from '../../presentation/RoomItem';
@ -26,7 +27,7 @@ const leftButtons = [{
testID: 'rooms-list-view-sidebar'
}];
const rightButtons = [{
id: 'createChannel',
id: 'newMessage',
icon: { uri: 'new_channel', scale: Dimensions.get('window').scale },
testID: 'rooms-list-view-create-channel'
}];
@ -38,7 +39,6 @@ if (Platform.OS === 'android') {
});
}
@connect((state) => {
let result = {
userId: state.login.user && state.login.user.id,
@ -74,7 +74,6 @@ export default class RoomsListView extends LoggedView {
static navigatorStyle = {
navBarCustomView: 'RoomsListHeaderView',
navBarComponentAlignment: 'fill',
navBarBackgroundColor: isAndroid() ? '#2F343D' : undefined,
navBarTextColor: isAndroid() ? '#FFF' : undefined,
navBarButtonColor: isAndroid() ? '#FFF' : undefined
@ -157,6 +156,10 @@ export default class RoomsListView extends LoggedView {
this.removeListener(this.direct);
this.removeListener(this.livechat);
if (database && database.deleteAll) {
database.deleteAll();
}
if (this.timeout) {
clearTimeout(this.timeout);
}
@ -165,12 +168,12 @@ export default class RoomsListView extends LoggedView {
onNavigatorEvent(event) {
const { navigator } = this.props;
if (event.type === 'NavBarButtonPress') {
if (event.id === 'createChannel') {
this.props.navigator.push({
screen: 'SelectedUsersView',
title: I18n.t('Select_Users'),
if (event.id === 'newMessage') {
this.props.navigator.showModal({
screen: 'NewMessageView',
title: I18n.t('New_Message'),
passProps: {
nextAction: 'CREATE_CHANNEL'
onPressItem: this._onPressItem
}
});
} else if (event.id === 'settings') {
@ -190,10 +193,6 @@ export default class RoomsListView extends LoggedView {
}
}
onSearchChangeText(text) {
this.search(text);
}
getSubscriptions = () => {
if (this.props.server && this.hasActiveDB()) {
if (this.props.sidebarSortby === 'alphabetical') {
@ -285,7 +284,6 @@ export default class RoomsListView extends LoggedView {
navigator.setButtons({ leftButtons, rightButtons });
navigator.setStyle({
navBarCustomView: 'RoomsListHeaderView',
navBarComponentAlignment: 'fill',
navBarBackgroundColor: isAndroid() ? '#2F343D' : undefined,
navBarTextColor: isAndroid() ? '#FFF' : undefined,
navBarButtonColor: isAndroid() ? '#FFF' : undefined
@ -327,55 +325,18 @@ export default class RoomsListView extends LoggedView {
_isUnread = item => item.unread > 0 || item.alert
async search(text) {
const searchText = text.trim();
if (searchText === '') {
delete this.oldPromise;
return this.setState({
search: []
});
}
let data = database.objects('subscriptions').filtered('name CONTAINS[c] $0', searchText).slice(0, 7);
const usernames = data.map(sub => sub.name);
try {
if (data.length < 7) {
if (this.oldPromise) {
this.oldPromise('cancel');
}
const { users, rooms } = await Promise.race([
RocketChat.spotlight(searchText, usernames, { users: true, rooms: true }),
new Promise((resolve, reject) => this.oldPromise = reject)
]);
data = data.concat(users.map(user => ({
...user,
rid: user.username,
name: user.username,
t: 'd',
search: true
})), rooms.map(room => ({
rid: room._id,
...room,
search: true
})));
delete this.oldPromise;
}
search = async(text) => {
const result = await RocketChat.search({ text });
this.setState({
search: data
search: result
});
} catch (e) {
// alert(JSON.stringify(e));
}
}
goRoom = (rid, name) => {
this.props.navigator.push({
screen: 'RoomView',
title: name,
backButtonTitle: '',
passProps: {
room: { rid, name },
rid,
@ -429,22 +390,7 @@ export default class RoomsListView extends LoggedView {
renderSearchBar = () => {
if (Platform.OS === 'ios') {
return (
<View style={styles.searchBoxView}>
<TextInput
underlineColorAndroid='transparent'
style={styles.searchBox}
onChangeText={text => this.onSearchChangeText(text)}
returnKeyType='search'
placeholder={I18n.t('Search')}
clearButtonMode='while-editing'
blurOnSubmit
autoCorrect={false}
autoCapitalize='none'
testID='rooms-list-view-search'
/>
</View>
);
return <SearchBox onChangeText={text => this.search(text)} testID='rooms-list-view-search' />;
}
}
@ -537,7 +483,7 @@ export default class RoomsListView extends LoggedView {
return (
<ScrollView
contentOffset={Platform.OS === 'ios' ? { x: 0, y: 37 } : {}}
contentOffset={Platform.OS === 'ios' ? { x: 0, y: 56 } : {}}
keyboardShouldPersistTaps='always'
testID='rooms-list-view-list'
>

View File

@ -31,17 +31,6 @@ export default StyleSheet.create({
height: 22,
color: 'white'
},
searchBoxView: {
backgroundColor: '#eee'
},
searchBox: {
backgroundColor: '#fff',
margin: 5,
borderRadius: 5,
padding: 5,
paddingLeft: 10,
color: '#aaa'
},
loading: {
flex: 1
},

View File

@ -1,56 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import { View, StyleSheet, TextInput, Text, TouchableOpacity, SafeAreaView, FlatList, LayoutAnimation } from 'react-native';
import { View, StyleSheet, SafeAreaView, FlatList, LayoutAnimation, Platform } from 'react-native';
import { connect } from 'react-redux';
import { addUser, removeUser, reset, setLoading } from '../actions/selectedUsers';
import database from '../lib/realm';
import RocketChat from '../lib/rocketchat';
import UserItem from '../presentation/UserItem';
import Avatar from '../containers/Avatar';
import Loading from '../containers/Loading';
import debounce from '../utils/debounce';
import LoggedView from './View';
import I18n from '../i18n';
import log from '../utils/log';
import { iconsMap } from '../Icons';
import SearchBox from '../containers/SearchBox';
import sharedStyles from './Styles';
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'stretch',
justifyContent: 'center'
},
safeAreaView: {
flex: 1,
backgroundColor: '#FFFFFF'
backgroundColor: Platform.OS === 'ios' ? '#F7F8FA' : '#E1E5E8'
},
list: {
width: '100%',
backgroundColor: '#FFFFFF'
},
searchBoxView: {
backgroundColor: '#eee'
},
searchBox: {
backgroundColor: '#fff',
margin: 5,
borderRadius: 5,
padding: 5,
paddingLeft: 10,
color: '#aaa'
},
selectItemView: {
width: 80,
height: 80,
padding: 8,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
header: {
backgroundColor: '#fff'
},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: '#E1E5E8',
marginLeft: 60
}
});
@ -95,15 +68,19 @@ export default class SelectedUsersView extends LoggedView {
});
}
componentWillReceiveProps(nextProps) {
if (nextProps.users.length !== this.props.users.length) {
const { length } = nextProps.users;
async componentDidUpdate(prevProps) {
const isVisible = await this.props.navigator.screenIsCurrentlyVisible();
if (!isVisible) {
return;
}
if (prevProps.users.length !== this.props.users.length) {
const { length } = this.props.users;
const rightButtons = [];
if (length > 0) {
rightButtons.push({
id: 'create',
testID: 'selected-users-view-submit',
icon: iconsMap.add
title: I18n.t('Next'),
testID: 'selected-users-view-submit'
});
}
this.props.navigator.setButtons({ rightButtons });
@ -123,7 +100,8 @@ export default class SelectedUsersView extends LoggedView {
if (nextAction === 'CREATE_CHANNEL') {
this.props.navigator.push({
screen: 'CreateChannelView',
title: I18n.t('Create_Channel')
title: I18n.t('Create_Channel'),
backButtonTitle: ''
});
} else {
try {
@ -148,90 +126,40 @@ export default class SelectedUsersView extends LoggedView {
this.forceUpdate();
}, 1000);
async search(text) {
const searchText = text.trim();
if (searchText === '') {
delete this.oldPromise;
return this.setState({
search: []
});
}
let data = this.data.filtered('name CONTAINS[c] $0 AND t = $1', searchText, 'd').slice(0, 7);
const usernames = data.map(sub => sub.map);
try {
if (data.length < 7) {
if (this.oldPromise) {
this.oldPromise('cancel');
}
const { users } = await Promise.race([
RocketChat.spotlight(searchText, usernames, { users: true, rooms: false }),
new Promise((resolve, reject) => this.oldPromise = reject)
]);
data = users.map(user => ({
...user,
rid: user.username,
name: user.username,
t: 'd',
search: true
}));
delete this.oldPromise;
}
search = async(text) => {
const result = await RocketChat.search({ text, filterRooms: false });
this.setState({
search: data
search: result
});
} catch (e) {
// alert(JSON.stringify(e));
}
}
isChecked = username => this.props.users.findIndex(el => el.name === username) !== -1;
toggleUser = (user) => {
LayoutAnimation.easeInEaseOut();
const index = this.props.users.findIndex(el => el.name === user.name);
if (index === -1) {
if (!this.isChecked(user.name)) {
this.props.addUser(user);
} else {
this.props.removeUser(user);
}
};
}
_onPressItem = (id, item = {}) => {
if (item.search) {
this.toggleUser({ _id: item._id, name: item.username });
this.toggleUser({ _id: item._id, name: item.username, fname: item.name });
} else {
this.toggleUser({ _id: item._id, name: item.name });
this.toggleUser({ _id: item._id, name: item.name, fname: item.fname });
}
}
};
_onPressSelectedItem = item => this.toggleUser(item);
renderHeader = () => (
<View style={styles.container}>
{this.renderSearchBar()}
<View style={styles.header}>
<SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='select-users-view-search' />
{this.renderSelected()}
</View>
);
renderSearchBar = () => (
<View style={styles.searchBoxView}>
<TextInput
underlineColorAndroid='transparent'
style={styles.searchBox}
onChangeText={text => this.onSearchChangeText(text)}
returnKeyType='search'
placeholder={I18n.t('Search')}
clearButtonMode='while-editing'
blurOnSubmit
testID='select-users-view-search'
autoCorrect={false}
autoCapitalize='none'
/>
</View>
);
)
renderSelected = () => {
if (this.props.users.length === 0) {
@ -241,57 +169,70 @@ export default class SelectedUsersView extends LoggedView {
<FlatList
data={this.props.users}
keyExtractor={item => item._id}
style={styles.list}
style={[styles.list, sharedStyles.separatorTop]}
contentContainerStyle={{ marginVertical: 5 }}
renderItem={this.renderSelectedItem}
enableEmptySections
keyboardShouldPersistTaps='always'
horizontal
/>
);
};
}
renderSelectedItem = ({ item }) => (
<TouchableOpacity
key={item._id}
style={styles.selectItemView}
<UserItem
name={item.fname}
username={item.name}
onPress={() => this._onPressSelectedItem(item)}
testID={`selected-user-${ item.name }`}
>
<Avatar text={item.name} size={40} />
<Text ellipsizeMode='tail' numberOfLines={1} style={{ fontSize: 10 }}>
{item.name}
</Text>
</TouchableOpacity>
);
renderSeparator = () => <View style={styles.separator} />;
renderItem = ({ item }) => (
<UserItem
name={item.fname ? item.fname : item.name}
username={item.fname ? item.name : item.username}
onPress={() => this._onPressItem(item._id, item)}
testID={`select-users-view-item-${ item.name }`}
style={{ paddingRight: 15 }}
/>
)
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />
renderItem = ({ item, index }) => {
const name = item.search ? item.name : item.fname;
const username = item.search ? item.username : item.name;
let style = {};
if (index === 0) {
style = { ...sharedStyles.separatorTop };
}
if (this.state.search.length > 0 && index === this.state.search.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
if (this.state.search.length === 0 && index === this.data.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
return (
<UserItem
name={name}
username={username}
onPress={() => this._onPressItem(item._id, item)}
testID={`select-users-view-item-${ item.name }`}
icon={this.isChecked(username) ? 'check' : null}
style={style}
/>
);
}
renderList = () => (
<FlatList
data={this.state.search.length > 0 ? this.state.search : this.data}
extraData={this.props}
keyExtractor={item => item._id}
style={styles.list}
renderItem={this.renderItem}
ListHeaderComponent={this.renderHeader}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
enableEmptySections
keyboardShouldPersistTaps='always'
/>
);
)
render = () => (
<SafeAreaView style={styles.safeAreaView} testID='select-users-view'>
{this.renderList()}
<Loading visible={this.props.loading} />
</SafeAreaView>
);
)
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { View, ScrollView, SafeAreaView, Platform, Dimensions } from 'react-native';
import { View, ScrollView, SafeAreaView, Dimensions } from 'react-native';
import RNPickerSelect from 'react-native-picker-select';
import { connect } from 'react-redux';
@ -50,7 +50,7 @@ export default class SettingsView extends LoggedView {
componentWillMount() {
this.props.navigator.setButtons({
leftButtons: [{
id: 'sideMenu',
id: 'settings',
icon: { uri: 'settings', scale: Dimensions.get('window').scale }
}]
});
@ -65,7 +65,7 @@ export default class SettingsView extends LoggedView {
onNavigatorEvent(event) {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'sideMenu' && Platform.OS === 'ios') {
if (event.id === 'settings') {
this.props.navigator.toggleDrawer({
side: 'left'
});

View File

@ -1,6 +1,6 @@
import { StyleSheet, Platform } from 'react-native';
import { COLOR_DANGER, COLOR_BUTTON_PRIMARY, COLOR_TEXT } from '../constants/colors';
import { COLOR_DANGER, COLOR_BUTTON_PRIMARY, COLOR_TEXT, COLOR_SEPARATOR } from '../constants/colors';
export default StyleSheet.create({
container: {
@ -197,5 +197,22 @@ export default StyleSheet.create({
width: 44,
alignItems: 'center',
justifyContent: 'center'
},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: COLOR_SEPARATOR
},
separatorTop: {
borderColor: COLOR_SEPARATOR,
borderTopWidth: StyleSheet.hairlineWidth
},
separatorBottom: {
borderColor: COLOR_SEPARATOR,
borderBottomWidth: StyleSheet.hairlineWidth
},
separatorVertical: {
borderColor: COLOR_SEPARATOR,
borderTopWidth: StyleSheet.hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth
}
});

View File

@ -6,6 +6,7 @@ import ForgotPasswordView from './ForgotPasswordView';
import LoginSignupView from './LoginSignupView';
import LoginView from './LoginView';
import MentionedMessagesView from './MentionedMessagesView';
import NewMessageView from './NewMessageView';
import NewServerView from './NewServerView';
import OAuthView from './OAuthView';
import OnboardingView from './OnboardingView';
@ -36,6 +37,7 @@ export const registerScreens = (store) => {
Navigation.registerComponent('LoginSignupView', () => LoginSignupView, store, Provider);
Navigation.registerComponent('LoginView', () => LoginView, store, Provider);
Navigation.registerComponent('MentionedMessagesView', () => MentionedMessagesView, store, Provider);
Navigation.registerComponent('NewMessageView', () => NewMessageView, store, Provider);
Navigation.registerComponent('NewServerView', () => NewServerView, store, Provider);
Navigation.registerComponent('OAuthView', () => OAuthView, store, Provider);
Navigation.registerComponent('OnboardingView', () => OnboardingView, store, Provider);

View File

@ -30,14 +30,14 @@ describe('Welcome screen', () => {
await element(by.id('welcome-view-login')).tap();
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('login-view'))).toBeVisible();
await tapBack('Welcome');
await tapBack();
});
it('should navigate to register', async() => {
await element(by.id('welcome-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('register-view'))).toBeVisible();
await tapBack('Welcome');
await tapBack();
});
afterEach(async() => {

View File

@ -49,18 +49,18 @@ describe('Login screen', () => {
await element(by.id('login-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('register-view'))).toBeVisible();
await tapBack('Login');
await tapBack();
});
it('should navigate to forgot password', async() => {
await element(by.id('login-view-forgot-password')).tap();
await waitFor(element(by.id('forgot-password-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('forgot-password-view'))).toBeVisible();
await tapBack('Login');
await tapBack();
});
it('should navigate to welcome', async() => {
await tapBack('Welcome');
await tapBack();
await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('welcome-view'))).toBeVisible();
await element(by.id('welcome-view-login')).tap();

View File

@ -11,9 +11,9 @@ describe('Rooms list screen', () => {
await expect(element(by.id('rooms-list-view'))).toBeVisible();
});
it('should have rooms list', async() => {
await expect(element(by.id('rooms-list-view-list'))).toBeVisible();
});
// it('should have rooms list', async() => {
// await expect(element(by.id('rooms-list-view-list'))).toBeVisible();
// });
it('should have room item', async() => {
await expect(element(by.id('rooms-list-view-item-general'))).toExist();
@ -38,9 +38,12 @@ describe('Rooms list screen', () => {
describe('Usage', async() => {
it('should search room and navigate', async() => {
await element(by.id('rooms-list-view-list')).swipe('down');
await waitFor(element(by.id('rooms-list-view-search'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view-search'))).toBeVisible();
// await element(by.id('rooms-list-view-list')).swipe('down');
// await waitFor(element(by.id('rooms-list-view-search'))).toBeVisible().withTimeout(2000);
// await expect(element(by.id('rooms-list-view-search'))).toBeVisible();
await waitFor(element(by.id('rooms-list-view-search'))).toExist().withTimeout(2000);
await element(by.id('rooms-list-view-search')).replaceText('rocket.cat');
await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible();
@ -49,7 +52,7 @@ describe('Rooms list screen', () => {
await expect(element(by.id('room-view'))).toBeVisible();
await waitFor(element(by.text('rocket.cat'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('rocket.cat'))).toBeVisible();
await tapBack('Messages');
await tapBack(2);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
await element(by.id('rooms-list-view-search')).replaceText('');

View File

@ -3,37 +3,70 @@ const {
} = require('detox');
const { takeScreenshot } = require('./helpers/screenshot');
const data = require('./data');
const { tapBack } = require('./helpers/app');
const { tapBack, sleep } = require('./helpers/app');
describe('Create room screen', () => {
before(async() => {
await sleep(5000);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await device.reloadReactNative();
await element(by.id('rooms-list-view-create-channel')).tap();
await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000);
});
describe('New Message', async() => {
describe('Render', async() => {
it('should have select users screen', async() => {
await expect(element(by.id('select-users-view'))).toBeVisible();
it('should have new message screen', async() => {
await expect(element(by.id('new-message-view'))).toBeVisible();
});
it('should have search input', async() => {
await expect(element(by.id('select-users-view-search'))).toBeVisible();
await waitFor(element(by.id('new-message-view-search'))).toExist().withTimeout(2000);
await expect(element(by.id('new-message-view-search'))).toExist();
});
after(async() => {
takeScreenshot();
});
});
})
describe('Usage', async() => {
it('should back to rooms list', async() => {
await tapBack('Messages');
await tapBack();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
await element(by.id('rooms-list-view-create-channel')).tap();
await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000);
});
it('should search user and navigate', async() => {
await element(by.id('new-message-view-search')).replaceText('rocket.cat');
await waitFor(element(by.id('new-message-view-item-rocket.cat'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('new-message-view-item-rocket.cat'))).toBeVisible();
await element(by.id('new-message-view-item-rocket.cat')).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(10000);
await expect(element(by.id('room-view'))).toBeVisible();
await waitFor(element(by.text('rocket.cat'))).toBeVisible().withTimeout(60000);
await expect(element(by.text('rocket.cat'))).toBeVisible();
await tapBack(2);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await element(by.id('rooms-list-view-create-channel')).tap();
});
it('should navigate to select users', async() => {
await element(by.id('new-message-view-create-channel')).tap();
await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('select-users-view'))).toBeVisible();
});
after(async() => {
takeScreenshot();
});
})
});
describe('Select Users', async() => {
it('should search users', async() => {
await element(by.id('select-users-view-search')).replaceText('rocket.cat');
await waitFor(element(by.id(`select-users-view-item-rocket.cat`))).toBeVisible().withTimeout(10000);
@ -57,18 +90,26 @@ describe('Create room screen', () => {
await element(by.id('selected-users-view-submit')).tap();
await waitFor(element(by.id('create-channel-view'))).toBeVisible().withTimeout(5000);
await expect(element(by.id('create-channel-view'))).toBeVisible();
});
})
describe('Create Channel', async() => {
describe('Render', async() => {
it('should render all fields', async() => {
await expect(element(by.id('create-channel-name'))).toBeVisible();
await expect(element(by.id('create-channel-type'))).toBeVisible();
await expect(element(by.id('create-channel-readonly'))).toBeVisible();
await expect(element(by.id('create-channel-broadcast'))).toExist();
await expect(element(by.id('create-channel-submit'))).toExist();
});
})
})
describe('Usage', async() => {
it('should get invalid room', async() => {
await element(by.id('create-channel-name')).replaceText('general');
await element(by.id('create-channel-submit')).tap();
await waitFor(element(by.id('create-channel-error'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('create-channel-error'))).toBeVisible();
await waitFor(element(by.text(`A channel with name 'general' exists`))).toBeVisible().withTimeout(60000);
await expect(element(by.text(`A channel with name 'general' exists`))).toBeVisible();
await element(by.text('OK')).tap();
});
it('should create public room', async() => {
@ -79,14 +120,18 @@ describe('Create room screen', () => {
await expect(element(by.id('room-view'))).toBeVisible();
await waitFor(element(by.text(`public${ data.random }`))).toBeVisible().withTimeout(60000);
await expect(element(by.text(`public${ data.random }`))).toBeVisible();
await tapBack('Messages');
await tapBack(2);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible();
});
it('should create private room', async() => {
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await device.reloadReactNative();
await element(by.id('rooms-list-view-create-channel')).tap();
await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000);
await element(by.id('new-message-view-create-channel')).tap();
await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000);
await element(by.id('select-users-view-item-rocket.cat')).tap();
await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(5000);
@ -98,11 +143,12 @@ describe('Create room screen', () => {
await expect(element(by.id('room-view'))).toBeVisible();
await waitFor(element(by.text(`private${ data.random }`))).toBeVisible().withTimeout(60000);
await expect(element(by.text(`private${ data.random }`))).toBeVisible();
await tapBack('Messages');
await tapBack(2);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible();
});
})
afterEach(async() => {
takeScreenshot();

View File

@ -74,7 +74,7 @@ describe('Room screen', () => {
describe('Usage', async() => {
describe('Header', async() => {
it('should back to rooms list', async() => {
await tapBack('Messages');
await tapBack(2);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
await navigateToRoom();
@ -84,7 +84,7 @@ describe('Room screen', () => {
await element(by.id('room-view-header-actions')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('room-actions-view'))).toBeVisible();
await tapBack(`private${ data.random }`);
await tapBack();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
});
});
@ -287,7 +287,8 @@ describe('Room screen', () => {
});
after(async() => {
await tapBack('Messages');
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
await tapBack(2);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
});

View File

@ -21,15 +21,16 @@ async function navigateToRoomActions(type) {
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000);
}
async function backToActions() {
await tapBack('Actions');
await waitFor(element(by.id('rooms-actions-view'))).toBeVisible().withTimeout(2000);
async function backToActions(index = 0) {
await tapBack(index);
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('room-actions-view'))).toBeVisible();
}
async function backToRoomsList(room) {
await tapBack(room);
async function backToRoomsList() {
await tapBack();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
await tapBack('Messages');
await tapBack(2);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
}
@ -98,7 +99,7 @@ describe('Room actions screen', () => {
});
after(async() => {
await backToRoomsList('rocket.cat');
await backToRoomsList();
});
});
@ -247,10 +248,10 @@ describe('Room actions screen', () => {
await element(by.id('room-actions-search')).tap();
await waitFor(element(by.id('search-messages-view'))).toExist().withTimeout(2000);
await expect(element(by.id('search-message-view-input'))).toBeVisible();
await element(by.id('search-message-view-input')).tap();
await element(by.id('search-message-view-input')).replaceText(`/${ data.random }message/`);
await waitFor(element(by.text(`${ data.random }message`).withAncestor(by.id('search-messages-view'))).atIndex(0)).toBeVisible().withTimeout(60000);
await expect(element(by.text(`${ data.random }message`).withAncestor(by.id('search-messages-view'))).atIndex(0)).toBeVisible();
await element(by.traits(['button'])).atIndex(0).tap();
await backToActions();
});
@ -284,7 +285,7 @@ describe('Room actions screen', () => {
await expect(element(by.text('You are the last owner. Please set new owner before leaving the room.'))).toBeVisible();
await takeScreenshot();
await element(by.text('OK')).tap();
await waitFor(element(by.id('rooms-actions-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
});
describe('Add User', async() => {
@ -304,7 +305,7 @@ describe('Room actions screen', () => {
await element(by.id('room-members-view-toggle-status')).tap();
await waitFor(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeVisible();
await backToActions();
await backToActions(1);
});
after(async() => {
@ -363,8 +364,8 @@ describe('Room actions screen', () => {
await expect(element(by.id('room-view'))).toBeVisible();
await waitFor(element(by.text(data.alternateUser))).toBeVisible().withTimeout(60000);
await expect(element(by.text(data.alternateUser))).toBeVisible();
await tapBack('Messages');
await waitFor(element(by.id('room-list-view'))).toBeVisible().withTimeout(2000);
await tapBack(2);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
});
afterEach(async() => {

View File

@ -158,7 +158,7 @@ describe('Room info screen', () => {
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
await tapBack('Room Info');
await tapBack();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-info-view-name'))).toHaveText(`${ room }new`).withTimeout(60000);
await expect(element(by.id('room-info-view-name'))).toHaveText(`${ room }new`);
@ -206,7 +206,7 @@ describe('Room info screen', () => {
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
await tapBack('Room Info');
await tapBack();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-info-view-description'))).toHaveText('new description').withTimeout(60000);
await expect(element(by.id('room-info-view-description'))).toHaveText('new description');
@ -223,7 +223,7 @@ describe('Room info screen', () => {
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
await tapBack('Room Info');
await tapBack();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-info-view-topic'))).toHaveText('new topic').withTimeout(60000);
await expect(element(by.id('room-info-view-topic'))).toHaveText('new topic');
@ -240,7 +240,7 @@ describe('Room info screen', () => {
await expect(element(by.text('Settings succesfully changed!'))).toBeVisible();
await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000);
await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible();
await tapBack('Room Info');
await tapBack();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-info-view-announcement'))).toHaveText('new announcement').withTimeout(60000);
await expect(element(by.id('room-info-view-announcement'))).toHaveText('new announcement');

View File

@ -12,6 +12,8 @@ describe('Broadcast room', () => {
it('should create broadcast room', async() => {
await element(by.id('rooms-list-view-create-channel')).tap();
await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000);
await element(by.id('new-message-view-create-channel')).tap();
await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000);
await element(by.id(`select-users-view-item-${ data.alternateUser }`)).tap();
await waitFor(element(by.id(`selected-user-${ data.alternateUser }`))).toBeVisible().withTimeout(5000);
@ -30,14 +32,14 @@ describe('Broadcast room', () => {
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('room-info-view-broadcast'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('room-info-view-broadcast'))).toBeVisible();
await tapBack('Actions');
await tapBack(1);
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
await tapBack(`broadcast${ data.random }`);
await tapBack();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
await tapBack('Messages');
await tapBack(2);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toBeVisible();
await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist();
});
it('should send message', async() => {
@ -51,7 +53,7 @@ describe('Broadcast room', () => {
});
it('should login as user without write message authorization and enter room', async() => {
await tapBack('Messages');
await tapBack(2);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
await logout();
@ -61,7 +63,8 @@ describe('Broadcast room', () => {
await element(by.id('login-view-submit')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
// await device.reloadReactNative(); // remove after fix logout
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
// await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
await element(by.id('rooms-list-view-search')).replaceText(`broadcast${ data.random }`);
await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toBeVisible();
await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap();
@ -107,7 +110,7 @@ describe('Broadcast room', () => {
after(async() => {
// log back as main test user and left screen on RoomsListView
await tapBack('Messages');
await tapBack(2);
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await logout();
await navigateToLogin();

View File

@ -36,12 +36,8 @@ async function logout() {
await expect(element(by.id('onboarding-view'))).toBeVisible();
}
async function tapBack(label) {
try {
return element(by.traits(['button']).and(by.label(label || 'Back'))).atIndex(0).tap();
} catch (err) {
return element(by.type('_UIModernBarButton').and(by.label(label || 'Back'))).tap();
}
async function tapBack(index) {
await element(by.type('_UIModernBarButton')).atIndex(index || 0).tap();
}
async function sleep(ms) {

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "plus.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "plus@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "plus@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "textinput_search.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "textinput_search@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "textinput_search@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

2
package-lock.json generated
View File

@ -15999,7 +15999,7 @@
}
},
"react-native-navigation": {
"version": "git+https://github.com/RocketChat/react-native-navigation.git#1a428f14ddda77511676d0c6d863083ce6225e32",
"version": "git+https://github.com/RocketChat/react-native-navigation.git#024095e7679afa0b4e9475f4ffce45d9e50ca5ad",
"from": "git+https://github.com/RocketChat/react-native-navigation.git",
"requires": {
"lodash": "4.x.x"