Use FlatList in RoomView (#762)
This commit is contained in:
parent
2ce770fd22
commit
fd5fbe47e8
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
|
@ -35,8 +35,7 @@ export const ROOM = createRequestTypes('ROOM', [
|
|||
'LEAVE',
|
||||
'ERASE',
|
||||
'USER_TYPING',
|
||||
'MESSAGE_RECEIVED',
|
||||
'SET_LAST_OPEN'
|
||||
'MESSAGE_RECEIVED'
|
||||
]);
|
||||
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT']);
|
||||
export const MESSAGES = createRequestTypes('MESSAGES', [
|
||||
|
|
|
@ -64,10 +64,3 @@ export function roomMessageReceived(message) {
|
|||
message
|
||||
};
|
||||
}
|
||||
|
||||
export function setLastOpen(date = new Date()) {
|
||||
return {
|
||||
type: types.ROOM.SET_LAST_OPEN,
|
||||
date
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { isIOS } from '../utils/deviceInfo';
|
|||
export const COLOR_DANGER = '#f5455c';
|
||||
export const COLOR_BUTTON_PRIMARY = '#1d74f5';
|
||||
export const COLOR_TEXT = '#292E35';
|
||||
export const COLOR_SEPARATOR = '#CBCED1';
|
||||
export const COLOR_SEPARATOR = '#A7A7AA';
|
||||
export const STATUS_COLORS = {
|
||||
online: '#2de0a5',
|
||||
busy: COLOR_DANGER,
|
||||
|
@ -11,6 +11,6 @@ export const STATUS_COLORS = {
|
|||
offline: '#cbced1'
|
||||
};
|
||||
|
||||
export const HEADER_BACKGROUND = isIOS ? '#FFF' : '#2F343D';
|
||||
export const HEADER_BACKGROUND = isIOS ? '#f8f8f8' : '#2F343D';
|
||||
export const HEADER_TITLE = isIOS ? '#0C0D0F' : '#FFF';
|
||||
export const HEADER_BACK = isIOS ? '#1d74f5' : '#FFF';
|
||||
|
|
|
@ -365,7 +365,7 @@ export default class Message extends PureComponent {
|
|||
onPress={this.onPress}
|
||||
>
|
||||
<View
|
||||
style={[styles.container, header && styles.marginBottom, editing && styles.editing, style]}
|
||||
style={[styles.container, header && styles.marginTop, editing && styles.editing, style]}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
>
|
||||
<View style={styles.flex}>
|
||||
|
|
|
@ -101,7 +101,6 @@ export default class MessageContainer extends React.Component {
|
|||
return _updatedAt.toGMTString() !== nextProps._updatedAt.toGMTString();
|
||||
}
|
||||
|
||||
|
||||
onLongPress = () => {
|
||||
const { onLongPress } = this.props;
|
||||
onLongPress(this.parseMessage());
|
||||
|
|
|
@ -5,11 +5,10 @@ export default StyleSheet.create({
|
|||
flexDirection: 'row'
|
||||
},
|
||||
container: {
|
||||
paddingVertical: 5,
|
||||
paddingVertical: 4,
|
||||
width: '100%',
|
||||
paddingHorizontal: 15,
|
||||
paddingHorizontal: 14,
|
||||
flexDirection: 'column',
|
||||
transform: [{ scaleY: -1 }],
|
||||
flex: 1
|
||||
},
|
||||
messageContent: {
|
||||
|
@ -39,8 +38,8 @@ export default StyleSheet.create({
|
|||
height: 20
|
||||
},
|
||||
temp: { opacity: 0.3 },
|
||||
marginBottom: {
|
||||
marginBottom: 10
|
||||
marginTop: {
|
||||
marginTop: 10
|
||||
},
|
||||
reactionsContainer: {
|
||||
flexDirection: 'row',
|
||||
|
@ -82,7 +81,7 @@ export default StyleSheet.create({
|
|||
marginLeft: 7
|
||||
},
|
||||
avatar: {
|
||||
marginTop: 5
|
||||
marginTop: 4
|
||||
},
|
||||
addReaction: {
|
||||
color: '#1D74F5'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AsyncStorage } from 'react-native';
|
||||
import { AsyncStorage, InteractionManager } from 'react-native';
|
||||
import foreach from 'lodash/forEach';
|
||||
import semver from 'semver';
|
||||
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
||||
|
@ -118,16 +118,16 @@ const RocketChat = {
|
|||
reduxStore.dispatch(setUser({ status: 'offline' }));
|
||||
}
|
||||
|
||||
if (this._setUserTimer) {
|
||||
clearTimeout(this._setUserTimer);
|
||||
this._setUserTimer = null;
|
||||
}
|
||||
|
||||
if (!this._setUserTimer) {
|
||||
this._setUserTimer = setTimeout(() => {
|
||||
reduxStore.dispatch(setActiveUser(this.activeUsers));
|
||||
const batchUsers = this.activeUsers;
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
reduxStore.dispatch(setActiveUser(batchUsers));
|
||||
});
|
||||
this._setUserTimer = null;
|
||||
return this.activeUsers = {};
|
||||
}, 2000);
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
const activeUser = reduxStore.getState().activeUsers[ddpMessage.id];
|
||||
if (!ddpMessage.fields) {
|
||||
|
@ -145,11 +145,18 @@ const RocketChat = {
|
|||
}
|
||||
this.roomsSub = await this.subscribeRooms();
|
||||
|
||||
this.sdk.subscribe('activeUsers');
|
||||
this.sdk.subscribe('roles');
|
||||
this.getPermissions();
|
||||
this.getCustomEmoji();
|
||||
this.registerPushToken().catch(e => console.log(e));
|
||||
|
||||
if (this.activeUsersSubTimeout) {
|
||||
clearTimeout(this.activeUsersSubTimeout);
|
||||
this.activeUsersSubTimeout = false;
|
||||
}
|
||||
this.activeUsersSubTimeout = setTimeout(() => {
|
||||
this.sdk.subscribe('activeUsers');
|
||||
}, 5000);
|
||||
},
|
||||
connect({ server, user }) {
|
||||
database.setActiveDB(server);
|
||||
|
@ -330,6 +337,11 @@ const RocketChat = {
|
|||
this.roomsSub.stop();
|
||||
}
|
||||
|
||||
if (this.activeUsersSubTimeout) {
|
||||
clearTimeout(this.activeUsersSubTimeout);
|
||||
this.activeUsersSubTimeout = false;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.removePushToken();
|
||||
} catch (error) {
|
||||
|
|
|
@ -214,7 +214,7 @@ export default class RoomItem extends React.Component {
|
|||
|
||||
formatDate = date => moment(date).calendar(null, {
|
||||
lastDay: `[${ I18n.t('Yesterday') }]`,
|
||||
sameDay: 'h:mm A',
|
||||
sameDay: 'HH:mm',
|
||||
lastWeek: 'dddd',
|
||||
sameElse: 'MMM D'
|
||||
})
|
||||
|
|
|
@ -9,18 +9,12 @@ export default function room(state = initialState, action) {
|
|||
case types.ROOM.OPEN:
|
||||
return {
|
||||
...initialState,
|
||||
...action.room,
|
||||
lastOpen: new Date()
|
||||
...action.room
|
||||
};
|
||||
case types.ROOM.CLOSE:
|
||||
return {
|
||||
...initialState
|
||||
};
|
||||
case types.ROOM.SET_LAST_OPEN:
|
||||
return {
|
||||
...state,
|
||||
lastOpen: action.date
|
||||
};
|
||||
case types.ROOM.ADD_USER_TYPING:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -13,6 +13,7 @@ import I18n from '../i18n';
|
|||
import DisclosureIndicator from '../containers/DisclosureIndicator';
|
||||
import { CloseModalButton } from '../containers/HeaderButton';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import { COLOR_SEPARATOR } from '../constants/colors';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -22,12 +23,12 @@ const styles = StyleSheet.create({
|
|||
scroll: {
|
||||
marginTop: 35,
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#cbced1',
|
||||
borderColor: COLOR_SEPARATOR,
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth
|
||||
},
|
||||
separator: {
|
||||
backgroundColor: '#cbced1',
|
||||
backgroundColor: COLOR_SEPARATOR,
|
||||
height: StyleSheet.hairlineWidth,
|
||||
width: '100%',
|
||||
marginLeft: 20
|
||||
|
|
|
@ -17,6 +17,7 @@ import Button from '../containers/Button';
|
|||
import I18n from '../i18n';
|
||||
import { LegalButton } from '../containers/HeaderButton';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import { COLOR_SEPARATOR } from '../constants/colors';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -72,7 +73,7 @@ const styles = StyleSheet.create({
|
|||
separatorLine: {
|
||||
flex: 1,
|
||||
height: 1,
|
||||
backgroundColor: '#e1e5e8'
|
||||
backgroundColor: COLOR_SEPARATOR
|
||||
},
|
||||
separatorLineLeft: {
|
||||
marginRight: 15
|
||||
|
|
|
@ -96,7 +96,7 @@ export default class LoginView extends LoggedView {
|
|||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { Site_Name, error } = this.props;
|
||||
if (Site_Name && nextProps.Site_Name !== Site_Name) {
|
||||
if (nextProps.Site_Name && nextProps.Site_Name !== Site_Name) {
|
||||
this.setTitle(nextProps.Site_Name);
|
||||
} else if (nextProps.failure && !equal(error, nextProps.error)) {
|
||||
if (nextProps.error && nextProps.error.error === 'totp-required') {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
import { COLOR_SEPARATOR } from '../../constants/colors';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
|
@ -26,7 +27,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
separator: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: '#ddd'
|
||||
backgroundColor: COLOR_SEPARATOR
|
||||
},
|
||||
sectionSeparator: {
|
||||
height: 10,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import { COLOR_DANGER } from '../../constants/colors';
|
||||
import { COLOR_DANGER, COLOR_SEPARATOR } from '../../constants/colors';
|
||||
|
||||
export default StyleSheet.create({
|
||||
buttonInverted: {
|
||||
|
@ -39,7 +39,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
divider: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
borderColor: '#ddd',
|
||||
borderColor: COLOR_SEPARATOR,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
marginVertical: 20
|
||||
},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
import { COLOR_SEPARATOR } from '../../constants/colors';
|
||||
|
||||
export default StyleSheet.create({
|
||||
list: {
|
||||
|
@ -24,7 +25,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
separator: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: '#E1E5E8',
|
||||
backgroundColor: COLOR_SEPARATOR,
|
||||
marginLeft: 60
|
||||
},
|
||||
username: {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import { ImageBackground, StyleSheet } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
image: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute'
|
||||
}
|
||||
});
|
||||
|
||||
const EmptyRoom = React.memo(({ length }) => {
|
||||
if (length === 0) {
|
||||
return <ImageBackground source={{ uri: 'message_empty' }} style={styles.image} />;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
EmptyRoom.propTypes = {
|
||||
length: PropTypes.number.isRequired
|
||||
};
|
||||
export default EmptyRoom;
|
|
@ -0,0 +1,50 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { STATUS_COLORS } from '../../../constants/colors';
|
||||
import { CustomIcon } from '../../../lib/Icons';
|
||||
import Status from '../../../containers/Status/Status';
|
||||
import { isIOS } from '../../../utils/deviceInfo';
|
||||
|
||||
const ICON_SIZE = 18;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
type: {
|
||||
width: ICON_SIZE,
|
||||
height: ICON_SIZE,
|
||||
marginRight: 8,
|
||||
color: isIOS ? '#9EA2A8' : '#fff'
|
||||
},
|
||||
status: {
|
||||
marginRight: 8
|
||||
}
|
||||
});
|
||||
|
||||
const Icon = React.memo(({ type, status }) => {
|
||||
if (type === 'd') {
|
||||
return <Status size={10} style={styles.status} status={status} />;
|
||||
}
|
||||
|
||||
const icon = type === 'c' ? 'hashtag' : 'lock';
|
||||
return (
|
||||
<CustomIcon
|
||||
name={icon}
|
||||
size={ICON_SIZE * 1}
|
||||
style={[
|
||||
styles.type,
|
||||
{
|
||||
width: ICON_SIZE * 1,
|
||||
height: ICON_SIZE * 1
|
||||
},
|
||||
type === 'd' && { color: STATUS_COLORS[status] }
|
||||
]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Icon.propTypes = {
|
||||
type: PropTypes.string,
|
||||
status: PropTypes.string
|
||||
};
|
||||
export default Icon;
|
|
@ -1,52 +1,45 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
View, Text, StyleSheet, LayoutAnimation, ScrollView
|
||||
View, Text, StyleSheet, ScrollView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { responsive } from 'react-native-responsive-ui';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import I18n from '../../../i18n';
|
||||
import { STATUS_COLORS } from '../../../constants/colors';
|
||||
import sharedStyles from '../../Styles';
|
||||
import { isIOS } from '../../../utils/deviceInfo';
|
||||
import { CustomIcon } from '../../../lib/Icons';
|
||||
import Status from '../../../containers/Status/Status';
|
||||
import { headerIconSize } from '../../../containers/HeaderButton';
|
||||
import Icon from './Icon';
|
||||
|
||||
const TITLE_SIZE = 18;
|
||||
const ICON_SIZE = 18;
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
flex: 1,
|
||||
height: '100%'
|
||||
},
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
flex: 1,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
title: {
|
||||
...sharedStyles.textSemibold,
|
||||
color: isIOS ? '#0C0D0F' : '#fff',
|
||||
fontSize: TITLE_SIZE
|
||||
},
|
||||
type: {
|
||||
width: ICON_SIZE,
|
||||
height: ICON_SIZE,
|
||||
marginRight: 8,
|
||||
color: isIOS ? '#9EA2A8' : '#fff'
|
||||
scroll: {
|
||||
alignItems: 'center'
|
||||
},
|
||||
typing: {
|
||||
...sharedStyles.textRegular,
|
||||
color: isIOS ? '#9EA2A8' : '#fff',
|
||||
fontSize: 12
|
||||
fontSize: 12,
|
||||
marginBottom: 2
|
||||
},
|
||||
typingUsers: {
|
||||
...sharedStyles.textSemibold,
|
||||
fontWeight: '600'
|
||||
},
|
||||
status: {
|
||||
marginRight: 8
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -119,14 +112,14 @@ export default class RoomHeaderView extends Component {
|
|||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (isIOS) {
|
||||
const { usersTyping } = this.props;
|
||||
if (!equal(prevProps.usersTyping, usersTyping)) {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
// componentDidUpdate(prevProps) {
|
||||
// if (isIOS) {
|
||||
// const { usersTyping } = this.props;
|
||||
// if (!equal(prevProps.usersTyping, usersTyping)) {
|
||||
// LayoutAnimation.easeInEaseOut();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
get typing() {
|
||||
const { usersTyping } = this.props;
|
||||
|
@ -146,32 +139,9 @@ export default class RoomHeaderView extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderIcon = () => {
|
||||
const { type, status } = this.props;
|
||||
if (type === 'd') {
|
||||
return <Status size={10} style={styles.status} status={status} />;
|
||||
}
|
||||
|
||||
const icon = type === 'c' ? 'hashtag' : 'lock';
|
||||
return (
|
||||
<CustomIcon
|
||||
name={icon}
|
||||
size={ICON_SIZE * 1}
|
||||
style={[
|
||||
styles.type,
|
||||
{
|
||||
width: ICON_SIZE * 1,
|
||||
height: ICON_SIZE * 1
|
||||
},
|
||||
type === 'd' && { color: STATUS_COLORS[status] }
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
window, title, usersTyping
|
||||
window, title, usersTyping, type, status
|
||||
} = this.props;
|
||||
const portrait = window.height > window.width;
|
||||
const widthScrollView = window.width - 6.5 * headerIconSize;
|
||||
|
@ -190,8 +160,9 @@ export default class RoomHeaderView extends Component {
|
|||
showsHorizontalScrollIndicator={false}
|
||||
horizontal
|
||||
bounces={false}
|
||||
contentContainerStyle={styles.scroll}
|
||||
>
|
||||
{this.renderIcon()}
|
||||
<Icon type={type} status={status} />
|
||||
<Text style={[styles.title, { fontSize: TITLE_SIZE * scale }]} numberOfLines={1}>{title}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
import React from 'react';
|
||||
import { ActivityIndicator, FlatList } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { responsive } from 'react-native-responsive-ui';
|
||||
|
||||
import styles from './styles';
|
||||
import database from '../../lib/realm';
|
||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
import debounce from '../../utils/debounce';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import log from '../../utils/log';
|
||||
import EmptyRoom from './EmptyRoom';
|
||||
import ScrollBottomButton from './ScrollBottomButton';
|
||||
|
||||
@responsive
|
||||
export class List extends React.Component {
|
||||
static propTypes = {
|
||||
onEndReached: PropTypes.func,
|
||||
renderFooter: PropTypes.func,
|
||||
renderRow: PropTypes.func,
|
||||
room: PropTypes.object,
|
||||
window: PropTypes.object
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.data = database
|
||||
.objects('messages')
|
||||
.filtered('rid = $0', props.room.rid)
|
||||
.sorted('ts', true);
|
||||
this.state = {
|
||||
loading: true,
|
||||
loadingMore: false,
|
||||
end: false,
|
||||
messages: this.data.slice(),
|
||||
showScollToBottomButton: false
|
||||
};
|
||||
this.data.addListener(this.updateState);
|
||||
}
|
||||
|
||||
// shouldComponentUpdate(nextProps, nextState) {
|
||||
// const {
|
||||
// loadingMore, loading, end, showScollToBottomButton, messages
|
||||
// } = this.state;
|
||||
// const { window } = this.props;
|
||||
// return end !== nextState.end
|
||||
// || loadingMore !== nextState.loadingMore
|
||||
// || loading !== nextState.loading
|
||||
// || showScollToBottomButton !== nextState.showScollToBottomButton
|
||||
// // || messages.length !== nextState.messages.length
|
||||
// || !equal(messages, nextState.messages)
|
||||
// || window.width !== nextProps.window.width;
|
||||
// }
|
||||
|
||||
componentWillUnmount() {
|
||||
this.data.removeAllListeners();
|
||||
this.updateState.stop();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
updateState = debounce(() => {
|
||||
this.setState({ messages: this.data.slice(), loading: false, loadingMore: false });
|
||||
}, 300);
|
||||
|
||||
onEndReached = async() => {
|
||||
const {
|
||||
loadingMore, loading, end, messages
|
||||
} = this.state;
|
||||
if (loadingMore || loading || end || messages.length < 50) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ loadingMore: true });
|
||||
const { room } = this.props;
|
||||
try {
|
||||
const result = await RocketChat.loadMessagesForRoom({ rid: room.rid, t: room.t, latest: this.data[this.data.length - 1].ts });
|
||||
this.setState({ end: result.length < 50 });
|
||||
} catch (e) {
|
||||
this.setState({ loadingMore: false });
|
||||
log('ListView.onEndReached', e);
|
||||
}
|
||||
}
|
||||
|
||||
scrollToBottom = () => {
|
||||
requestAnimationFrame(() => {
|
||||
this.list.scrollToOffset({ offset: -100 });
|
||||
});
|
||||
}
|
||||
|
||||
handleScroll = (event) => {
|
||||
if (event.nativeEvent.contentOffset.y > 0) {
|
||||
this.setState({ showScollToBottomButton: true });
|
||||
} else {
|
||||
this.setState({ showScollToBottomButton: false });
|
||||
}
|
||||
}
|
||||
|
||||
renderFooter = () => {
|
||||
const { loadingMore, loading } = this.state;
|
||||
if (loadingMore || loading) {
|
||||
return <ActivityIndicator style={styles.loadingMore} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { renderRow, window } = this.props;
|
||||
const { showScollToBottomButton, messages } = this.state;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<EmptyRoom length={messages.length} />
|
||||
<FlatList
|
||||
testID='room-view-messages'
|
||||
ref={ref => this.list = ref}
|
||||
keyExtractor={item => item._id}
|
||||
data={messages}
|
||||
extraData={this.state}
|
||||
renderItem={({ item, index }) => renderRow(item, messages[index + 1])}
|
||||
style={styles.list}
|
||||
onScroll={this.handleScroll}
|
||||
inverted
|
||||
removeClippedSubviews
|
||||
initialNumToRender={10}
|
||||
onEndReached={this.onEndReached}
|
||||
onEndReachedThreshold={0.5}
|
||||
maxToRenderPerBatch={20}
|
||||
ListFooterComponent={this.renderFooter}
|
||||
{...scrollPersistTaps}
|
||||
/>
|
||||
<ScrollBottomButton
|
||||
show={showScollToBottomButton}
|
||||
onPress={this.scrollToBottom}
|
||||
landscape={window.width > window.height}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,277 +0,0 @@
|
|||
import { ListView as OldList } from 'realm/react-native';
|
||||
import React from 'react';
|
||||
import {
|
||||
TouchableOpacity, ScrollView, ListView as OldList2, ImageBackground, ActivityIndicator
|
||||
} from 'react-native';
|
||||
import moment from 'moment';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Separator from './Separator';
|
||||
import styles from './styles';
|
||||
import database from '../../lib/realm';
|
||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
import debounce from '../../utils/debounce';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import log from '../../utils/log';
|
||||
import { CustomIcon } from '../../lib/Icons';
|
||||
import { isIOS, isNotch } from '../../utils/deviceInfo';
|
||||
|
||||
const DEFAULT_SCROLL_CALLBACK_THROTTLE = 100;
|
||||
|
||||
export class DataSource extends OldList.DataSource {
|
||||
getRowData(sectionIndex: number, rowIndex: number): any {
|
||||
const sectionID = this.sectionIdentities[sectionIndex];
|
||||
const rowID = this.rowIdentities[sectionIndex][rowIndex];
|
||||
return this._getRowData(this._dataBlob, sectionID, rowID);
|
||||
}
|
||||
_calculateDirtyArrays() { // eslint-disable-line
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const ds = new DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
|
||||
|
||||
export class List extends React.Component {
|
||||
static propTypes = {
|
||||
onEndReached: PropTypes.func,
|
||||
renderFooter: PropTypes.func,
|
||||
renderRow: PropTypes.func,
|
||||
room: PropTypes.object
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.data = database
|
||||
.objects('messages')
|
||||
.filtered('rid = $0', props.room.rid)
|
||||
.sorted('ts', true);
|
||||
this.state = {
|
||||
loading: true,
|
||||
loadingMore: false,
|
||||
end: false,
|
||||
showScollToBottomButton: false
|
||||
};
|
||||
this.dataSource = ds.cloneWithRows(this.data);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateState();
|
||||
this.data.addListener(this.updateState);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { loadingMore, loading, end, showScollToBottomButton } = this.state;
|
||||
return end !== nextState.end || loadingMore !== nextState.loadingMore || loading !== nextState.loading || showScollToBottomButton !== nextState.showScollToBottomButton;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.data.removeAllListeners();
|
||||
this.updateState.stop();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
updateState = debounce(() => {
|
||||
this.setState({ loading: true });
|
||||
this.dataSource = this.dataSource.cloneWithRows(this.data);
|
||||
this.setState({ loading: false });
|
||||
}, 300);
|
||||
|
||||
onEndReached = async() => {
|
||||
const { loadingMore, end } = this.state;
|
||||
if (loadingMore || end || this.data.length < 50) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ loadingMore: true });
|
||||
const { room } = this.props;
|
||||
try {
|
||||
const result = await RocketChat.loadMessagesForRoom({ rid: room.rid, t: room.t, latest: this.data[this.data.length - 1].ts });
|
||||
this.setState({ end: result.length < 50, loadingMore: false });
|
||||
} catch (e) {
|
||||
this.setState({ loadingMore: false });
|
||||
log('ListView.onEndReached', e);
|
||||
}
|
||||
}
|
||||
|
||||
scrollToBottom = () => {
|
||||
this.listView.scrollTo({ x: 0, y: 0, animated: true });
|
||||
}
|
||||
|
||||
handleScroll= (event) => {
|
||||
if (event.nativeEvent.contentOffset.y > 0) {
|
||||
this.setState({ showScollToBottomButton: true });
|
||||
} else {
|
||||
this.setState({ showScollToBottomButton: false });
|
||||
}
|
||||
}
|
||||
|
||||
renderFooter = () => {
|
||||
const { loadingMore, loading } = this.state;
|
||||
if (loadingMore || loading) {
|
||||
return <ActivityIndicator style={styles.loadingMore} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getScrollButtonStyle = () => {
|
||||
let right = 30;
|
||||
if (isIOS) {
|
||||
right = isNotch ? 45 : 30;
|
||||
}
|
||||
return ({
|
||||
position: 'absolute',
|
||||
width: 42,
|
||||
height: 42,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
right,
|
||||
bottom: 70,
|
||||
backgroundColor: '#EAF2FE',
|
||||
borderRadius: 20
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { renderRow } = this.props;
|
||||
const { showScollToBottomButton } = this.state;
|
||||
const scrollButtonStyle = this.getScrollButtonStyle();
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ListView
|
||||
enableEmptySections
|
||||
ref={ref => this.listView = ref}
|
||||
style={styles.list}
|
||||
data={this.data}
|
||||
keyExtractor={item => item._id}
|
||||
onEndReachedThreshold={100}
|
||||
renderFooter={this.renderFooter}
|
||||
onEndReached={this.onEndReached}
|
||||
dataSource={this.dataSource}
|
||||
renderRow={(item, previousItem) => renderRow(item, previousItem)}
|
||||
initialListSize={1}
|
||||
pageSize={20}
|
||||
onScroll={this.handleScroll}
|
||||
testID='room-view-messages'
|
||||
{...scrollPersistTaps}
|
||||
/>
|
||||
{showScollToBottomButton ? (
|
||||
<TouchableOpacity activeOpacity={0.5} style={scrollButtonStyle} onPress={this.scrollToBottom}>
|
||||
<CustomIcon name='arrow-down' color='white' size={30} />
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@connect(state => ({
|
||||
lastOpen: state.room.lastOpen
|
||||
}), null, null, { forwardRef: true })
|
||||
export class ListView extends OldList2 {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
curRenderedRowsCount: 10
|
||||
// highlightedRow: ({}: Object)
|
||||
};
|
||||
}
|
||||
|
||||
getInnerViewNode() {
|
||||
return this.refs.listView.getInnerViewNode();
|
||||
}
|
||||
|
||||
scrollTo(...args) {
|
||||
this.refs.listView.scrollTo(...args);
|
||||
}
|
||||
|
||||
setNativeProps(props) {
|
||||
this.refs.listView.setNativeProps(props);
|
||||
}
|
||||
|
||||
static DataSource = DataSource;
|
||||
|
||||
render() {
|
||||
const bodyComponents = [];
|
||||
|
||||
// const stickySectionHeaderIndices = [];
|
||||
|
||||
// const { renderSectionHeader } = this.props;
|
||||
|
||||
const header = this.props.renderHeader ? this.props.renderHeader() : null;
|
||||
const footer = this.props.renderFooter ? this.props.renderFooter() : null;
|
||||
// let totalIndex = header ? 1 : 0;
|
||||
|
||||
const { data } = this.props;
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < this.state.curRenderedRowsCount && i < data.length; i += 1, count += 1) {
|
||||
const message = data[i];
|
||||
const previousMessage = data[i + 1];
|
||||
bodyComponents.push(this.props.renderRow(message, previousMessage));
|
||||
|
||||
|
||||
if (!previousMessage) {
|
||||
bodyComponents.push(<Separator key={message.ts.toISOString()} ts={message.ts} />);
|
||||
continue; // eslint-disable-line
|
||||
}
|
||||
|
||||
const showUnreadSeparator = this.props.lastOpen
|
||||
&& moment(message.ts).isAfter(this.props.lastOpen)
|
||||
&& moment(previousMessage.ts).isBefore(this.props.lastOpen);
|
||||
const showDateSeparator = !moment(message.ts).isSame(previousMessage.ts, 'day');
|
||||
|
||||
if (showUnreadSeparator || showDateSeparator) {
|
||||
bodyComponents.push(<Separator
|
||||
key={message.ts.toISOString()}
|
||||
ts={showDateSeparator ? message.ts : null}
|
||||
unread={showUnreadSeparator}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
|
||||
const { ...props } = this.props;
|
||||
if (!props.scrollEventThrottle) {
|
||||
props.scrollEventThrottle = DEFAULT_SCROLL_CALLBACK_THROTTLE;
|
||||
}
|
||||
if (props.removeClippedSubviews === undefined) {
|
||||
props.removeClippedSubviews = true;
|
||||
}
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This comment
|
||||
* suppresses an error found when Flow v0.54 was deployed. To see the error
|
||||
* delete this comment and run Flow. */
|
||||
Object.assign(props, {
|
||||
onScroll: this._onScroll,
|
||||
/* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error when upgrading Flow's support for React.
|
||||
* To see the error delete this comment and run Flow. */
|
||||
// stickyHeaderIndices: this.props.stickyHeaderIndices.concat(stickySectionHeaderIndices,),
|
||||
|
||||
// Do not pass these events downstream to ScrollView since they will be
|
||||
// registered in ListView's own ScrollResponder.Mixin
|
||||
onKeyboardWillShow: undefined,
|
||||
onKeyboardWillHide: undefined,
|
||||
onKeyboardDidShow: undefined,
|
||||
onKeyboardDidHide: undefined
|
||||
});
|
||||
|
||||
const image = data.length === 0 ? { uri: 'message_empty' } : null;
|
||||
return (
|
||||
[
|
||||
<ImageBackground key='listview-background' source={image} style={styles.imageBackground} />,
|
||||
<ScrollView
|
||||
key='listview-scroll'
|
||||
ref={this._setScrollComponentRef}
|
||||
onContentSizeChange={this._onContentSizeChange}
|
||||
onLayout={this._onLayout}
|
||||
{...props}
|
||||
>
|
||||
{header}
|
||||
{bodyComponents}
|
||||
{footer}
|
||||
</ScrollView>
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
ListView.DataSource = DataSource;
|
|
@ -0,0 +1,60 @@
|
|||
import React from 'react';
|
||||
import { TouchableOpacity, StyleSheet } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { isNotch } from '../../utils/deviceInfo';
|
||||
import { CustomIcon } from '../../lib/Icons';
|
||||
import { COLOR_BUTTON_PRIMARY } from '../../constants/colors';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
position: 'absolute',
|
||||
width: 42,
|
||||
height: 42,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#EAF2FE',
|
||||
borderRadius: 21,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 1
|
||||
},
|
||||
shadowOpacity: 0.20,
|
||||
shadowRadius: 1.41,
|
||||
elevation: 2
|
||||
}
|
||||
});
|
||||
|
||||
let right;
|
||||
let bottom = 80;
|
||||
if (isNotch) {
|
||||
bottom = 120;
|
||||
}
|
||||
|
||||
const ScrollBottomButton = React.memo(({ show, onPress, landscape }) => {
|
||||
if (show) {
|
||||
if (landscape) {
|
||||
right = 45;
|
||||
} else {
|
||||
right = 30;
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.8}
|
||||
style={[styles.button, { right, bottom }]}
|
||||
onPress={onPress}
|
||||
>
|
||||
<CustomIcon name='arrow-down' color={COLOR_BUTTON_PRIMARY} size={30} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
ScrollBottomButton.propTypes = {
|
||||
show: PropTypes.bool.isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
landscape: PropTypes.bool
|
||||
};
|
||||
export default ScrollBottomButton;
|
|
@ -8,10 +8,9 @@ const styles = StyleSheet.create({
|
|||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 25,
|
||||
marginTop: 15,
|
||||
marginHorizontal: 15,
|
||||
transform: [{ scaleY: -1 }]
|
||||
marginTop: 16,
|
||||
marginBottom: 4,
|
||||
marginHorizontal: 14
|
||||
},
|
||||
line: {
|
||||
backgroundColor: '#9ea2a8',
|
||||
|
@ -40,7 +39,7 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const DateSeparator = ({ ts, unread }) => {
|
||||
const DateSeparator = React.memo(({ ts, unread }) => {
|
||||
const date = ts ? moment(ts).format('MMM DD, YYYY') : null;
|
||||
if (ts && unread) {
|
||||
return (
|
||||
|
@ -65,7 +64,7 @@ const DateSeparator = ({ ts, unread }) => {
|
|||
<View style={[styles.line, styles.unreadLine]} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
DateSeparator.propTypes = {
|
||||
ts: PropTypes.instanceOf(Date),
|
||||
|
|
|
@ -11,6 +11,7 @@ import RocketChat from '../../lib/rocketchat';
|
|||
import log from '../../utils/log';
|
||||
import I18n from '../../i18n';
|
||||
import { CustomIcon } from '../../lib/Icons';
|
||||
import { COLOR_SEPARATOR } from '../../constants/colors';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -23,7 +24,7 @@ const styles = StyleSheet.create({
|
|||
backgroundColor: '#F1F2F4',
|
||||
height: 54,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#CACED1',
|
||||
borderColor: COLOR_SEPARATOR,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 20
|
||||
},
|
||||
|
|
|
@ -7,11 +7,12 @@ import { connect } from 'react-redux';
|
|||
import { RectButton } from 'react-native-gesture-handler';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import equal from 'deep-equal';
|
||||
import moment from 'moment';
|
||||
|
||||
import { openRoom as openRoomAction, closeRoom as closeRoomAction, setLastOpen as setLastOpenAction } from '../../actions/room';
|
||||
import { openRoom as openRoomAction, closeRoom as closeRoomAction } from '../../actions/room';
|
||||
import { toggleReactionPicker as toggleReactionPickerAction, actionsShow as actionsShowAction } from '../../actions/messages';
|
||||
import LoggedView from '../View';
|
||||
import { List } from './ListView';
|
||||
import { List } from './List';
|
||||
import database from '../../lib/realm';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import Message from '../../containers/message';
|
||||
|
@ -28,6 +29,7 @@ import ConnectionBadge from '../../containers/ConnectionBadge';
|
|||
import { CustomHeaderButtons, Item } from '../../containers/HeaderButton';
|
||||
import RoomHeaderView from './Header';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import Separator from './Separator';
|
||||
|
||||
@connect(state => ({
|
||||
user: {
|
||||
|
@ -41,7 +43,6 @@ import StatusBar from '../../containers/StatusBar';
|
|||
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background'
|
||||
}), dispatch => ({
|
||||
openRoom: room => dispatch(openRoomAction(room)),
|
||||
setLastOpen: date => dispatch(setLastOpenAction(date)),
|
||||
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message)),
|
||||
actionsShow: actionMessage => dispatch(actionsShowAction(actionMessage)),
|
||||
closeRoom: () => dispatch(closeRoomAction())
|
||||
|
@ -70,7 +71,6 @@ export default class RoomView extends LoggedView {
|
|||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
openRoom: PropTypes.func.isRequired,
|
||||
setLastOpen: PropTypes.func.isRequired,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
|
@ -92,9 +92,14 @@ export default class RoomView extends LoggedView {
|
|||
this.state = {
|
||||
loaded: false,
|
||||
joined: this.rooms.length > 0,
|
||||
room: {}
|
||||
room: {},
|
||||
lastOpen: null
|
||||
};
|
||||
this.beginAnimating = false;
|
||||
this.onReactionPress = this.onReactionPress.bind(this);
|
||||
setTimeout(() => {
|
||||
this.beginAnimating = true;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -181,7 +186,7 @@ export default class RoomView extends LoggedView {
|
|||
};
|
||||
|
||||
internalSetState = (...args) => {
|
||||
if (isIOS) {
|
||||
if (isIOS && this.beginAnimating) {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
}
|
||||
this.setState(...args);
|
||||
|
@ -189,7 +194,7 @@ export default class RoomView extends LoggedView {
|
|||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
updateRoom = () => {
|
||||
const { openRoom, setLastOpen } = this.props;
|
||||
const { openRoom } = this.props;
|
||||
|
||||
if (this.rooms.length > 0) {
|
||||
const { room: prevRoom } = this.state;
|
||||
|
@ -201,9 +206,9 @@ export default class RoomView extends LoggedView {
|
|||
...room
|
||||
});
|
||||
if (room.alert || room.unread || room.userMentions) {
|
||||
setLastOpen(room.ls);
|
||||
this.setLastOpen(room.ls);
|
||||
} else {
|
||||
setLastOpen(null);
|
||||
this.setLastOpen(null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -226,13 +231,14 @@ export default class RoomView extends LoggedView {
|
|||
}
|
||||
|
||||
sendMessage = (message) => {
|
||||
const { setLastOpen } = this.props;
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
RocketChat.sendMessage(this.rid, message).then(() => {
|
||||
setLastOpen(null);
|
||||
this.setLastOpen(null);
|
||||
});
|
||||
};
|
||||
|
||||
setLastOpen = lastOpen => this.setState({ lastOpen });
|
||||
|
||||
joinRoom = async() => {
|
||||
try {
|
||||
const result = await RocketChat.joinRoom(this.rid);
|
||||
|
@ -275,8 +281,46 @@ export default class RoomView extends LoggedView {
|
|||
}
|
||||
|
||||
renderItem = (item, previousItem) => {
|
||||
const { room } = this.state;
|
||||
const { room, lastOpen } = this.state;
|
||||
const { user } = this.props;
|
||||
let dateSeparator = null;
|
||||
let showUnreadSeparator = false;
|
||||
|
||||
if (!previousItem) {
|
||||
dateSeparator = item.ts;
|
||||
showUnreadSeparator = moment(item.ts).isAfter(lastOpen);
|
||||
} else {
|
||||
showUnreadSeparator = lastOpen
|
||||
&& moment(item.ts).isAfter(lastOpen)
|
||||
&& moment(previousItem.ts).isBefore(lastOpen);
|
||||
if (!moment(item.ts).isSame(previousItem.ts, 'day')) {
|
||||
dateSeparator = previousItem.ts;
|
||||
}
|
||||
}
|
||||
|
||||
if (showUnreadSeparator || dateSeparator) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Message
|
||||
key={item._id}
|
||||
item={item}
|
||||
status={item.status}
|
||||
reactions={JSON.parse(JSON.stringify(item.reactions))}
|
||||
user={user}
|
||||
archived={room.archived}
|
||||
broadcast={room.broadcast}
|
||||
previousItem={previousItem}
|
||||
_updatedAt={item._updatedAt}
|
||||
onReactionPress={this.onReactionPress}
|
||||
onLongPress={this.onMessageLongPress}
|
||||
/>
|
||||
<Separator
|
||||
ts={dateSeparator}
|
||||
unread={showUnreadSeparator}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Message
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
import { COLOR_SEPARATOR } from '../../constants/colors';
|
||||
|
||||
export default StyleSheet.create({
|
||||
typing: { fontWeight: 'bold', paddingHorizontal: 15, height: 25 },
|
||||
|
@ -10,12 +11,11 @@ export default StyleSheet.create({
|
|||
flex: 1
|
||||
},
|
||||
list: {
|
||||
flex: 1,
|
||||
transform: [{ scaleY: -1 }]
|
||||
flex: 1
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
backgroundColor: '#CED0CE'
|
||||
backgroundColor: COLOR_SEPARATOR
|
||||
},
|
||||
bannerContainer: {
|
||||
backgroundColor: 'orange'
|
||||
|
@ -26,7 +26,6 @@ export default StyleSheet.create({
|
|||
color: '#a00'
|
||||
},
|
||||
loadingMore: {
|
||||
transform: [{ scaleY: -1 }],
|
||||
textAlign: 'center',
|
||||
padding: 15,
|
||||
color: '#ccc'
|
||||
|
@ -46,11 +45,6 @@ export default StyleSheet.create({
|
|||
loading: {
|
||||
flex: 1
|
||||
},
|
||||
imageBackground: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute'
|
||||
},
|
||||
joinRoomContainer: {
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
|
|
|
@ -245,7 +245,8 @@ export default class RoomsListView extends LoggedView {
|
|||
}
|
||||
|
||||
internalSetState = (...args) => {
|
||||
if (isIOS) {
|
||||
const { navigation } = this.props;
|
||||
if (isIOS && navigation.isFocused()) {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
}
|
||||
this.setState(...args);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
import { isIOS } from '../../utils/deviceInfo';
|
||||
import { COLOR_SEPARATOR } from '../../constants/colors';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
|
@ -8,8 +9,8 @@ export default StyleSheet.create({
|
|||
},
|
||||
separator: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: '#E1E5E8',
|
||||
marginLeft: 78
|
||||
backgroundColor: COLOR_SEPARATOR,
|
||||
marginLeft: 73
|
||||
},
|
||||
list: {
|
||||
width: '100%',
|
||||
|
@ -36,7 +37,7 @@ export default StyleSheet.create({
|
|||
dropdownContainerHeader: {
|
||||
height: 41,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#E1E5E8',
|
||||
borderColor: COLOR_SEPARATOR,
|
||||
alignItems: 'center',
|
||||
backgroundColor: isIOS ? '#fff' : '#54585E',
|
||||
flexDirection: 'row'
|
||||
|
@ -79,7 +80,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
sortSeparator: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: '#CBCED1',
|
||||
backgroundColor: COLOR_SEPARATOR,
|
||||
marginHorizontal: 15,
|
||||
flex: 1
|
||||
},
|
||||
|
@ -154,7 +155,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
serverSeparator: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: '#E1E5E8',
|
||||
backgroundColor: COLOR_SEPARATOR,
|
||||
marginLeft: 72
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
import { COLOR_SEPARATOR } from '../../constants/colors';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
|
@ -19,7 +20,7 @@ export default StyleSheet.create({
|
|||
divider: {
|
||||
width: '100%',
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: '#E7EBF2',
|
||||
backgroundColor: COLOR_SEPARATOR,
|
||||
marginVertical: 20
|
||||
},
|
||||
listEmptyContainer: {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
import { COLOR_SEPARATOR } from '../../constants/colors';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
|
@ -27,7 +28,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
separator: {
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#E1E5E8',
|
||||
borderColor: COLOR_SEPARATOR,
|
||||
marginVertical: 4
|
||||
},
|
||||
header: {
|
||||
|
|
|
@ -117,8 +117,8 @@ describe('Create room screen', () => {
|
|||
await element(by.id('create-channel-submit')).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('room-view'))).toBeVisible();
|
||||
await waitFor(element(by.text(room))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.text(room))).toBeVisible();
|
||||
await waitFor(element(by.text(room))).toExist().withTimeout(60000);
|
||||
await expect(element(by.text(room))).toExist();
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(60000);
|
||||
|
@ -141,8 +141,8 @@ describe('Create room screen', () => {
|
|||
await element(by.id('create-channel-submit')).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('room-view'))).toBeVisible();
|
||||
await waitFor(element(by.text(room))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.text(room))).toBeVisible();
|
||||
await waitFor(element(by.text(room))).toExist().withTimeout(60000);
|
||||
await expect(element(by.text(room))).toExist();
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||
await element(by.id('rooms-list-view-search')).replaceText(room);
|
||||
|
@ -165,8 +165,8 @@ describe('Create room screen', () => {
|
|||
await element(by.id('create-channel-submit')).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('room-view'))).toBeVisible();
|
||||
await waitFor(element(by.text(room))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.text(room))).toBeVisible();
|
||||
await waitFor(element(by.text(room))).toExist().withTimeout(60000);
|
||||
await expect(element(by.text(room))).toExist();
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||
await element(by.id('rooms-list-view-search')).replaceText(room);
|
||||
|
|
|
@ -26,8 +26,8 @@ describe('Broadcast room', () => {
|
|||
await element(by.id('create-channel-submit')).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('room-view'))).toBeVisible();
|
||||
await waitFor(element(by.text(`broadcast${ data.random }`))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.text(`broadcast${ data.random }`))).toBeVisible();
|
||||
await waitFor(element(by.text(`broadcast${ data.random }`))).toExist().withTimeout(60000);
|
||||
await expect(element(by.text(`broadcast${ data.random }`))).toExist();
|
||||
await element(by.id('room-view-header-actions')).tap();
|
||||
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000);
|
||||
await element(by.id('room-actions-info')).tap();
|
||||
|
@ -76,8 +76,8 @@ describe('Broadcast room', () => {
|
|||
await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist();
|
||||
await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||
await waitFor(element(by.text(`broadcast${ data.random }`))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.text(`broadcast${ data.random }`))).toBeVisible();
|
||||
await waitFor(element(by.text(`broadcast${ data.random }`))).toExist().withTimeout(60000);
|
||||
await expect(element(by.text(`broadcast${ data.random }`))).toExist();
|
||||
});
|
||||
|
||||
it('should not have messagebox', async() => {
|
||||
|
|
|
@ -7,7 +7,7 @@ const data = {
|
|||
password: `password${ value }`,
|
||||
alternateUser: 'detoxrn',
|
||||
alternateUserPassword: '123',
|
||||
alternateUserTOTPSecret: 'KESVIUCQMZWEYNBMJJAUW4LYKRBVWYZ7HBWTIWDPIAZUOURTF4WA',
|
||||
alternateUserTOTPSecret: 'I5SGETK3GBXXA7LNLMZTEJJRIN3G6LTEEE4G4PS3EQRXU4LNPU7A',
|
||||
email: `diego.mello+e2e${ value }@rocket.chat`,
|
||||
random: value
|
||||
}
|
||||
|
|
|
@ -8,9 +8,8 @@ import MessageSeparator from '../../app/views/RoomView/Separator';
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
separator: {
|
||||
transform: [{ scaleY: -1 }],
|
||||
marginBottom: 30,
|
||||
marginTop: 0
|
||||
marginTop: 30,
|
||||
marginBottom: 0
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -44,14 +43,15 @@ const Message = props => (
|
|||
const Separator = ({ title }) => <StoriesSeparator title={title} style={styles.separator} />;
|
||||
|
||||
export default (
|
||||
<ScrollView style={{ flex: 1, transform: [{ scaleY: -1 }] }} contentContainerStyle={{ marginVertical: 30 }}>
|
||||
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ marginVertical: 30 }}>
|
||||
|
||||
<Message msg='Message' />
|
||||
<Separator title='Simple' />
|
||||
<Message msg='Message' />
|
||||
|
||||
<Message msg='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum' />
|
||||
<Separator title='Long message' />
|
||||
<Message msg='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum' />
|
||||
|
||||
<Separator title='Grouped messages' />
|
||||
<Message msg='...' />
|
||||
<Message
|
||||
msg='Different user'
|
||||
|
@ -63,23 +63,23 @@ export default (
|
|||
<Message msg='This is the third message' header={false} />
|
||||
<Message msg='This is the second message' header={false} />
|
||||
<Message msg='This is the first message' />
|
||||
<Separator title='Grouped messages' />
|
||||
|
||||
<Message msg='Message' header={false} />
|
||||
<Separator title='Without header' />
|
||||
<Message msg='Message' header={false} />
|
||||
|
||||
<Message msg='Message' alias='Diego Mello' />
|
||||
<Separator title='With alias' />
|
||||
<Message msg='Message' alias='Diego Mello' />
|
||||
|
||||
<Message msg='Message' edited />
|
||||
<Separator title='Edited' />
|
||||
<Message msg='Message' edited />
|
||||
|
||||
<Separator title='Static avatar' />
|
||||
<Message
|
||||
msg='Message'
|
||||
avatar='https://pbs.twimg.com/profile_images/1016397063649660929/14EIApTi_400x400.jpg'
|
||||
/>
|
||||
<Separator title='Static avatar' />
|
||||
|
||||
<Separator title='Full name' />
|
||||
<Message
|
||||
msg='Message'
|
||||
author={{
|
||||
|
@ -89,20 +89,20 @@ export default (
|
|||
}}
|
||||
useRealName
|
||||
/>
|
||||
<Separator title='Full name' />
|
||||
|
||||
<Message msg='@rocket.cat @diego.mello @all @here #general' />
|
||||
<Separator title='Mentions' />
|
||||
<Message msg='@rocket.cat @diego.mello @all @here #general' />
|
||||
|
||||
<Message msg='👊🤙👏' />
|
||||
<Separator title='Emojis' />
|
||||
<Message msg='👊🤙👏' />
|
||||
|
||||
<Message msg=':react_rocket: :nyan_rocket: :marioparty:' />
|
||||
<Separator title='Custom Emojis' />
|
||||
<Message msg=':react_rocket: :nyan_rocket: :marioparty:' />
|
||||
|
||||
<Message msg='Testing' timeFormat='DD MMMM YYYY' />
|
||||
<Separator title='Time format' />
|
||||
<Message msg='Testing' timeFormat='DD MMMM YYYY' />
|
||||
|
||||
<Separator title='Reactions' />
|
||||
<Message
|
||||
msg='Reactions'
|
||||
reactions={[{
|
||||
|
@ -117,8 +117,8 @@ export default (
|
|||
}]}
|
||||
onReactionPress={() => {}}
|
||||
/>
|
||||
<Separator title='Reactions' />
|
||||
|
||||
<Separator title='Multiple reactions' />
|
||||
<Message
|
||||
msg='Multiple Reactions'
|
||||
reactions={[{
|
||||
|
@ -148,8 +148,8 @@ export default (
|
|||
}]}
|
||||
onReactionPress={() => {}}
|
||||
/>
|
||||
<Separator title='Multiple reactions' />
|
||||
|
||||
<Separator title='Intercalated users' />
|
||||
<Message
|
||||
msg='Fourth message'
|
||||
author={{
|
||||
|
@ -166,8 +166,8 @@ export default (
|
|||
}}
|
||||
/>
|
||||
<Message msg='First message' />
|
||||
<Separator title='Intercalated users' />
|
||||
|
||||
<Separator title='Date and Unread separators' />
|
||||
<Message
|
||||
msg='Fourth message'
|
||||
author={{
|
||||
|
@ -195,8 +195,8 @@ export default (
|
|||
/>
|
||||
<MessageSeparator ts={date} />
|
||||
<Message msg='First message' />
|
||||
<Separator title='Date and Unread separators' />
|
||||
|
||||
<Separator title='With image' />
|
||||
<Message
|
||||
attachments={[{
|
||||
title: 'This is a title',
|
||||
|
@ -211,8 +211,8 @@ export default (
|
|||
image_url: '/file-upload/sxLXBzjwuqxMnebyP/Clipboard%20-%2029%20de%20Agosto%20de%202018%20%C3%A0s%2018:10'
|
||||
}]}
|
||||
/>
|
||||
<Separator title='With image' />
|
||||
|
||||
<Separator title='With video' />
|
||||
<Message
|
||||
attachments={[{
|
||||
title: 'This is a title',
|
||||
|
@ -220,8 +220,8 @@ export default (
|
|||
video_url: '/file-upload/cqnKqb6kdajky5Rxj/WhatsApp%20Video%202018-08-22%20at%2019.09.55.mp4'
|
||||
}]}
|
||||
/>
|
||||
<Separator title='With video' />
|
||||
|
||||
<Separator title='With audio' />
|
||||
<Message
|
||||
attachments={[{
|
||||
title: 'This is a title',
|
||||
|
@ -229,8 +229,8 @@ export default (
|
|||
audio_url: '/file-upload/c4wcNhrbXJLBvAJtN/1535569819516.aac'
|
||||
}]}
|
||||
/>
|
||||
<Separator title='With audio' />
|
||||
|
||||
<Separator title='Message with reply' />
|
||||
<Message
|
||||
msg="I'm fine!"
|
||||
attachments={[{
|
||||
|
@ -249,8 +249,8 @@ export default (
|
|||
text: 'How are you?'
|
||||
}]}
|
||||
/>
|
||||
<Separator title='Message with reply' />
|
||||
|
||||
<Separator title='URL' />
|
||||
<Message
|
||||
urls={[{
|
||||
url: 'https://rocket.chat',
|
||||
|
@ -263,8 +263,8 @@ export default (
|
|||
description: 'Search the world\'s information, including webpages, images, videos and more. Google has many special features to help you find exactly what you\'re looking for.'
|
||||
}]}
|
||||
/>
|
||||
<Separator title='URL' />
|
||||
|
||||
<Separator title='Custom fields' />
|
||||
<Message
|
||||
msg='Message'
|
||||
attachments={[{
|
||||
|
@ -290,8 +290,8 @@ export default (
|
|||
}]
|
||||
}]}
|
||||
/>
|
||||
<Separator title='Custom fields' />
|
||||
|
||||
<Separator title='Two short custom fields' />
|
||||
<Message
|
||||
msg='Message'
|
||||
attachments={[{
|
||||
|
@ -324,33 +324,33 @@ export default (
|
|||
}]
|
||||
}]}
|
||||
/>
|
||||
<Separator title='Two short custom fields' />
|
||||
|
||||
<Message msg='Broadcasted message' broadcast replyBroadcast={() => alert('broadcast!')} />
|
||||
<Separator title='Broadcast' />
|
||||
<Message msg='Broadcasted message' broadcast replyBroadcast={() => alert('broadcast!')} />
|
||||
|
||||
<Message msg='This message is inside an archived room' archived />
|
||||
<Separator title='Archived' />
|
||||
<Message msg='This message is inside an archived room' archived />
|
||||
|
||||
<Separator title='Error' />
|
||||
<Message msg='This message has error too' status={messagesStatus.ERROR} onErrorPress={() => alert('Error pressed')} header={false} />
|
||||
<Message msg='This message has error' status={messagesStatus.ERROR} onErrorPress={() => alert('Error pressed')} />
|
||||
<Separator title='Error' />
|
||||
|
||||
<Message msg='Temp message' status={messagesStatus.TEMP} />
|
||||
<Separator title='Temp' />
|
||||
<Message msg='Temp message' status={messagesStatus.TEMP} />
|
||||
|
||||
<Message msg='Message being edited' editing />
|
||||
<Separator title='Editing' />
|
||||
<Message msg='Message being edited' editing />
|
||||
|
||||
<Message type='rm' />
|
||||
<Separator title='Removed' />
|
||||
<Message type='rm' />
|
||||
|
||||
<Message type='uj' />
|
||||
<Separator title='Joined' />
|
||||
<Message type='uj' />
|
||||
|
||||
<Message msg='New name' type='r' />
|
||||
<Separator title='Room name changed' />
|
||||
<Message msg='New name' type='r' />
|
||||
|
||||
<Separator title='Message pinned' />
|
||||
<Message
|
||||
msg='New name'
|
||||
type='message_pinned'
|
||||
|
@ -361,55 +361,55 @@ export default (
|
|||
text: 'First message'
|
||||
}]}
|
||||
/>
|
||||
<Separator title='Message pinned' />
|
||||
|
||||
<Message type='ul' />
|
||||
<Separator title='Has left the channel' />
|
||||
<Message type='ul' />
|
||||
|
||||
<Message msg='rocket.cat' type='ru' />
|
||||
<Separator title='User removed' />
|
||||
<Message msg='rocket.cat' type='ru' />
|
||||
|
||||
<Message msg='rocket.cat' type='au' />
|
||||
<Separator title='User added' />
|
||||
<Message msg='rocket.cat' type='au' />
|
||||
|
||||
<Message msg='rocket.cat' type='user-muted' />
|
||||
<Separator title='User muted' />
|
||||
<Message msg='rocket.cat' type='user-muted' />
|
||||
|
||||
<Message msg='rocket.cat' type='user-unmuted' />
|
||||
<Separator title='User unmuted' />
|
||||
<Message msg='rocket.cat' type='user-unmuted' />
|
||||
|
||||
<Separator title='Role added' />
|
||||
<Message
|
||||
msg='rocket.cat'
|
||||
role='admin' // eslint-disable-line
|
||||
type='subscription-role-added'
|
||||
/>
|
||||
<Separator title='Role added' />
|
||||
|
||||
<Separator title='Role removed' />
|
||||
<Message
|
||||
msg='rocket.cat'
|
||||
role='admin' // eslint-disable-line
|
||||
type='subscription-role-removed'
|
||||
/>
|
||||
<Separator title='Role removed' />
|
||||
|
||||
<Message msg='new description' type='room_changed_description' />
|
||||
<Separator title='Changed description' />
|
||||
<Message msg='new description' type='room_changed_description' />
|
||||
|
||||
<Message msg='new announcement' type='room_changed_announcement' />
|
||||
<Separator title='Changed announcement' />
|
||||
<Message msg='new announcement' type='room_changed_announcement' />
|
||||
|
||||
<Message msg='new topic' type='room_changed_topic' />
|
||||
<Separator title='Changed topic' />
|
||||
<Message msg='new topic' type='room_changed_topic' />
|
||||
|
||||
<Message msg='public' type='room_changed_privacy' />
|
||||
<Separator title='Changed type' />
|
||||
<Message msg='public' type='room_changed_privacy' />
|
||||
|
||||
<Message msg='Message' style={[styles.normalize, { backgroundColor: '#ddd' }]} />
|
||||
<Separator title='Custom style' />
|
||||
<Message msg='Message' style={[styles.normalize, { backgroundColor: '#ddd' }]} />
|
||||
|
||||
<Message msg='Italic with *asterisks* or _underscores_. Bold with **asterisks** or __underscores__. ~~Strikethrough~~' />
|
||||
<Separator title='Markdown emphasis' />
|
||||
<Message msg='Italic with *asterisks* or _underscores_. Bold with **asterisks** or __underscores__. ~~Strikethrough~~' />
|
||||
|
||||
<Separator title='Markdown headers' />
|
||||
<Message
|
||||
msg='# H1
|
||||
## H2
|
||||
|
@ -418,31 +418,30 @@ export default (
|
|||
##### H5
|
||||
###### H6'
|
||||
/>
|
||||
<Separator title='Markdown headers' />
|
||||
|
||||
<Message msg='Support <http://google.com|Google> [I`m an inline-style link](https://www.google.com) https://google.com' />
|
||||
<Separator title='Markdown links' />
|
||||
<Message msg='Support <http://google.com|Google> [I`m an inline-style link](https://www.google.com) https://google.com' />
|
||||
|
||||
<Message msg='![alt text](https://play.google.com/intl/en_us/badges/images/badge_new.png)' />
|
||||
<Separator title='Markdown image' />
|
||||
<Message msg='![alt text](https://play.google.com/intl/en_us/badges/images/badge_new.png)' />
|
||||
|
||||
<Separator title='Markdown code' />
|
||||
<Message
|
||||
msg='Inline `code` has `back-ticks around` it.
|
||||
```
|
||||
Code block
|
||||
```'
|
||||
/>
|
||||
<Separator title='Markdown code' />
|
||||
|
||||
<Message msg='> Quote' />
|
||||
<Separator title='Markdown quote' />
|
||||
<Message msg='> Quote' />
|
||||
|
||||
<Separator title='Markdown table' />
|
||||
<Message
|
||||
msg='First Header | Second Header
|
||||
------------ | -------------
|
||||
Content from cell 1 | Content from cell 2
|
||||
Content in the first column | Content in the second column'
|
||||
/>
|
||||
<Separator title='Markdown table' />
|
||||
</ScrollView>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue