Improve RoomsList render time (#384)
<!-- INSTRUCTION: Keep the line below to notify all core developers about this new PR --> @RocketChat/ReactNative <!-- INSTRUCTION: Inform the issue number that this PR closes, or remove the line below --> <!-- INSTRUCTION: Tell us more about your PR with screen shots if you can --> - [x] Added FlatList.getItemLayout() to improve list render time - [x] Some texts were breaking lines at sidebar - [x] Removed onPress from links at RoomsListView - [x] Added eslint rule to prevent unused styles - [x] Fixed auto focus bug at CreateChannel and NewServer - [x] Fix change server bug - [x] Fixed a bug when resuming in ListServer - [x] I18n fixed - [x] Fixed a bug on actionsheet ref not being created - [x] Reply wasn't showing on Android - [x] Use Notification.Builder.setColor/getColor only after Android SDK 23 - [x] Listen to app state only when inside app - [x] Switched register push token position in order to improve login performance - [x] When deep link changes server, it doesn't refresh rooms list - [x] Added SafeAreaView in all views to improve iPhone X experience - [x] Subpath regex #388
This commit is contained in:
parent
90c777cd2b
commit
50eb03589a
|
@ -124,7 +124,8 @@ module.exports = {
|
|||
"prefer-const": 2,
|
||||
"object-shorthand": 2,
|
||||
"consistent-return": 0,
|
||||
"global-require": "off"
|
||||
"global-require": "off",
|
||||
"react-native/no-unused-styles": 2
|
||||
},
|
||||
"globals": {
|
||||
"__DEV__": true
|
||||
|
|
|
@ -72,6 +72,10 @@ import com.android.build.OutputFile
|
|||
* ]
|
||||
*/
|
||||
|
||||
project.ext.react = [
|
||||
entryFile: "index.android.js"
|
||||
]
|
||||
|
||||
apply from: "../../node_modules/react-native/react.gradle"
|
||||
|
||||
/**
|
||||
|
@ -98,7 +102,7 @@ android {
|
|||
minSdkVersion 19
|
||||
targetSdkVersion 27
|
||||
versionCode VERSIONCODE as Integer
|
||||
versionName "1"
|
||||
versionName "1.0.1"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="chat.rocket.reactnative"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
package="chat.rocket.reactnative">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
|
|
@ -46,10 +46,13 @@ public class CustomPushNotification extends PushNotification {
|
|||
.setContentText(message)
|
||||
.setStyle(new Notification.BigTextStyle().bigText(message))
|
||||
.setPriority(Notification.PRIORITY_HIGH)
|
||||
.setColor(mContext.getColor(R.color.notification_text))
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.setAutoCancel(true);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
notification.setColor(mContext.getColor(R.color.notification_text));
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
|
||||
CHANNEL_NAME,
|
||||
|
|
|
@ -43,7 +43,7 @@ public class MainApplication extends NavigationApplication implements INotificat
|
|||
|
||||
@Override
|
||||
public String getJSMainModuleName() {
|
||||
return "index";
|
||||
return "index.android";
|
||||
}
|
||||
|
||||
protected List<ReactPackage> getPackages() {
|
||||
|
|
|
@ -75,7 +75,8 @@ export const SELECTED_USERS = createRequestTypes('SELECTED_USERS', ['ADD_USER',
|
|||
export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']);
|
||||
export const SERVER = createRequestTypes('SERVER', [
|
||||
...defaultTypes,
|
||||
'SELECT',
|
||||
'SELECT_SUCCESS',
|
||||
'SELECT_REQUEST',
|
||||
'CHANGED',
|
||||
'ADD'
|
||||
]);
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
import { SERVER } from './actionsTypes';
|
||||
|
||||
export function selectServer(server) {
|
||||
export function selectServerRequest(server) {
|
||||
return {
|
||||
type: SERVER.SELECT,
|
||||
type: SERVER.SELECT_REQUEST,
|
||||
server
|
||||
};
|
||||
}
|
||||
|
||||
export function selectServerSuccess(server) {
|
||||
return {
|
||||
type: SERVER.SELECT_SUCCESS,
|
||||
server
|
||||
};
|
||||
}
|
||||
|
||||
export function serverRequest(server) {
|
||||
return {
|
||||
type: SERVER.REQUEST,
|
||||
|
|
|
@ -13,6 +13,7 @@ const colors = {
|
|||
textColorSecondary: COLOR_TEXT
|
||||
};
|
||||
|
||||
/* eslint-disable react-native/no-unused-styles */
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
paddingHorizontal: 15,
|
||||
|
|
|
@ -66,13 +66,21 @@ export default class Loading extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.opacityAnimation.stop();
|
||||
this.scaleAnimation.stop();
|
||||
if (this.opacityAnimation && this.opacityAnimation.stop) {
|
||||
this.opacityAnimation.stop();
|
||||
}
|
||||
if (this.scaleAnimation && this.scaleAnimation.stop) {
|
||||
this.scaleAnimation.stop();
|
||||
}
|
||||
}
|
||||
|
||||
startAnimations() {
|
||||
this.opacityAnimation.start();
|
||||
this.scaleAnimation.start();
|
||||
if (this.opacityAnimation && this.opacityAnimation.start) {
|
||||
this.opacityAnimation.start();
|
||||
}
|
||||
if (this.scaleAnimation && this.scaleAnimation.start) {
|
||||
this.scaleAnimation.start();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -121,7 +121,9 @@ export default class MessageActions extends React.Component {
|
|||
this.DELETE_INDEX = this.options.length - 1;
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.ActionSheet.show();
|
||||
if (this.actionSheet && this.actionSheet.show) {
|
||||
this.actionSheet.show();
|
||||
}
|
||||
Vibration.vibrate(50);
|
||||
});
|
||||
}
|
||||
|
@ -301,7 +303,7 @@ export default class MessageActions extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<ActionSheet
|
||||
ref={o => this.ActionSheet = o}
|
||||
ref={o => this.actionSheet = o}
|
||||
title={I18n.t('Message_actions')}
|
||||
testID='message-actions'
|
||||
options={this.options}
|
||||
|
|
|
@ -27,7 +27,9 @@ export default class FilesActions extends Component {
|
|||
this.LIBRARY_INDEX = 2;
|
||||
|
||||
setTimeout(() => {
|
||||
this.ActionSheet.show();
|
||||
if (this.actionSheet && this.actionSheet.show) {
|
||||
this.actionSheet.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -49,7 +51,7 @@ export default class FilesActions extends Component {
|
|||
render() {
|
||||
return (
|
||||
<ActionSheet
|
||||
ref={o => this.ActionSheet = o}
|
||||
ref={o => this.actionSheet = o}
|
||||
options={this.options}
|
||||
cancelButtonIndex={this.CANCEL_INDEX}
|
||||
onPress={this.handleActionPress}
|
||||
|
|
|
@ -9,7 +9,6 @@ import Markdown from '../message/Markdown';
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
messageContainer: {
|
||||
|
@ -35,11 +34,6 @@ const styles = StyleSheet.create({
|
|||
lineHeight: 16,
|
||||
marginLeft: 5
|
||||
},
|
||||
content: {
|
||||
color: '#0C0D0F',
|
||||
fontSize: 16,
|
||||
lineHeight: 20
|
||||
},
|
||||
close: {
|
||||
marginRight: 15
|
||||
}
|
||||
|
|
|
@ -30,7 +30,9 @@ export default class MessageErrorActions extends React.Component {
|
|||
this.CANCEL_INDEX = 0;
|
||||
this.DELETE_INDEX = 1;
|
||||
this.RESEND_INDEX = 2;
|
||||
this.ActionSheet.show();
|
||||
if (this.actionSheet && this.actionSheet.show) {
|
||||
this.actionSheet.show();
|
||||
}
|
||||
}
|
||||
|
||||
handleResend = protectedFunction(() => RocketChat.resendMessage(this.props.actionMessage._id));
|
||||
|
@ -59,7 +61,7 @@ export default class MessageErrorActions extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<ActionSheet
|
||||
ref={o => this.ActionSheet = o}
|
||||
ref={o => this.actionSheet = o}
|
||||
title={I18n.t('Message_actions')}
|
||||
options={this.options}
|
||||
cancelButtonIndex={this.CANCEL_INDEX}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, AsyncStorage, SafeAreaView } from 'react-native';
|
||||
import { ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import database from '../lib/realm';
|
||||
import { selectServer } from '../actions/server';
|
||||
import { selectServerRequest } from '../actions/server';
|
||||
import { logout } from '../actions/login';
|
||||
import { appStart } from '../actions';
|
||||
import Avatar from '../containers/Avatar';
|
||||
import Status from '../containers/status';
|
||||
import Touch from '../utils/touch';
|
||||
|
@ -19,8 +18,9 @@ import I18n from '../i18n';
|
|||
import { NavigationActions } from '../Navigation';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
selected: {
|
||||
backgroundColor: 'rgba(0, 0, 0, .04)'
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff'
|
||||
},
|
||||
item: {
|
||||
flexDirection: 'row',
|
||||
|
@ -31,9 +31,6 @@ const styles = StyleSheet.create({
|
|||
width: 30,
|
||||
alignItems: 'center'
|
||||
},
|
||||
itemLeftOpacity: {
|
||||
opacity: 0.62
|
||||
},
|
||||
itemText: {
|
||||
marginVertical: 16,
|
||||
fontWeight: 'bold',
|
||||
|
@ -88,18 +85,16 @@ const keyExtractor = item => item.id;
|
|||
username: state.login.user && state.login.user.username
|
||||
}
|
||||
}), dispatch => ({
|
||||
selectServer: server => dispatch(selectServer(server)),
|
||||
logout: () => dispatch(logout()),
|
||||
appStart: () => dispatch(appStart('outside'))
|
||||
selectServerRequest: server => dispatch(selectServerRequest(server)),
|
||||
logout: () => dispatch(logout())
|
||||
}))
|
||||
export default class Sidebar extends Component {
|
||||
static propTypes = {
|
||||
navigator: PropTypes.object,
|
||||
server: PropTypes.string.isRequired,
|
||||
selectServer: PropTypes.func.isRequired,
|
||||
selectServerRequest: PropTypes.func.isRequired,
|
||||
user: PropTypes.object,
|
||||
logout: PropTypes.func.isRequired,
|
||||
appStart: PropTypes.func
|
||||
logout: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -127,7 +122,7 @@ export default class Sidebar extends Component {
|
|||
}
|
||||
|
||||
onPressItem = (item) => {
|
||||
this.props.selectServer(item.id);
|
||||
this.props.selectServerRequest(item.id);
|
||||
}
|
||||
|
||||
setStatus = () => {
|
||||
|
@ -230,11 +225,7 @@ export default class Sidebar extends Component {
|
|||
this.closeDrawer();
|
||||
this.toggleServers();
|
||||
if (this.props.server !== item.id) {
|
||||
this.props.selectServer(item.id);
|
||||
const token = await AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ item.id }`);
|
||||
if (!token) {
|
||||
this.props.appStart();
|
||||
}
|
||||
this.props.selectServerRequest(item.id);
|
||||
}
|
||||
},
|
||||
testID: `sidebar-${ item.id }`
|
||||
|
@ -314,8 +305,8 @@ export default class Sidebar extends Component {
|
|||
return null;
|
||||
}
|
||||
return (
|
||||
<ScrollView style={{ backgroundColor: '#fff' }}>
|
||||
<SafeAreaView testID='sidebar'>
|
||||
<ScrollView style={styles.container}>
|
||||
<SafeAreaView testID='sidebar' style={styles.container}>
|
||||
<Touch
|
||||
onPress={() => this.toggleServers()}
|
||||
underlayColor='rgba(255, 255, 255, 0.5)'
|
||||
|
@ -331,9 +322,9 @@ export default class Sidebar extends Component {
|
|||
<View style={styles.headerTextContainer}>
|
||||
<View style={styles.headerUsername}>
|
||||
<Status style={styles.status} id={user.id} />
|
||||
<Text>{user.username}</Text>
|
||||
<Text numberOfLines={1}>{user.username}</Text>
|
||||
</View>
|
||||
<Text style={styles.currentServerText}>{server}</Text>
|
||||
<Text style={styles.currentServerText} numberOfLines={1}>{server}</Text>
|
||||
</View>
|
||||
<Icon
|
||||
name={this.state.showServers ? 'keyboard-arrow-up' : 'keyboard-arrow-down'}
|
||||
|
|
|
@ -1,25 +1,12 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { TouchableOpacity, StyleSheet } from 'react-native';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import PhotoModal from './PhotoModal';
|
||||
import Markdown from './Markdown';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
flex: 1,
|
||||
flexDirection: 'column'
|
||||
},
|
||||
image: {
|
||||
width: 320,
|
||||
height: 200
|
||||
// resizeMode: 'cover'
|
||||
},
|
||||
labelContainer: {
|
||||
alignItems: 'flex-start'
|
||||
}
|
||||
});
|
||||
import styles from './styles';
|
||||
|
||||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||
|
@ -55,7 +42,7 @@ export default class extends React.PureComponent {
|
|||
<TouchableOpacity
|
||||
key='image'
|
||||
onPress={() => this._onPressButton()}
|
||||
style={styles.button}
|
||||
style={styles.imageContainer}
|
||||
>
|
||||
<FastImage
|
||||
style={styles.image}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Text, Platform } from 'react-native';
|
||||
import { Text, Platform, Image } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { emojify } from 'react-emojione';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -66,6 +66,10 @@ export default class Markdown extends React.Component {
|
|||
return null;
|
||||
},
|
||||
blocklink: () => {},
|
||||
image: node => (
|
||||
// TODO: should use Image component
|
||||
<Image key={node.key} style={styles.inlineImage} source={{ uri: node.attributes.src }} />
|
||||
),
|
||||
...rules
|
||||
}}
|
||||
style={{
|
||||
|
|
|
@ -17,13 +17,6 @@ const styles = StyleSheet.create({
|
|||
marginTop: 2,
|
||||
alignSelf: 'flex-end'
|
||||
},
|
||||
quoteSign: {
|
||||
borderWidth: 2,
|
||||
borderRadius: 4,
|
||||
borderColor: '#a0a0a0',
|
||||
height: '100%',
|
||||
marginRight: 5
|
||||
},
|
||||
attachmentContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'column'
|
||||
|
|
|
@ -12,13 +12,6 @@ const styles = StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
marginVertical: 2
|
||||
},
|
||||
quoteSign: {
|
||||
borderWidth: 2,
|
||||
borderRadius: 4,
|
||||
borderColor: '#a0a0a0',
|
||||
height: '100%',
|
||||
marginRight: 5
|
||||
},
|
||||
image: {
|
||||
height: 80,
|
||||
width: 80,
|
||||
|
|
|
@ -107,5 +107,19 @@ export default StyleSheet.create({
|
|||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
imageContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'column'
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
maxWidth: 400,
|
||||
height: 300
|
||||
},
|
||||
inlineImage: {
|
||||
width: 300,
|
||||
height: 300,
|
||||
resizeMode: 'contain'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -73,6 +73,8 @@ export default class Socket extends EventEmitter {
|
|||
this.subscriptions = {};
|
||||
this.ddp = new EventEmitter();
|
||||
this._logged = false;
|
||||
this.forceDisconnect = false;
|
||||
this.connected = false;
|
||||
const waitTimeout = () => setTimeout(() => {
|
||||
// this.connection.ping();
|
||||
this.send({ msg: 'ping' }).catch(e => log('ping', e));
|
||||
|
@ -164,8 +166,11 @@ export default class Socket extends EventEmitter {
|
|||
}
|
||||
}
|
||||
async send(obj, ignore) {
|
||||
console.log('send');
|
||||
console.log('send', obj);
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.connected) {
|
||||
return reject();
|
||||
}
|
||||
this.id += 1;
|
||||
const id = obj.id || `ddp-react-native-${ this.id }`;
|
||||
// console.log('send', { ...obj, id });
|
||||
|
@ -209,15 +214,19 @@ export default class Socket extends EventEmitter {
|
|||
this.connection = new WebSocket(`${ this.url }/websocket`, null);
|
||||
|
||||
this.connection.onopen = async() => {
|
||||
this.connected = true;
|
||||
this.forceDisconnect = false;
|
||||
this.emit('open');
|
||||
resolve();
|
||||
this.ddp.emit('open');
|
||||
console.log(`Connected to: ${ this.url }`);
|
||||
if (this._login) {
|
||||
return this.login(this._login).catch(e => console.warn(e));
|
||||
}
|
||||
};
|
||||
this.connection.onclose = debounce((e) => {
|
||||
this.emit('disconnected', e);
|
||||
this.connected = false;
|
||||
}, 300);
|
||||
this.connection.onmessage = (e) => {
|
||||
try {
|
||||
|
@ -238,13 +247,17 @@ export default class Socket extends EventEmitter {
|
|||
.finally(() => this.subscriptions = {});
|
||||
}
|
||||
disconnect() {
|
||||
this._close();
|
||||
this._logged = false;
|
||||
this._login = null;
|
||||
this.subscriptions = {};
|
||||
this.forceDisconnect = true;
|
||||
this._close();
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
async reconnect() {
|
||||
if (this._timer) {
|
||||
if (this._timer || this.forceDisconnect) {
|
||||
return;
|
||||
}
|
||||
this._close();
|
||||
|
|
|
@ -15,6 +15,9 @@ const getLastMessage = () => {
|
|||
|
||||
export default async function() {
|
||||
try {
|
||||
if (!this.ddp.status) {
|
||||
return;
|
||||
}
|
||||
const lastMessage = getLastMessage();
|
||||
let emojis = await this.ddp.call('listEmojiCustom');
|
||||
emojis = emojis.filter(emoji => !lastMessage || emoji._updatedAt > lastMessage);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import moment from 'moment';
|
||||
|
||||
import parseUrls from './parseUrls';
|
||||
|
||||
function normalizeAttachments(msg) {
|
||||
|
@ -6,6 +8,9 @@ function normalizeAttachments(msg) {
|
|||
}
|
||||
msg.attachments = msg.attachments.map((att) => {
|
||||
att.fields = att.fields || [];
|
||||
if (att.ts) {
|
||||
att.ts = moment(att.ts).toDate();
|
||||
}
|
||||
att = normalizeAttachments(att);
|
||||
return att;
|
||||
});
|
||||
|
|
|
@ -299,44 +299,6 @@ const schema = [
|
|||
uploadsSchema
|
||||
];
|
||||
|
||||
// class DebouncedDb {
|
||||
// constructor(db) {
|
||||
// this.database = db;
|
||||
// }
|
||||
// deleteAll(...args) {
|
||||
// return this.database.write(() => this.database.deleteAll(...args));
|
||||
// }
|
||||
// delete(...args) {
|
||||
// return this.database.delete(...args);
|
||||
// }
|
||||
// write(fn) {
|
||||
// return fn();
|
||||
// }
|
||||
// create(...args) {
|
||||
// this.queue = this.queue || [];
|
||||
// if (this.timer) {
|
||||
// clearTimeout(this.timer);
|
||||
// this.timer = null;
|
||||
// }
|
||||
// this.timer = setTimeout(() => {
|
||||
// alert(this.queue.length);
|
||||
// this.database.write(() => {
|
||||
// this.queue.forEach(({ db, args }) => this.database.create(...args));
|
||||
// });
|
||||
//
|
||||
// this.timer = null;
|
||||
// return this.roles = [];
|
||||
// }, 1000);
|
||||
//
|
||||
// this.queue.push({
|
||||
// db: this.database,
|
||||
// args
|
||||
// });
|
||||
// }
|
||||
// objects(...args) {
|
||||
// return this.database.objects(...args);
|
||||
// }
|
||||
// }
|
||||
class DB {
|
||||
databases = {
|
||||
serversDB: new Realm({
|
||||
|
@ -376,9 +338,3 @@ class DB {
|
|||
}
|
||||
}
|
||||
export default new DB();
|
||||
|
||||
// realm.write(() => {
|
||||
// realm.create('servers', { id: 'https://open.rocket.chat', current: false }, true);
|
||||
// realm.create('servers', { id: 'http://localhost:3000', current: false }, true);
|
||||
// realm.create('servers', { id: 'http://10.0.2.2:3000', current: false }, true);
|
||||
// });
|
||||
|
|
|
@ -85,16 +85,14 @@ const RocketChat = {
|
|||
return (headers['x-instance-id'] != null && headers['x-instance-id'].length > 0) || (headers['X-Instance-ID'] != null && headers['X-Instance-ID'].length > 0);
|
||||
},
|
||||
async testServer(url) {
|
||||
if (/^(https?:\/\/)?(((\w|[0-9-_])+(\.(\w|[0-9-_])+)+)|localhost)(:\d+)?$/.test(url)) {
|
||||
try {
|
||||
let response = await RNFetchBlob.fetch('HEAD', url);
|
||||
response = response.respInfo;
|
||||
if (response.status === 200 && RocketChat._hasInstanceId(response.headers)) {
|
||||
return url;
|
||||
}
|
||||
} catch (e) {
|
||||
log('testServer', e);
|
||||
try {
|
||||
let response = await RNFetchBlob.fetch('HEAD', url);
|
||||
response = response.respInfo;
|
||||
if (response.status === 200 && RocketChat._hasInstanceId(response.headers)) {
|
||||
return url;
|
||||
}
|
||||
} catch (e) {
|
||||
log('testServer', e);
|
||||
}
|
||||
throw new Error({ error: 'invalid server' });
|
||||
},
|
||||
|
@ -141,6 +139,7 @@ const RocketChat = {
|
|||
const userInfo = await this.userInfo({ token: user.token, userId: user.id });
|
||||
user = { ...user, ...userInfo.user };
|
||||
}
|
||||
RocketChat.registerPushToken(user.id);
|
||||
return reduxStore.dispatch(loginSuccess(user));
|
||||
} catch (e) {
|
||||
log('rocketchat.loginSuccess', e);
|
||||
|
@ -154,9 +153,9 @@ const RocketChat = {
|
|||
}
|
||||
|
||||
this.ddp = new Ddp(url, login);
|
||||
if (login) {
|
||||
protectedFunction(() => RocketChat.getRooms());
|
||||
}
|
||||
// if (login) {
|
||||
// protectedFunction(() => RocketChat.getRooms());
|
||||
// }
|
||||
|
||||
this.ddp.on('login', protectedFunction(() => reduxStore.dispatch(loginRequest())));
|
||||
|
||||
|
@ -198,84 +197,6 @@ const RocketChat = {
|
|||
return reduxStore.dispatch(someoneTyping({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] }));
|
||||
}));
|
||||
|
||||
// this.ddp.on('stream-notify-logged', (ddpMessage) => {
|
||||
// // this entire logic needs a better solution
|
||||
// // we're using it only because our image cache lib doesn't support clear cache
|
||||
// if (ddpMessage.fields && ddpMessage.fields.eventName === 'updateAvatar') {
|
||||
// const { args } = ddpMessage.fields;
|
||||
// InteractionManager.runAfterInteractions(() =>
|
||||
// args.forEach((arg) => {
|
||||
// const user = database.objects('users').filtered('username = $0', arg.username);
|
||||
// if (user.length > 0) {
|
||||
// database.write(() => {
|
||||
// user[0].avatarVersion += 1;
|
||||
// });
|
||||
// }
|
||||
// }));
|
||||
// }
|
||||
// });
|
||||
|
||||
// this.ddp.on('stream-notify-user', protectedFunction((ddpMessage) => {
|
||||
// console.warn('rc.stream-notify-user')
|
||||
// const [type, data] = ddpMessage.fields.args;
|
||||
// const [, ev] = ddpMessage.fields.eventName.split('/');
|
||||
// if (/subscriptions/.test(ev)) {
|
||||
// if (data.roles) {
|
||||
// data.roles = data.roles.map(role => ({ value: role }));
|
||||
// }
|
||||
// if (data.blocker) {
|
||||
// data.blocked = true;
|
||||
// } else {
|
||||
// data.blocked = false;
|
||||
// }
|
||||
// if (data.mobilePushNotifications === 'nothing') {
|
||||
// data.notifications = true;
|
||||
// } else {
|
||||
// data.notifications = false;
|
||||
// }
|
||||
// database.write(() => {
|
||||
// database.create('subscriptions', data, true);
|
||||
// });
|
||||
// }
|
||||
// if (/rooms/.test(ev) && type === 'updated') {
|
||||
// const sub = database.objects('subscriptions').filtered('rid == $0', data._id)[0];
|
||||
|
||||
// database.write(() => {
|
||||
// sub.roomUpdatedAt = data._updatedAt;
|
||||
// sub.lastMessage = normalizeMessage(data.lastMessage);
|
||||
// sub.ro = data.ro;
|
||||
// sub.description = data.description;
|
||||
// sub.topic = data.topic;
|
||||
// sub.announcement = data.announcement;
|
||||
// sub.reactWhenReadOnly = data.reactWhenReadOnly;
|
||||
// sub.archived = data.archived;
|
||||
// sub.joinCodeRequired = data.joinCodeRequired;
|
||||
// if (data.muted) {
|
||||
// sub.muted = data.muted.map(m => ({ value: m }));
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// if (/message/.test(ev)) {
|
||||
// const [args] = ddpMessage.fields.args;
|
||||
// const _id = Random.id();
|
||||
// const message = {
|
||||
// _id,
|
||||
// rid: args.rid,
|
||||
// msg: args.msg,
|
||||
// ts: new Date(),
|
||||
// _updatedAt: new Date(),
|
||||
// status: messagesStatus.SENT,
|
||||
// u: {
|
||||
// _id,
|
||||
// username: 'rocket.cat'
|
||||
// }
|
||||
// };
|
||||
// requestAnimationFrame(() => database.write(() => {
|
||||
// database.create('messages', message, true);
|
||||
// }));
|
||||
// }
|
||||
// }));
|
||||
|
||||
this.ddp.on('rocketchat_starred_message', protectedFunction((ddpMessage) => {
|
||||
if (ddpMessage.msg === 'added') {
|
||||
this.starredMessages = this.starredMessages || [];
|
||||
|
|
|
@ -47,16 +47,6 @@ const styles = StyleSheet.create({
|
|||
color: '#444',
|
||||
marginRight: 8
|
||||
},
|
||||
lastMessage: {
|
||||
flex: 1,
|
||||
flexShrink: 1,
|
||||
marginRight: 8,
|
||||
maxHeight: 20,
|
||||
overflow: 'hidden',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
alert: {
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
|
@ -268,6 +258,11 @@ export default class RoomItem extends React.Component {
|
|||
<Text key={node.key}>
|
||||
#{node.content}
|
||||
</Text>
|
||||
),
|
||||
link: (node, children) => (
|
||||
<Text key={node.key}>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -6,7 +6,8 @@ const initialState = {
|
|||
errorMessage: '',
|
||||
failure: false,
|
||||
server: '',
|
||||
adding: false
|
||||
adding: false,
|
||||
loading: true
|
||||
};
|
||||
|
||||
|
||||
|
@ -38,11 +39,18 @@ export default function server(state = initialState, action) {
|
|||
...state,
|
||||
adding: true
|
||||
};
|
||||
case SERVER.SELECT:
|
||||
case SERVER.SELECT_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
server: action.server,
|
||||
adding: false
|
||||
loading: true
|
||||
};
|
||||
case SERVER.SELECT_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
server: action.server,
|
||||
adding: false,
|
||||
loading: false
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { takeLatest, take, select, put } from 'redux-saga/effects';
|
|||
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import { appStart } from '../actions';
|
||||
import { selectServer, addServer } from '../actions/server';
|
||||
import { selectServerRequest, addServer } from '../actions/server';
|
||||
import database from '../lib/realm';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import { NavigationActions } from '../Navigation';
|
||||
|
@ -68,7 +68,7 @@ const handleOpen = function* handleOpen({ params }) {
|
|||
if (!token) {
|
||||
yield put(appStart('outside'));
|
||||
} else {
|
||||
yield put(selectServer(deepLinkServer));
|
||||
yield put(selectServerRequest(deepLinkServer));
|
||||
yield take(types.METEOR.REQUEST);
|
||||
yield navigate({ params, sameServer: false });
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { AsyncStorage } from 'react-native';
|
|||
import { call, put, takeLatest } from 'redux-saga/effects';
|
||||
|
||||
import * as actions from '../actions';
|
||||
import { selectServer } from '../actions/server';
|
||||
import { selectServerRequest } from '../actions/server';
|
||||
import { restoreToken, setUser } from '../actions/login';
|
||||
import { APP } from '../actions/actionsTypes';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
@ -19,10 +19,7 @@ const restore = function* restore() {
|
|||
|
||||
const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer');
|
||||
if (currentServer) {
|
||||
yield put(selectServer(currentServer));
|
||||
if (token) {
|
||||
yield put(actions.appStart('inside'));
|
||||
}
|
||||
yield put(selectServerRequest(currentServer));
|
||||
|
||||
const login = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`);
|
||||
if (login) {
|
||||
|
|
|
@ -17,13 +17,14 @@ import {
|
|||
setUsernameRequest,
|
||||
setUsernameSuccess,
|
||||
forgotPasswordSuccess,
|
||||
forgotPasswordFailure
|
||||
forgotPasswordFailure,
|
||||
setUser
|
||||
} from '../actions/login';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import log from '../utils/log';
|
||||
import I18n from '../i18n';
|
||||
|
||||
const getUser = state => state.login;
|
||||
const getUser = state => state.login.user;
|
||||
const getServer = state => state.server.server;
|
||||
const getIsConnected = state => state.meteor.connected;
|
||||
|
||||
|
@ -36,15 +37,10 @@ const forgotPasswordCall = args => RocketChat.forgotPassword(args);
|
|||
|
||||
const handleLoginSuccess = function* handleLoginSuccess() {
|
||||
try {
|
||||
const [server, user] = yield all([select(getServer), select(getUser)]);
|
||||
const user = yield select(getUser);
|
||||
yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token);
|
||||
yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user));
|
||||
// const token = yield AsyncStorage.getItem('pushId');
|
||||
// if (token) {
|
||||
// yield RocketChat.registerPushToken(user.user.id, token);
|
||||
// }
|
||||
yield RocketChat.registerPushToken(user.user.id);
|
||||
if (!user.user.username || user.isRegistering) {
|
||||
yield put(setUser(user));
|
||||
if (!user.username || user.isRegistering) {
|
||||
yield put(registerIncomplete());
|
||||
} else {
|
||||
yield delay(300);
|
||||
|
@ -127,17 +123,22 @@ const watchLoginOpen = function* watchLoginOpen() {
|
|||
}
|
||||
const sub = yield RocketChat.subscribe('meteor.loginServiceConfiguration');
|
||||
yield take(types.LOGIN.CLOSE);
|
||||
yield sub.unsubscribe().catch(err => console.warn(err));
|
||||
if (sub) {
|
||||
yield sub.unsubscribe().catch(err => console.warn(err));
|
||||
}
|
||||
} catch (e) {
|
||||
log('watchLoginOpen', e);
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line require-yield
|
||||
const handleSetUser = function* handleSetUser(params) {
|
||||
const handleSetUser = function* handleSetUser() {
|
||||
const [server, user] = yield all([select(getServer), select(getUser)]);
|
||||
if (params.language) {
|
||||
I18n.locale = params.language;
|
||||
if (user) {
|
||||
// TODO: temporary... remove in future releases
|
||||
delete user.user;
|
||||
if (user.language) {
|
||||
I18n.locale = user.language;
|
||||
}
|
||||
}
|
||||
yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user));
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@ import { NavigationActions } from '../Navigation';
|
|||
import { SERVER } from '../actions/actionsTypes';
|
||||
import * as actions from '../actions';
|
||||
import { connectRequest } from '../actions/connect';
|
||||
import { serverSuccess, serverFailure, selectServer } from '../actions/server';
|
||||
import { serverSuccess, serverFailure, selectServerRequest, selectServerSuccess } from '../actions/server';
|
||||
import { setRoles } from '../actions/roles';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import database from '../lib/realm';
|
||||
|
@ -20,11 +20,14 @@ const validate = function* validate(server) {
|
|||
const handleSelectServer = function* handleSelectServer({ server }) {
|
||||
try {
|
||||
yield database.setActiveDB(server);
|
||||
|
||||
// yield RocketChat.disconnect();
|
||||
|
||||
yield call([AsyncStorage, 'setItem'], 'currentServer', server);
|
||||
// yield AsyncStorage.removeItem(RocketChat.TOKEN_KEY);
|
||||
const token = yield AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`);
|
||||
if (token) {
|
||||
yield put(actions.appStart('inside'));
|
||||
} else {
|
||||
yield put(actions.appStart('outside'));
|
||||
}
|
||||
|
||||
const settings = database.objects('settings');
|
||||
yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length))));
|
||||
const emojis = database.objects('customEmojis');
|
||||
|
@ -36,6 +39,7 @@ const handleSelectServer = function* handleSelectServer({ server }) {
|
|||
}, {})));
|
||||
|
||||
yield put(connectRequest());
|
||||
yield put(selectServerSuccess(server));
|
||||
} catch (e) {
|
||||
log('handleSelectServer', e);
|
||||
}
|
||||
|
@ -59,7 +63,7 @@ const addServer = function* addServer({ server }) {
|
|||
database.databases.serversDB.write(() => {
|
||||
database.databases.serversDB.create('servers', { id: server, current: false }, true);
|
||||
});
|
||||
yield put(selectServer(server));
|
||||
yield put(selectServerRequest(server));
|
||||
} catch (e) {
|
||||
log('addServer', e);
|
||||
}
|
||||
|
@ -67,7 +71,7 @@ const addServer = function* addServer({ server }) {
|
|||
|
||||
const root = function* root() {
|
||||
yield takeLatest(SERVER.REQUEST, validateServer);
|
||||
yield takeLatest(SERVER.SELECT, handleSelectServer);
|
||||
yield takeLatest(SERVER.SELECT_REQUEST, handleSelectServer);
|
||||
yield takeLatest(SERVER.ADD, addServer);
|
||||
};
|
||||
export default root;
|
||||
|
|
|
@ -5,6 +5,10 @@ import RocketChat from '../lib/rocketchat';
|
|||
import log from '../utils/log';
|
||||
|
||||
const appHasComeBackToForeground = function* appHasComeBackToForeground() {
|
||||
const appRoot = yield select(state => state.app.root);
|
||||
if (appRoot === 'outside') {
|
||||
return;
|
||||
}
|
||||
const auth = yield select(state => state.login.isAuthenticated);
|
||||
if (!auth) {
|
||||
return;
|
||||
|
@ -17,6 +21,10 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() {
|
|||
};
|
||||
|
||||
const appHasComeBackToBackground = function* appHasComeBackToBackground() {
|
||||
const appRoot = yield select(state => state.app.root);
|
||||
if (appRoot === 'outside') {
|
||||
return;
|
||||
}
|
||||
const auth = yield select(state => state.login.isAuthenticated);
|
||||
if (!auth) {
|
||||
return;
|
||||
|
|
|
@ -38,6 +38,12 @@ export default class CreateChannelView extends LoggedView {
|
|||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
setTimeout(() => {
|
||||
this.channelNameRef.focus();
|
||||
}, 600);
|
||||
}
|
||||
|
||||
submit = () => {
|
||||
if (!this.state.channelName.trim() || this.props.createChannel.isFetching) {
|
||||
return;
|
||||
|
@ -138,12 +144,12 @@ export default class CreateChannelView extends LoggedView {
|
|||
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
|
||||
<SafeAreaView testID='create-channel-view'>
|
||||
<RCTextInput
|
||||
inputRef={ref => this.channelNameRef = ref}
|
||||
label={I18n.t('Channel_Name')}
|
||||
value={this.state.channelName}
|
||||
onChangeText={channelName => this.setState({ channelName })}
|
||||
placeholder={I18n.t('Type_the_channel_name_here')}
|
||||
returnKeyType='done'
|
||||
autoFocus
|
||||
testID='create-channel-name'
|
||||
/>
|
||||
{this.renderChannelNameError()}
|
||||
|
|
|
@ -77,8 +77,8 @@ export default class ForgotPasswordView extends LoggedView {
|
|||
keyboardVerticalOffset={128}
|
||||
>
|
||||
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
|
||||
<SafeAreaView testID='forgot-password-view'>
|
||||
<View style={styles.loginView}>
|
||||
<SafeAreaView style={styles.container} testID='forgot-password-view'>
|
||||
<View>
|
||||
<TextInput
|
||||
inputStyle={this.state.invalidEmail ? { borderColor: 'red' } : {}}
|
||||
label={I18n.t('Email')}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import React from 'react';
|
||||
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text, SectionList, StyleSheet } from 'react-native';
|
||||
import { View, Text, SectionList, StyleSheet, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import LoggedView from './View';
|
||||
import { selectServer } from '../actions/server';
|
||||
import { selectServerRequest } from '../actions/server';
|
||||
import database from '../lib/realm';
|
||||
import Fade from '../animations/fade';
|
||||
import Touch from '../utils/touch';
|
||||
|
@ -14,27 +13,9 @@ import I18n from '../i18n';
|
|||
import { iconsMap } from '../Icons';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
view: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: '#fff'
|
||||
},
|
||||
input: {
|
||||
height: 40,
|
||||
borderColor: '#aaa',
|
||||
margin: 20,
|
||||
padding: 5,
|
||||
borderWidth: 0,
|
||||
backgroundColor: '#f8f8f8'
|
||||
},
|
||||
text: {
|
||||
textAlign: 'center',
|
||||
color: '#888'
|
||||
},
|
||||
container: {
|
||||
flex: 1
|
||||
flex: 1,
|
||||
backgroundColor: '#fff'
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
|
@ -67,19 +48,20 @@ const styles = StyleSheet.create({
|
|||
login: state.login,
|
||||
connected: state.meteor.connected
|
||||
}), dispatch => ({
|
||||
selectServer: server => dispatch(selectServer(server))
|
||||
selectServerRequest: server => dispatch(selectServerRequest(server))
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class ListServerView extends LoggedView {
|
||||
static propTypes = {
|
||||
navigator: PropTypes.object,
|
||||
login: PropTypes.object.isRequired,
|
||||
selectServer: PropTypes.func.isRequired,
|
||||
selectServerRequest: PropTypes.func.isRequired,
|
||||
server: PropTypes.string
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super('ListServerView', props);
|
||||
this.focused = true;
|
||||
this.state = {
|
||||
sections: []
|
||||
};
|
||||
|
@ -102,8 +84,19 @@ export default class ListServerView extends LoggedView {
|
|||
this.jumpToSelectedServer();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.server !== nextProps.server && nextProps.server && !this.props.login.isRegistering) {
|
||||
this.timeout = setTimeout(() => {
|
||||
this.openLogin(nextProps.server);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.data.removeAllListeners();
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
onNavigatorEvent(event) {
|
||||
|
@ -114,6 +107,8 @@ export default class ListServerView extends LoggedView {
|
|||
title: I18n.t('New_Server')
|
||||
});
|
||||
}
|
||||
} else if (event.type === 'ScreenChangedEvent') {
|
||||
this.focused = event.id === 'didAppear' || event.id === 'onActivityResumed';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,14 +129,16 @@ export default class ListServerView extends LoggedView {
|
|||
};
|
||||
|
||||
openLogin = (server) => {
|
||||
this.props.navigator.push({
|
||||
screen: 'LoginSignupView',
|
||||
title: server
|
||||
});
|
||||
if (this.focused) {
|
||||
this.props.navigator.push({
|
||||
screen: 'LoginSignupView',
|
||||
title: server
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
selectAndNavigateTo = (server) => {
|
||||
this.props.selectServer(server);
|
||||
this.props.selectServerRequest(server);
|
||||
this.openLogin(server);
|
||||
}
|
||||
|
||||
|
@ -193,7 +190,7 @@ export default class ListServerView extends LoggedView {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.view} testID='list-server-view'>
|
||||
<SafeAreaView style={styles.container} testID='list-server-view'>
|
||||
<SectionList
|
||||
style={styles.list}
|
||||
sections={this.state.sections}
|
||||
|
@ -202,7 +199,7 @@ export default class ListServerView extends LoggedView {
|
|||
keyExtractor={item => item.id}
|
||||
ItemSeparatorComponent={this.renderSeparator}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, View, ScrollView, TouchableOpacity, LayoutAnimation, Image, StyleSheet } from 'react-native';
|
||||
import { Text, View, ScrollView, TouchableOpacity, LayoutAnimation, Image, StyleSheet, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
|
@ -279,7 +279,7 @@ export default class LoginSignupView extends LoggedView {
|
|||
style={[sharedStyles.container, sharedStyles.containerScrollView]}
|
||||
{...scrollPersistTaps}
|
||||
>
|
||||
<View testID='welcome-view'>
|
||||
<SafeAreaView style={sharedStyles.container} testID='welcome-view'>
|
||||
<View style={styles.container}>
|
||||
<Image
|
||||
source={require('../static/images/logo.png')}
|
||||
|
@ -307,7 +307,7 @@ export default class LoginSignupView extends LoggedView {
|
|||
{this.renderServices()}
|
||||
</View>
|
||||
<Loading visible={this.props.isFetching} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Keyboard, Text, ScrollView, View } from 'react-native';
|
||||
import { Keyboard, Text, ScrollView, View, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { Answers } from 'react-native-fabric';
|
||||
|
||||
|
@ -106,7 +106,7 @@ export default class LoginView extends LoggedView {
|
|||
key='login-view'
|
||||
>
|
||||
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
|
||||
<View testID='login-view'>
|
||||
<SafeAreaView style={styles.container} testID='login-view'>
|
||||
<Text style={[styles.loginText, styles.loginTitle]}>Login</Text>
|
||||
<TextInput
|
||||
label={I18n.t('Username')}
|
||||
|
@ -158,7 +158,7 @@ export default class LoginView extends LoggedView {
|
|||
|
||||
{this.props.failure ? <Text style={styles.error}>{this.props.reason}</Text> : null}
|
||||
<Loading visible={this.props.isFetching} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
</KeyboardView>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Text } from 'react-native';
|
||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import LoggedView from '../View';
|
||||
|
@ -102,10 +102,8 @@ export default class MentionedMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
return (
|
||||
[
|
||||
<SafeAreaView style={styles.list} testID='mentioned-messages-view'>
|
||||
<FlatList
|
||||
key='mentioned-messages-view-list'
|
||||
testID='mentioned-messages-view'
|
||||
data={messages}
|
||||
renderItem={this.renderItem}
|
||||
style={styles.list}
|
||||
|
@ -114,7 +112,7 @@ export default class MentionedMessagesView extends LoggedView {
|
|||
ListHeaderComponent={loading ? <RCActivityIndicator /> : null}
|
||||
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
|
||||
/>
|
||||
]
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, ScrollView, View, Keyboard } from 'react-native';
|
||||
import { Text, ScrollView, View, Keyboard, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { serverRequest, addServer } from '../actions/server';
|
||||
|
@ -40,6 +40,12 @@ export default class NewServerView extends LoggedView {
|
|||
props.validateServer(this.state.defaultServer); // Need to call because in case of submit with empty field
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
setTimeout(() => {
|
||||
this.input.focus();
|
||||
}, 600);
|
||||
}
|
||||
|
||||
onChangeText = (text) => {
|
||||
this.setState({ text });
|
||||
this.props.validateServer(this.completeUrl(text));
|
||||
|
@ -106,7 +112,7 @@ export default class NewServerView extends LoggedView {
|
|||
keyboardVerticalOffset={128}
|
||||
>
|
||||
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
|
||||
<View testID='new-server-view'>
|
||||
<SafeAreaView style={styles.container} testID='new-server-view'>
|
||||
<Text style={[styles.loginText, styles.loginTitle]}>{I18n.t('Sign_in_your_server')}</Text>
|
||||
<TextInput
|
||||
inputRef={e => this.input = e}
|
||||
|
@ -129,7 +135,7 @@ export default class NewServerView extends LoggedView {
|
|||
/>
|
||||
</View>
|
||||
<Loading visible={this.props.addingServer} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
</KeyboardView>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Text } from 'react-native';
|
||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
|
||||
|
@ -69,7 +69,9 @@ export default class PinnedMessagesView extends LoggedView {
|
|||
|
||||
onLongPress = (message) => {
|
||||
this.setState({ message });
|
||||
this.actionSheet.show();
|
||||
if (this.actionSheet && this.actionSheet.show) {
|
||||
this.actionSheet.show();
|
||||
}
|
||||
}
|
||||
|
||||
handleActionPress = (actionIndex) => {
|
||||
|
@ -126,10 +128,8 @@ export default class PinnedMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
return (
|
||||
[
|
||||
<SafeAreaView style={styles.list} testID='pinned-messages-view'>
|
||||
<FlatList
|
||||
key='pinned-messages-view-list'
|
||||
testID='pinned-messages-view'
|
||||
data={messages}
|
||||
renderItem={this.renderItem}
|
||||
style={styles.list}
|
||||
|
@ -137,16 +137,15 @@ export default class PinnedMessagesView extends LoggedView {
|
|||
onEndReached={this.moreData}
|
||||
ListHeaderComponent={loading ? <RCActivityIndicator /> : null}
|
||||
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
|
||||
/>,
|
||||
/>
|
||||
<ActionSheet
|
||||
key='pinned-messages-view-action-sheet'
|
||||
ref={o => this.actionSheet = o}
|
||||
title={I18n.t('Actions')}
|
||||
options={options}
|
||||
cancelButtonIndex={CANCEL_INDEX}
|
||||
onPress={this.handleActionPress}
|
||||
/>
|
||||
]
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { WebView } from 'react-native';
|
||||
import { WebView, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import styles from './Styles';
|
||||
|
||||
@connect(state => ({
|
||||
privacyPolicy: state.settings.Layout_Privacy_Policy
|
||||
}))
|
||||
|
@ -13,7 +15,9 @@ export default class PrivacyPolicyView extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<WebView source={{ html: this.props.privacyPolicy }} />
|
||||
<SafeAreaView style={styles.container}>
|
||||
<WebView originWhitelist={['*']} source={{ html: this.props.privacyPolicy, baseUrl: '' }} />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -378,7 +378,7 @@ export default class ProfileView extends LoggedView {
|
|||
testID='profile-view-list'
|
||||
{...scrollPersistTaps}
|
||||
>
|
||||
<SafeAreaView testID='profile-view'>
|
||||
<SafeAreaView style={sharedStyles.container} testID='profile-view'>
|
||||
<View style={styles.avatarContainer} testID='profile-view-avatar'>
|
||||
<Avatar
|
||||
text={username}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Keyboard, Text, View, ScrollView } from 'react-native';
|
||||
import { Keyboard, Text, View, ScrollView, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { registerSubmit, setUsernameSubmit } from '../actions/login';
|
||||
|
@ -212,7 +212,7 @@ export default class RegisterView extends LoggedView {
|
|||
return (
|
||||
<KeyboardView contentContainerStyle={styles.container}>
|
||||
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
|
||||
<View testID='register-view'>
|
||||
<SafeAreaView style={styles.container} testID='register-view'>
|
||||
<Text style={[styles.loginText, styles.loginTitle]}>{I18n.t('Sign_Up')}</Text>
|
||||
{this._renderRegister()}
|
||||
{this._renderUsername()}
|
||||
|
@ -223,7 +223,7 @@ export default class RegisterView extends LoggedView {
|
|||
: null
|
||||
}
|
||||
<Loading visible={this.props.login.isFetching} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
</KeyboardView>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, SectionList, Text, Alert } from 'react-native';
|
||||
import { View, SectionList, Text, Alert, SafeAreaView } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -394,7 +394,7 @@ export default class RoomActionsView extends LoggedView {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<View testID='room-actions-view'>
|
||||
<SafeAreaView style={styles.container} testID='room-actions-view'>
|
||||
<SectionList
|
||||
style={styles.container}
|
||||
stickySectionHeadersEnabled={false}
|
||||
|
@ -404,7 +404,7 @@ export default class RoomActionsView extends LoggedView {
|
|||
keyExtractor={item => item.name}
|
||||
testID='room-actions-list'
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { StyleSheet } from 'react-native';
|
|||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#F6F7F9'
|
||||
},
|
||||
sectionItem: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Text } from 'react-native';
|
||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import LoggedView from '../View';
|
||||
|
@ -98,10 +98,8 @@ export default class RoomFilesView extends LoggedView {
|
|||
|
||||
const { loading, loadingMore } = this.state;
|
||||
return (
|
||||
[
|
||||
<SafeAreaView style={styles.list} testID='room-files-view'>
|
||||
<FlatList
|
||||
key='room-files-view-list'
|
||||
testID='room-files-view'
|
||||
data={messages}
|
||||
renderItem={this.renderItem}
|
||||
style={styles.list}
|
||||
|
@ -110,7 +108,7 @@ export default class RoomFilesView extends LoggedView {
|
|||
ListHeaderComponent={loading ? <RCActivityIndicator /> : null}
|
||||
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
|
||||
/>
|
||||
]
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,8 +34,11 @@ const PERMISSIONS_ARRAY = [
|
|||
PERMISSION_DELETE_P
|
||||
];
|
||||
|
||||
@connect(null, dispatch => ({
|
||||
eraseRoom: rid => dispatch(eraseRoom(rid))
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
class RoomInfoEditView extends LoggedView {
|
||||
export default class RoomInfoEditView extends LoggedView {
|
||||
static propTypes = {
|
||||
rid: PropTypes.string,
|
||||
eraseRoom: PropTypes.func
|
||||
|
@ -263,7 +266,7 @@ class RoomInfoEditView extends LoggedView {
|
|||
testID='room-info-edit-view-list'
|
||||
{...scrollPersistTaps}
|
||||
>
|
||||
<SafeAreaView testID='room-info-edit-view'>
|
||||
<SafeAreaView style={sharedStyles.container} testID='room-info-edit-view'>
|
||||
<RCTextInput
|
||||
inputRef={(e) => { this.name = e; }}
|
||||
label={I18n.t('Name')}
|
||||
|
@ -398,9 +401,3 @@ class RoomInfoEditView extends LoggedView {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
eraseRoom: rid => dispatch(eraseRoom(rid))
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(RoomInfoEditView);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text, ScrollView } from 'react-native';
|
||||
import { View, Text, ScrollView, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import moment from 'moment';
|
||||
|
||||
|
@ -211,17 +211,19 @@ export default class RoomInfoView extends LoggedView {
|
|||
return <View />;
|
||||
}
|
||||
return (
|
||||
<ScrollView style={styles.container}>
|
||||
<View style={styles.avatarContainer} testID='room-info-view'>
|
||||
{this.renderAvatar(room, roomUser)}
|
||||
<View style={styles.roomTitleContainer}>{ getRoomTitle(room) }</View>
|
||||
</View>
|
||||
{!this.isDirect() ? this.renderItem('description', room) : null}
|
||||
{!this.isDirect() ? this.renderItem('topic', room) : null}
|
||||
{!this.isDirect() ? this.renderItem('announcement', room) : null}
|
||||
{this.isDirect() ? this.renderRoles() : null}
|
||||
{this.isDirect() ? this.renderTimezone(roomUser._id) : null}
|
||||
{room.broadcast ? this.renderBroadcast() : null}
|
||||
<ScrollView style={styles.scroll}>
|
||||
<SafeAreaView style={styles.container} testID='room-info-view'>
|
||||
<View style={styles.avatarContainer}>
|
||||
{this.renderAvatar(room, roomUser)}
|
||||
<View style={styles.roomTitleContainer}>{ getRoomTitle(room) }</View>
|
||||
</View>
|
||||
{!this.isDirect() ? this.renderItem('description', room) : null}
|
||||
{!this.isDirect() ? this.renderItem('topic', room) : null}
|
||||
{!this.isDirect() ? this.renderItem('announcement', room) : null}
|
||||
{this.isDirect() ? this.renderRoles() : null}
|
||||
{this.isDirect() ? this.renderTimezone(roomUser._id) : null}
|
||||
{room.broadcast ? this.renderBroadcast() : null}
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,10 @@ import { StyleSheet } from 'react-native';
|
|||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#ffffff'
|
||||
},
|
||||
scroll: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
backgroundColor: '#ffffff',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, TextInput, Vibration } from 'react-native';
|
||||
import { FlatList, View, TextInput, Vibration, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
|
||||
|
@ -122,7 +122,9 @@ export default class RoomMembersView extends LoggedView {
|
|||
}
|
||||
this.setState({ userLongPressed: user });
|
||||
Vibration.vibrate(50);
|
||||
this.ActionSheet.show();
|
||||
if (this.actionSheet && this.actionSheet.show) {
|
||||
this.actionSheet.show();
|
||||
}
|
||||
}
|
||||
|
||||
updateRoom = async() => {
|
||||
|
@ -202,10 +204,8 @@ export default class RoomMembersView extends LoggedView {
|
|||
render() {
|
||||
const { filtering, members, membersFiltered } = this.state;
|
||||
return (
|
||||
[
|
||||
<SafeAreaView style={styles.list} testID='room-members-view'>
|
||||
<FlatList
|
||||
key='room-members-view-list'
|
||||
testID='room-members-view'
|
||||
data={filtering ? membersFiltered : members}
|
||||
renderItem={this.renderItem}
|
||||
style={styles.list}
|
||||
|
@ -213,16 +213,15 @@ export default class RoomMembersView extends LoggedView {
|
|||
ItemSeparatorComponent={this.renderSeparator}
|
||||
ListHeaderComponent={this.renderSearchBar}
|
||||
{...scrollPersistTaps}
|
||||
/>,
|
||||
/>
|
||||
<ActionSheet
|
||||
key='room-members-actionsheet'
|
||||
ref={o => this.ActionSheet = o}
|
||||
ref={o => this.actionSheet = o}
|
||||
title={I18n.t('Actions')}
|
||||
options={this.actionSheetOptions}
|
||||
cancelButtonIndex={this.CANCEL_INDEX}
|
||||
onPress={this.handleActionPress}
|
||||
/>
|
||||
]
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, View, LayoutAnimation, ActivityIndicator } from 'react-native';
|
||||
import { Text, View, LayoutAnimation, ActivityIndicator, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
|
@ -295,7 +295,7 @@ export default class RoomView extends LoggedView {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container} testID='room-view'>
|
||||
<SafeAreaView style={styles.container} testID='room-view'>
|
||||
{this.renderList()}
|
||||
{this.renderFooter()}
|
||||
{this.state.room._id && this.props.showActions ?
|
||||
|
@ -304,7 +304,7 @@ export default class RoomView extends LoggedView {
|
|||
{this.props.showErrorActions ? <MessageErrorActions /> : null}
|
||||
<ReactionPicker onEmojiSelected={this.onReactionPress} />
|
||||
<UploadProgress rid={this.rid} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,10 @@ import { setSearch } from '../../../actions/rooms';
|
|||
import styles from './styles';
|
||||
import I18n from '../../../i18n';
|
||||
|
||||
class RoomsListSearchView extends React.Component {
|
||||
@connect(null, dispatch => ({
|
||||
setSearch: searchText => dispatch(setSearch(searchText))
|
||||
}))
|
||||
export default class RoomsListSearchView extends React.Component {
|
||||
static propTypes = {
|
||||
setSearch: PropTypes.func
|
||||
}
|
||||
|
@ -39,9 +42,3 @@ class RoomsListSearchView extends React.Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
setSearch: searchText => dispatch(setSearch(searchText))
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(RoomsListSearchView);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Platform, View, TextInput, FlatList, BackHandler } from 'react-native';
|
||||
import { Platform, View, TextInput, FlatList, BackHandler, ActivityIndicator, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { iconsMap } from '../../Icons';
|
||||
|
@ -13,11 +13,14 @@ import LoggedView from '../View';
|
|||
import log from '../../utils/log';
|
||||
import I18n from '../../i18n';
|
||||
|
||||
const ROW_HEIGHT = 70.5;
|
||||
|
||||
@connect(state => ({
|
||||
userId: state.login.user && state.login.user.id,
|
||||
server: state.server.server,
|
||||
Site_Url: state.settings.Site_Url,
|
||||
searchText: state.rooms.searchText
|
||||
searchText: state.rooms.searchText,
|
||||
loadingServer: state.server.loading
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class RoomsListView extends LoggedView {
|
||||
|
@ -26,7 +29,8 @@ export default class RoomsListView extends LoggedView {
|
|||
userId: PropTypes.string,
|
||||
Site_Url: PropTypes.string,
|
||||
server: PropTypes.string,
|
||||
searchText: PropTypes.string
|
||||
searchText: PropTypes.string,
|
||||
loadingServer: PropTypes.bool
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -34,7 +38,8 @@ export default class RoomsListView extends LoggedView {
|
|||
|
||||
this.state = {
|
||||
search: [],
|
||||
rooms: []
|
||||
rooms: [],
|
||||
loading: true
|
||||
};
|
||||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
|
||||
}
|
||||
|
@ -48,7 +53,9 @@ export default class RoomsListView extends LoggedView {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
if (this.props.server !== props.server && props.server) {
|
||||
if (props.server && props.loadingServer) {
|
||||
this.setState({ loading: true });
|
||||
} else if (props.server && !props.loadingServer) {
|
||||
this.getSubscriptions();
|
||||
} else if (this.props.searchText !== props.searchText) {
|
||||
this.search(props.searchText);
|
||||
|
@ -60,6 +67,9 @@ export default class RoomsListView extends LoggedView {
|
|||
if (this.data) {
|
||||
this.data.removeAllListeners();
|
||||
}
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
onNavigatorEvent(event) {
|
||||
|
@ -104,6 +114,9 @@ export default class RoomsListView extends LoggedView {
|
|||
this.data = database.objects('subscriptions').filtered('archived != true && open == true').sorted('roomUpdatedAt', true);
|
||||
this.data.addListener(this.updateState);
|
||||
}
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({ loading: false });
|
||||
}, 200);
|
||||
}
|
||||
|
||||
initDefaultHeader = () => {
|
||||
|
@ -285,24 +298,31 @@ export default class RoomsListView extends LoggedView {
|
|||
/>);
|
||||
}
|
||||
|
||||
renderList = () => (
|
||||
<FlatList
|
||||
data={this.state.search.length > 0 ? this.state.search : this.state.rooms}
|
||||
extraData={this.state.search.length > 0 ? this.state.search : this.state.rooms}
|
||||
keyExtractor={item => item.rid}
|
||||
style={styles.list}
|
||||
renderItem={this.renderItem}
|
||||
ListHeaderComponent={Platform.OS === 'ios' ? this.renderSearchBar : null}
|
||||
contentOffset={Platform.OS === 'ios' ? { x: 0, y: 38 } : {}}
|
||||
enableEmptySections
|
||||
removeClippedSubviews
|
||||
keyboardShouldPersistTaps='always'
|
||||
testID='rooms-list-view-list'
|
||||
/>
|
||||
)
|
||||
renderList = () => {
|
||||
if (this.state.loading) {
|
||||
return <ActivityIndicator style={styles.loading} />;
|
||||
}
|
||||
return (
|
||||
<FlatList
|
||||
data={this.state.search.length > 0 ? this.state.search : this.state.rooms}
|
||||
extraData={this.state.search.length > 0 ? this.state.search : this.state.rooms}
|
||||
keyExtractor={item => item.rid}
|
||||
style={styles.list}
|
||||
renderItem={this.renderItem}
|
||||
ListHeaderComponent={Platform.OS === 'ios' ? this.renderSearchBar : null}
|
||||
contentOffset={Platform.OS === 'ios' ? { x: 0, y: 38 } : {}}
|
||||
getItemLayout={(data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index })}
|
||||
enableEmptySections
|
||||
removeClippedSubviews
|
||||
keyboardShouldPersistTaps='always'
|
||||
testID='rooms-list-view-list'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render = () => (
|
||||
<View style={styles.container} testID='rooms-list-view'>
|
||||
<SafeAreaView style={styles.container} testID='rooms-list-view'>
|
||||
{this.renderList()}
|
||||
</View>)
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -37,5 +37,8 @@ export default StyleSheet.create({
|
|||
padding: 5,
|
||||
paddingLeft: 10,
|
||||
color: '#aaa'
|
||||
},
|
||||
loading: {
|
||||
flex: 1
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, FlatList } from 'react-native';
|
||||
import { View, FlatList, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import LoggedView from '../View';
|
||||
|
@ -115,10 +115,7 @@ export default class SearchMessagesView extends LoggedView {
|
|||
render() {
|
||||
const { searching, loadingMore } = this.state;
|
||||
return (
|
||||
<View
|
||||
style={styles.container}
|
||||
testID='search-messages-view'
|
||||
>
|
||||
<SafeAreaView style={styles.container} testID='search-messages-view'>
|
||||
<View style={styles.searchContainer}>
|
||||
<RCTextInput
|
||||
inputRef={(e) => { this.name = e; }}
|
||||
|
@ -140,7 +137,7 @@ export default class SearchMessagesView extends LoggedView {
|
|||
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
|
||||
{...scrollPersistTaps}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -297,11 +297,9 @@ export default class SelectedUsersView extends LoggedView {
|
|||
/>
|
||||
);
|
||||
render = () => (
|
||||
<View style={styles.container} testID='select-users-view'>
|
||||
<SafeAreaView style={styles.safeAreaView}>
|
||||
{this.renderList()}
|
||||
<Loading visible={this.props.loading} />
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
<SafeAreaView style={styles.safeAreaView} testID='select-users-view'>
|
||||
{this.renderList()}
|
||||
<Loading visible={this.props.loading} />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -76,6 +76,15 @@ export default class SettingsView extends LoggedView {
|
|||
}
|
||||
}
|
||||
|
||||
getLabel = (language) => {
|
||||
const { languages } = this.state;
|
||||
const l = languages.find(i => i.value === language);
|
||||
if (l && l.label) {
|
||||
return l.label;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
formIsChanged = () => {
|
||||
const { language } = this.state;
|
||||
return !(this.props.userLanguage === language);
|
||||
|
@ -107,6 +116,10 @@ export default class SettingsView extends LoggedView {
|
|||
this.setState({ saving: false });
|
||||
setTimeout(() => {
|
||||
showToast(I18n.t('Preferences_saved'));
|
||||
|
||||
if (params.language) {
|
||||
this.props.navigator.setTitle({ title: I18n.t('Settings') });
|
||||
}
|
||||
}, 300);
|
||||
} catch (e) {
|
||||
this.setState({ saving: false });
|
||||
|
@ -132,7 +145,7 @@ export default class SettingsView extends LoggedView {
|
|||
testID='settings-view-list'
|
||||
{...scrollPersistTaps}
|
||||
>
|
||||
<SafeAreaView testID='settings-view'>
|
||||
<SafeAreaView style={sharedStyles.container} testID='settings-view'>
|
||||
<RNPickerSelect
|
||||
items={languages}
|
||||
onValueChange={(value) => {
|
||||
|
@ -145,7 +158,7 @@ export default class SettingsView extends LoggedView {
|
|||
inputRef={(e) => { this.name = e; }}
|
||||
label={I18n.t('Language')}
|
||||
placeholder={I18n.t('Language')}
|
||||
value={languages.find(i => i.value === language).label}
|
||||
value={this.getLabel(language)}
|
||||
testID='settings-view-language'
|
||||
/>
|
||||
</RNPickerSelect>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Text } from 'react-native';
|
||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import LoggedView from '../View';
|
||||
|
@ -102,10 +102,8 @@ export default class SnippetedMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
return (
|
||||
[
|
||||
<SafeAreaView style={styles.list} testID='snippeted-messages-view'>
|
||||
<FlatList
|
||||
key='snippeted-messages-view-list'
|
||||
testID='snippeted-messages-view'
|
||||
data={messages}
|
||||
renderItem={this.renderItem}
|
||||
style={styles.list}
|
||||
|
@ -114,7 +112,7 @@ export default class SnippetedMessagesView extends LoggedView {
|
|||
ListHeaderComponent={loading ? <RCActivityIndicator /> : null}
|
||||
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
|
||||
/>
|
||||
]
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Text } from 'react-native';
|
||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
|
||||
|
@ -69,7 +69,9 @@ export default class StarredMessagesView extends LoggedView {
|
|||
|
||||
onLongPress = (message) => {
|
||||
this.setState({ message });
|
||||
this.actionSheet.show();
|
||||
if (this.actionSheet && this.actionSheet.show) {
|
||||
this.actionSheet.show();
|
||||
}
|
||||
}
|
||||
|
||||
handleActionPress = (actionIndex) => {
|
||||
|
@ -126,10 +128,8 @@ export default class StarredMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
return (
|
||||
[
|
||||
<SafeAreaView style={styles.list} testID='starred-messages-view'>
|
||||
<FlatList
|
||||
key='starred-messages-view-list'
|
||||
testID='starred-messages-view'
|
||||
data={messages}
|
||||
renderItem={this.renderItem}
|
||||
style={styles.list}
|
||||
|
@ -137,16 +137,15 @@ export default class StarredMessagesView extends LoggedView {
|
|||
onEndReached={this.moreData}
|
||||
ListHeaderComponent={loading ? <RCActivityIndicator /> : null}
|
||||
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
|
||||
/>,
|
||||
/>
|
||||
<ActionSheet
|
||||
key='starred-messages-view-action-sheet'
|
||||
ref={o => this.actionSheet = o}
|
||||
title={I18n.t('Actions')}
|
||||
options={options}
|
||||
cancelButtonIndex={CANCEL_INDEX}
|
||||
onPress={this.handleActionPress}
|
||||
/>
|
||||
]
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { WebView } from 'react-native';
|
||||
import { WebView, SafeAreaView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import styles from './Styles';
|
||||
|
||||
@connect(state => ({
|
||||
termsService: state.settings.Layout_Terms_of_Service
|
||||
}))
|
||||
|
@ -13,7 +15,9 @@ export default class TermsServiceView extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<WebView source={{ html: this.props.termsService }} />
|
||||
<SafeAreaView style={styles.container}>
|
||||
<WebView originWhitelist={['*']} source={{ html: this.props.termsService, baseUrl: '' }} />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import '@babel/polyfill';
|
||||
import 'regenerator-runtime/runtime';
|
||||
|
||||
import './app/ReactotronConfig';
|
||||
import './app/push';
|
||||
import App from './app/index';
|
|
@ -25,7 +25,7 @@
|
|||
{
|
||||
NSURL *jsCodeLocation;
|
||||
#ifdef DEBUG
|
||||
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
|
||||
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
|
||||
#else
|
||||
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
#endif
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<string>1.0.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
|
Loading…
Reference in New Issue