[FIX] Threads (#838)
Closes #826 Closes #827 Closes #828 Closes #829 Closes #830 Closes #831 Closes #832 Closes #833
This commit is contained in:
parent
0266cc2e01
commit
5744114d7d
File diff suppressed because it is too large
Load Diff
|
@ -11,6 +11,7 @@ export const COLOR_TEXT_DESCRIPTION = '#9ca2a8';
|
||||||
export const COLOR_SEPARATOR = '#A7A7AA';
|
export const COLOR_SEPARATOR = '#A7A7AA';
|
||||||
export const COLOR_BACKGROUND_CONTAINER = '#f3f4f5';
|
export const COLOR_BACKGROUND_CONTAINER = '#f3f4f5';
|
||||||
export const COLOR_BORDER = '#e1e5e8';
|
export const COLOR_BORDER = '#e1e5e8';
|
||||||
|
export const COLOR_UNREAD = '#e1e5e8';
|
||||||
export const STATUS_COLORS = {
|
export const STATUS_COLORS = {
|
||||||
online: '#2de0a5',
|
online: '#2de0a5',
|
||||||
busy: COLOR_DANGER,
|
busy: COLOR_DANGER,
|
||||||
|
|
|
@ -57,12 +57,14 @@ class MessageBox extends Component {
|
||||||
replying: PropTypes.bool,
|
replying: PropTypes.bool,
|
||||||
editing: PropTypes.bool,
|
editing: PropTypes.bool,
|
||||||
threadsEnabled: PropTypes.bool,
|
threadsEnabled: PropTypes.bool,
|
||||||
|
isFocused: PropTypes.bool,
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
username: PropTypes.string,
|
username: PropTypes.string,
|
||||||
token: PropTypes.string
|
token: PropTypes.string
|
||||||
}),
|
}),
|
||||||
roomType: PropTypes.string,
|
roomType: PropTypes.string,
|
||||||
|
tmid: PropTypes.string,
|
||||||
editCancel: PropTypes.func.isRequired,
|
editCancel: PropTypes.func.isRequired,
|
||||||
editRequest: PropTypes.func.isRequired,
|
editRequest: PropTypes.func.isRequired,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
|
@ -92,23 +94,37 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { rid } = this.props;
|
const { rid, tmid } = this.props;
|
||||||
|
let msg;
|
||||||
|
if (tmid) {
|
||||||
|
const thread = database.objectForPrimaryKey('threads', tmid);
|
||||||
|
if (thread) {
|
||||||
|
msg = thread.draftMessage;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||||
if (room && room.draftMessage) {
|
if (room) {
|
||||||
this.setInput(room.draftMessage);
|
msg = room.draftMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
this.setInput(msg);
|
||||||
this.setShowSend(true);
|
this.setShowSend(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const { message, replyMessage } = this.props;
|
const { message, replyMessage, isFocused } = this.props;
|
||||||
if (message !== nextProps.message && nextProps.message.msg) {
|
if (!isFocused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!equal(message, nextProps.message) && nextProps.message.msg) {
|
||||||
this.setInput(nextProps.message.msg);
|
this.setInput(nextProps.message.msg);
|
||||||
if (this.text) {
|
if (this.text) {
|
||||||
this.setShowSend(true);
|
this.setShowSend(true);
|
||||||
}
|
}
|
||||||
this.focus();
|
this.focus();
|
||||||
} else if (replyMessage !== nextProps.replyMessage && nextProps.replyMessage.msg) {
|
} else if (!equal(replyMessage, nextProps.replyMessage)) {
|
||||||
this.focus();
|
this.focus();
|
||||||
} else if (!nextProps.message) {
|
} else if (!nextProps.message) {
|
||||||
this.clearInput();
|
this.clearInput();
|
||||||
|
@ -120,8 +136,11 @@ class MessageBox extends Component {
|
||||||
showEmojiKeyboard, showFilesAction, showSend, recording, mentions, file
|
showEmojiKeyboard, showFilesAction, showSend, recording, mentions, file
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
roomType, replying, editing
|
roomType, replying, editing, isFocused
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
if (!isFocused) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (nextProps.roomType !== roomType) {
|
if (nextProps.roomType !== roomType) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -481,7 +500,7 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
sendImageMessage = async(file) => {
|
sendImageMessage = async(file) => {
|
||||||
const { rid } = this.props;
|
const { rid, tmid } = this.props;
|
||||||
|
|
||||||
this.setState({ file: { isVisible: false } });
|
this.setState({ file: { isVisible: false } });
|
||||||
const fileInfo = {
|
const fileInfo = {
|
||||||
|
@ -493,7 +512,7 @@ class MessageBox extends Component {
|
||||||
path: file.path
|
path: file.path
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await RocketChat.sendFileMessage(rid, fileInfo);
|
await RocketChat.sendFileMessage(rid, fileInfo, tmid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('sendImageMessage', e);
|
log('sendImageMessage', e);
|
||||||
}
|
}
|
||||||
|
@ -539,14 +558,14 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
finishAudioMessage = async(fileInfo) => {
|
finishAudioMessage = async(fileInfo) => {
|
||||||
const { rid } = this.props;
|
const { rid, tmid } = this.props;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
recording: false
|
recording: false
|
||||||
});
|
});
|
||||||
if (fileInfo) {
|
if (fileInfo) {
|
||||||
try {
|
try {
|
||||||
await RocketChat.sendFileMessage(rid, fileInfo);
|
await RocketChat.sendFileMessage(rid, fileInfo, tmid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e && e.error === 'error-file-too-large') {
|
if (e && e.error === 'error-file-too-large') {
|
||||||
return Alert.alert(I18n.t(e.error));
|
return Alert.alert(I18n.t(e.error));
|
||||||
|
@ -830,7 +849,7 @@ class MessageBox extends Component {
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
message: state.messages.message,
|
message: state.messages.message,
|
||||||
replyMessage: state.messages.replyMessage,
|
replyMessage: state.messages.replyMessage,
|
||||||
replying: state.messages.replyMessage && !!state.messages.replyMessage.msg,
|
replying: state.messages.replying,
|
||||||
editing: state.messages.editing,
|
editing: state.messages.editing,
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
threadsEnabled: state.settings.Threads_enabled,
|
threadsEnabled: state.settings.Threads_enabled,
|
||||||
|
|
|
@ -6,8 +6,8 @@ import {
|
||||||
import Video from 'react-native-video';
|
import Video from 'react-native-video';
|
||||||
import Slider from 'react-native-slider';
|
import Slider from 'react-native-slider';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
|
@ -27,7 +27,7 @@ const styles = StyleSheet.create({
|
||||||
marginBottom: 6
|
marginBottom: 6
|
||||||
},
|
},
|
||||||
playPauseButton: {
|
playPauseButton: {
|
||||||
width: 56,
|
marginHorizontal: 10,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'transparent'
|
backgroundColor: 'transparent'
|
||||||
},
|
},
|
||||||
|
@ -35,11 +35,10 @@ const styles = StyleSheet.create({
|
||||||
color: COLOR_PRIMARY
|
color: COLOR_PRIMARY
|
||||||
},
|
},
|
||||||
slider: {
|
slider: {
|
||||||
flex: 1,
|
flex: 1
|
||||||
marginRight: 10
|
|
||||||
},
|
},
|
||||||
duration: {
|
duration: {
|
||||||
marginRight: 16,
|
marginHorizontal: 12,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
...sharedStyles.textColorNormal,
|
...sharedStyles.textColorNormal,
|
||||||
...sharedStyles.textRegular
|
...sharedStyles.textRegular
|
||||||
|
@ -47,10 +46,16 @@ const styles = StyleSheet.create({
|
||||||
thumbStyle: {
|
thumbStyle: {
|
||||||
width: 12,
|
width: 12,
|
||||||
height: 12
|
height: 12
|
||||||
|
},
|
||||||
|
trackStyle: {
|
||||||
|
height: 2
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const formatTime = seconds => moment.utc(seconds * 1000).format('mm:ss');
|
const formatTime = seconds => moment.utc(seconds * 1000).format('mm:ss');
|
||||||
|
const BUTTON_HIT_SLOP = {
|
||||||
|
top: 12, right: 12, bottom: 12, left: 12
|
||||||
|
};
|
||||||
|
|
||||||
export default class Audio extends React.Component {
|
export default class Audio extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -97,30 +102,30 @@ export default class Audio extends React.Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad(data) {
|
onLoad = (data) => {
|
||||||
this.setState({ duration: data.duration > 0 ? data.duration : 0 });
|
this.setState({ duration: data.duration > 0 ? data.duration : 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgress(data) {
|
onProgress = (data) => {
|
||||||
const { duration } = this.state;
|
const { duration } = this.state;
|
||||||
if (data.currentTime <= duration) {
|
if (data.currentTime <= duration) {
|
||||||
this.setState({ currentTime: data.currentTime });
|
this.setState({ currentTime: data.currentTime });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnd() {
|
onEnd = () => {
|
||||||
this.setState({ paused: true, currentTime: 0 });
|
this.setState({ paused: true, currentTime: 0 });
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.player.seek(0);
|
this.player.seek(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getDuration() {
|
getDuration = () => {
|
||||||
const { duration } = this.state;
|
const { duration } = this.state;
|
||||||
return formatTime(duration);
|
return formatTime(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePlayPause() {
|
togglePlayPause = () => {
|
||||||
const { paused } = this.state;
|
const { paused } = this.state;
|
||||||
this.setState({ paused: !paused });
|
this.setState({ paused: !paused });
|
||||||
}
|
}
|
||||||
|
@ -152,16 +157,18 @@ export default class Audio extends React.Component {
|
||||||
paused={paused}
|
paused={paused}
|
||||||
repeat={false}
|
repeat={false}
|
||||||
/>
|
/>
|
||||||
<BorderlessButton
|
<Touchable
|
||||||
style={styles.playPauseButton}
|
style={styles.playPauseButton}
|
||||||
onPress={() => this.togglePlayPause()}
|
onPress={this.togglePlayPause}
|
||||||
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
|
background={Touchable.SelectableBackgroundBorderless()}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
paused
|
paused
|
||||||
? <CustomIcon name='play' size={30} style={styles.playPauseImage} />
|
? <CustomIcon name='play' size={36} style={styles.playPauseImage} />
|
||||||
: <CustomIcon name='pause' size={30} style={styles.playPauseImage} />
|
: <CustomIcon name='pause' size={36} style={styles.playPauseImage} />
|
||||||
}
|
}
|
||||||
</BorderlessButton>
|
</Touchable>
|
||||||
<Slider
|
<Slider
|
||||||
style={styles.slider}
|
style={styles.slider}
|
||||||
value={currentTime}
|
value={currentTime}
|
||||||
|
@ -177,6 +184,7 @@ export default class Audio extends React.Component {
|
||||||
minimumTrackTintColor={COLOR_PRIMARY}
|
minimumTrackTintColor={COLOR_PRIMARY}
|
||||||
onValueChange={value => this.setState({ currentTime: value })}
|
onValueChange={value => this.setState({ currentTime: value })}
|
||||||
thumbStyle={styles.thumbStyle}
|
thumbStyle={styles.thumbStyle}
|
||||||
|
trackStyle={styles.trackStyle}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.duration}>{this.getDuration()}</Text>
|
<Text style={styles.duration}>{this.getDuration()}</Text>
|
||||||
</View>,
|
</View>,
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default class Markdown extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
msg, customEmojis, style, rules, baseUrl, username, edited
|
msg, customEmojis, style, rules, baseUrl, username, edited, numberOfLines
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -32,12 +32,15 @@ export default class Markdown extends React.Component {
|
||||||
m = emojify(m, { output: 'unicode' });
|
m = emojify(m, { output: 'unicode' });
|
||||||
}
|
}
|
||||||
m = m.replace(/^\[([^\]]*)\]\(([^)]*)\)/, '').trim();
|
m = m.replace(/^\[([^\]]*)\]\(([^)]*)\)/, '').trim();
|
||||||
|
if (numberOfLines > 0) {
|
||||||
|
m = m.replace(/[\n]+/g, '\n').trim();
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<MarkdownRenderer
|
<MarkdownRenderer
|
||||||
rules={{
|
rules={{
|
||||||
paragraph: (node, children) => (
|
paragraph: (node, children) => (
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
<Text key={node.key} style={styles.paragraph}>
|
<Text key={node.key} style={styles.paragraph} numberOfLines={numberOfLines}>
|
||||||
{children}
|
{children}
|
||||||
{edited ? <Text style={styles.edited}> (edited)</Text> : null}
|
{edited ? <Text style={styles.edited}> (edited)</Text> : null}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -111,5 +114,6 @@ Markdown.propTypes = {
|
||||||
customEmojis: PropTypes.object.isRequired,
|
customEmojis: PropTypes.object.isRequired,
|
||||||
style: PropTypes.any,
|
style: PropTypes.any,
|
||||||
rules: PropTypes.object,
|
rules: PropTypes.object,
|
||||||
edited: PropTypes.bool
|
edited: PropTypes.bool,
|
||||||
|
numberOfLines: PropTypes.number
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,10 +5,8 @@ import {
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { KeyboardUtils } from 'react-native-keyboard-input';
|
import { KeyboardUtils } from 'react-native-keyboard-input';
|
||||||
import {
|
|
||||||
BorderlessButton
|
|
||||||
} from 'react-native-gesture-handler';
|
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
import { emojify } from 'react-emojione';
|
||||||
|
|
||||||
import Image from './Image';
|
import Image from './Image';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
|
@ -164,6 +162,11 @@ export default class Message extends PureComponent {
|
||||||
|
|
||||||
onPress = () => {
|
onPress = () => {
|
||||||
KeyboardUtils.dismiss();
|
KeyboardUtils.dismiss();
|
||||||
|
|
||||||
|
const { onThreadPress, tlm, tmid } = this.props;
|
||||||
|
if ((tlm || tmid) && onThreadPress) {
|
||||||
|
onThreadPress();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onLongPress = () => {
|
onLongPress = () => {
|
||||||
|
@ -269,10 +272,25 @@ export default class Message extends PureComponent {
|
||||||
if (this.isInfoMessage()) {
|
if (this.isInfoMessage()) {
|
||||||
return <Text style={styles.textInfo}>{getInfoMessage({ ...this.props })}</Text>;
|
return <Text style={styles.textInfo}>{getInfoMessage({ ...this.props })}</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
customEmojis, msg, baseUrl, user, edited
|
customEmojis, msg, baseUrl, user, edited, tmid
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return <Markdown msg={msg} customEmojis={customEmojis} baseUrl={baseUrl} username={user.username} edited={edited} />;
|
|
||||||
|
if (tmid && !msg) {
|
||||||
|
return <Text style={styles.text}>{I18n.t('Sent_an_attachment')}</Text>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Markdown
|
||||||
|
msg={msg}
|
||||||
|
customEmojis={customEmojis}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
username={user.username}
|
||||||
|
edited={edited}
|
||||||
|
numberOfLines={tmid ? 1 : 0}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAttachment() {
|
renderAttachment() {
|
||||||
|
@ -316,9 +334,9 @@ export default class Message extends PureComponent {
|
||||||
}
|
}
|
||||||
const { onErrorPress } = this.props;
|
const { onErrorPress } = this.props;
|
||||||
return (
|
return (
|
||||||
<BorderlessButton onPress={onErrorPress} style={styles.errorButton}>
|
<Touchable onPress={onErrorPress} style={styles.errorButton}>
|
||||||
<CustomIcon name='circle-cross' color={COLOR_DANGER} size={20} />
|
<CustomIcon name='circle-cross' color={COLOR_DANGER} size={20} />
|
||||||
</BorderlessButton>
|
</Touchable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -457,7 +475,7 @@ export default class Message extends PureComponent {
|
||||||
|
|
||||||
renderRepliedThread = () => {
|
renderRepliedThread = () => {
|
||||||
const {
|
const {
|
||||||
tmid, tmsg, header, onThreadPress, fetchThreadName
|
tmid, tmsg, header, fetchThreadName
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (!tmid || !header || this.isTemp()) {
|
if (!tmid || !header || this.isTemp()) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -468,15 +486,18 @@ export default class Message extends PureComponent {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const msg = emojify(tmsg, { output: 'unicode' });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text style={styles.repliedThread} numberOfLines={3} testID={`message-thread-replied-on-${ tmsg }`}>
|
<View style={styles.repliedThread} testID={`message-thread-replied-on-${ msg }`}>
|
||||||
{I18n.t('Replied_on')} <Text style={styles.repliedThreadName} onPress={onThreadPress}>{tmsg}</Text>
|
<CustomIcon name='thread' size={20} style={[styles.buttonIcon, styles.repliedThreadIcon]} />
|
||||||
</Text>
|
<Text style={styles.repliedThreadName} numberOfLines={1}>{msg}</Text>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderInner = () => {
|
renderInner = () => {
|
||||||
const { type } = this.props;
|
const { type, tmid } = this.props;
|
||||||
if (type === 'discussion-created') {
|
if (type === 'discussion-created') {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -485,11 +506,19 @@ export default class Message extends PureComponent {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (tmid) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{this.renderUsername()}
|
{this.renderUsername()}
|
||||||
{this.renderRepliedThread()}
|
{this.renderRepliedThread()}
|
||||||
{this.renderContent()}
|
{this.renderContent()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{this.renderUsername()}
|
||||||
|
{this.renderContent()}
|
||||||
{this.renderAttachment()}
|
{this.renderAttachment()}
|
||||||
{this.renderUrl()}
|
{this.renderUrl()}
|
||||||
{this.renderThread()}
|
{this.renderThread()}
|
||||||
|
|
|
@ -76,7 +76,7 @@ export default class MessageContainer extends React.Component {
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const { reactionsModal } = this.state;
|
const { reactionsModal } = this.state;
|
||||||
const {
|
const {
|
||||||
status, editingMessage, item, _updatedAt
|
status, editingMessage, item, _updatedAt, navigation
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (reactionsModal !== nextState.reactionsModal) {
|
if (reactionsModal !== nextState.reactionsModal) {
|
||||||
|
@ -89,7 +89,7 @@ export default class MessageContainer extends React.Component {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!equal(editingMessage, nextProps.editingMessage)) {
|
if (navigation.isFocused() && !equal(editingMessage, nextProps.editingMessage)) {
|
||||||
if (nextProps.editingMessage && nextProps.editingMessage._id === item._id) {
|
if (nextProps.editingMessage && nextProps.editingMessage._id === item._id) {
|
||||||
return true;
|
return true;
|
||||||
} else if (!nextProps.editingMessage._id !== item._id && editingMessage._id === item._id) {
|
} else if (!nextProps.editingMessage._id !== item._id && editingMessage._id === item._id) {
|
||||||
|
|
|
@ -216,13 +216,16 @@ export default StyleSheet.create({
|
||||||
fontWeight: '300'
|
fontWeight: '300'
|
||||||
},
|
},
|
||||||
repliedThread: {
|
repliedThread: {
|
||||||
fontSize: 16,
|
flexDirection: 'row',
|
||||||
marginBottom: 6,
|
flex: 1
|
||||||
...sharedStyles.textColorDescription,
|
},
|
||||||
...sharedStyles.textRegular
|
repliedThreadIcon: {
|
||||||
|
color: COLOR_PRIMARY
|
||||||
},
|
},
|
||||||
repliedThreadName: {
|
repliedThreadName: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
fontStyle: 'normal',
|
||||||
|
flex: 1,
|
||||||
color: COLOR_PRIMARY,
|
color: COLOR_PRIMARY,
|
||||||
...sharedStyles.textSemibold
|
...sharedStyles.textSemibold
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,6 +293,7 @@ export default {
|
||||||
Send: 'Send',
|
Send: 'Send',
|
||||||
Send_audio_message: 'Send audio message',
|
Send_audio_message: 'Send audio message',
|
||||||
Send_message: 'Send message',
|
Send_message: 'Send message',
|
||||||
|
Sent_an_attachment: 'Sent an attachment',
|
||||||
Server: 'Server',
|
Server: 'Server',
|
||||||
Servers: 'Servers',
|
Servers: 'Servers',
|
||||||
Set_username_subtitle: 'The username is used to allow others to mention you in messages',
|
Set_username_subtitle: 'The username is used to allow others to mention you in messages',
|
||||||
|
|
|
@ -295,6 +295,7 @@ export default {
|
||||||
Send: 'Enviar',
|
Send: 'Enviar',
|
||||||
Send_audio_message: 'Enviar mensagem de áudio',
|
Send_audio_message: 'Enviar mensagem de áudio',
|
||||||
Send_message: 'Enviar mensagem',
|
Send_message: 'Enviar mensagem',
|
||||||
|
Sent_an_attachment: 'Enviou um anexo',
|
||||||
Server: 'Servidor',
|
Server: 'Servidor',
|
||||||
Set_username_subtitle: 'O usuário é utilizado para permitir que você seja mencionado em mensagens',
|
Set_username_subtitle: 'O usuário é utilizado para permitir que você seja mencionado em mensagens',
|
||||||
Settings: 'Configurações',
|
Settings: 'Configurações',
|
||||||
|
|
|
@ -284,6 +284,7 @@ export default {
|
||||||
Send: 'Enviar',
|
Send: 'Enviar',
|
||||||
Send_audio_message: 'Enviar mensagem de áudio',
|
Send_audio_message: 'Enviar mensagem de áudio',
|
||||||
Send_message: 'Enviar mensagem',
|
Send_message: 'Enviar mensagem',
|
||||||
|
Sent_an_attachment: 'Enviou um ficheiro',
|
||||||
Server: 'Servidor',
|
Server: 'Servidor',
|
||||||
Servers: 'Servidores',
|
Servers: 'Servidores',
|
||||||
Set_username_subtitle: 'O nome de utilizador é usado para permitir que outros mencionem você em mensagens',
|
Set_username_subtitle: 'O nome de utilizador é usado para permitir que outros mencionem você em mensagens',
|
||||||
|
|
|
@ -145,7 +145,7 @@ const ProfileStack = createStackNavigator({
|
||||||
defaultNavigationOptions: defaultHeader
|
defaultNavigationOptions: defaultHeader
|
||||||
});
|
});
|
||||||
|
|
||||||
ProfileView.navigationOptions = ({ navigation }) => {
|
ProfileStack.navigationOptions = ({ navigation }) => {
|
||||||
let drawerLockMode = 'unlocked';
|
let drawerLockMode = 'unlocked';
|
||||||
if (navigation.state.index > 0) {
|
if (navigation.state.index > 0) {
|
||||||
drawerLockMode = 'locked-closed';
|
drawerLockMode = 'locked-closed';
|
||||||
|
|
|
@ -46,6 +46,11 @@ export default function loadMessagesForRoom(...args) {
|
||||||
if (message.tlm) {
|
if (message.tlm) {
|
||||||
database.create('threads', message, true);
|
database.create('threads', message, true);
|
||||||
}
|
}
|
||||||
|
// if it belongs to a thread
|
||||||
|
if (message.tmid) {
|
||||||
|
message.rid = message.tmid;
|
||||||
|
database.create('threadMessages', message, true);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('loadMessagesForRoom -> create messages', e);
|
log('loadMessagesForRoom -> create messages', e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,11 +40,14 @@ export default function loadMissedMessages(...args) {
|
||||||
if (message.tlm) {
|
if (message.tlm) {
|
||||||
database.create('threads', message, true);
|
database.create('threads', message, true);
|
||||||
}
|
}
|
||||||
|
if (message.tmid) {
|
||||||
|
message.rid = message.tmid;
|
||||||
|
database.create('threadMessages', message, true);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('loadMissedMessages -> create messages', e);
|
log('loadMissedMessages -> create messages', e);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
resolve(updated);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (data.deleted && data.deleted.length) {
|
if (data.deleted && data.deleted.length) {
|
||||||
|
@ -55,6 +58,10 @@ export default function loadMissedMessages(...args) {
|
||||||
deleted.forEach((m) => {
|
deleted.forEach((m) => {
|
||||||
const message = database.objects('messages').filtered('_id = $0', m._id);
|
const message = database.objects('messages').filtered('_id = $0', m._id);
|
||||||
database.delete(message);
|
database.delete(message);
|
||||||
|
const thread = database.objects('threads').filtered('_id = $0', m._id);
|
||||||
|
database.delete(thread);
|
||||||
|
const threadMessage = database.objects('threadMessages').filtered('_id = $0', m._id);
|
||||||
|
database.delete(threadMessage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -63,7 +70,7 @@ export default function loadMissedMessages(...args) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve([]);
|
resolve();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('loadMissedMessages', e);
|
log('loadMissedMessages', e);
|
||||||
reject(e);
|
reject(e);
|
||||||
|
|
|
@ -5,24 +5,26 @@ import buildMessage from './helpers/buildMessage';
|
||||||
import database from '../realm';
|
import database from '../realm';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
|
||||||
async function load({ tmid, skip }) {
|
async function load({ tmid, offset }) {
|
||||||
try {
|
try {
|
||||||
// RC 1.0
|
// RC 1.0
|
||||||
const data = await this.sdk.methodCall('getThreadMessages', { tmid, limit: 50, skip });
|
const result = await this.sdk.get('chat.getThreadMessages', {
|
||||||
if (!data || data.status === 'error') {
|
tmid, count: 50, offset, sort: { ts: -1 }
|
||||||
|
});
|
||||||
|
if (!result || !result.success) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return data;
|
return result.messages;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function loadThreadMessages({ tmid, skip }) {
|
export default function loadThreadMessages({ tmid, offset = 0 }) {
|
||||||
return new Promise(async(resolve, reject) => {
|
return new Promise(async(resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const data = await load.call(this, { tmid, skip });
|
const data = await load.call(this, { tmid, offset });
|
||||||
|
|
||||||
if (data && data.length) {
|
if (data && data.length) {
|
||||||
InteractionManager.runAfterInteractions(() => {
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
|
|
@ -29,7 +29,7 @@ export async function cancelUpload(path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendFileMessage(rid, fileInfo) {
|
export async function sendFileMessage(rid, fileInfo, tmid) {
|
||||||
try {
|
try {
|
||||||
const data = await RNFetchBlob.wrap(fileInfo.path);
|
const data = await RNFetchBlob.wrap(fileInfo.path);
|
||||||
if (!fileInfo.size) {
|
if (!fileInfo.size) {
|
||||||
|
@ -86,6 +86,8 @@ export async function sendFileMessage(rid, fileInfo) {
|
||||||
name: completeResult.name,
|
name: completeResult.name,
|
||||||
description: completeResult.description,
|
description: completeResult.description,
|
||||||
url: completeResult.path
|
url: completeResult.path
|
||||||
|
}, {
|
||||||
|
tmid
|
||||||
});
|
});
|
||||||
|
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
|
|
|
@ -104,7 +104,8 @@ const subscriptionSchema = {
|
||||||
muted: { type: 'list', objectType: 'usersMuted' },
|
muted: { type: 'list', objectType: 'usersMuted' },
|
||||||
broadcast: { type: 'bool', optional: true },
|
broadcast: { type: 'bool', optional: true },
|
||||||
prid: { type: 'string', optional: true },
|
prid: { type: 'string', optional: true },
|
||||||
draftMessage: { type: 'string', optional: true }
|
draftMessage: { type: 'string', optional: true },
|
||||||
|
lastThreadSync: 'date?'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -259,7 +260,8 @@ const threadsSchema = {
|
||||||
tmid: { type: 'string', optional: true },
|
tmid: { type: 'string', optional: true },
|
||||||
tcount: { type: 'int', optional: true },
|
tcount: { type: 'int', optional: true },
|
||||||
tlm: { type: 'date', optional: true },
|
tlm: { type: 'date', optional: true },
|
||||||
replies: 'string[]'
|
replies: 'string[]',
|
||||||
|
draftMessage: 'string?'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -387,9 +389,9 @@ class DB {
|
||||||
schema: [
|
schema: [
|
||||||
serversSchema
|
serversSchema
|
||||||
],
|
],
|
||||||
schemaVersion: 4,
|
schemaVersion: 5,
|
||||||
migration: (oldRealm, newRealm) => {
|
migration: (oldRealm, newRealm) => {
|
||||||
if (oldRealm.schemaVersion >= 1 && newRealm.schemaVersion <= 3) {
|
if (oldRealm.schemaVersion >= 1 && newRealm.schemaVersion <= 5) {
|
||||||
const newServers = newRealm.objects('servers');
|
const newServers = newRealm.objects('servers');
|
||||||
|
|
||||||
// eslint-disable-next-line no-plusplus
|
// eslint-disable-next-line no-plusplus
|
||||||
|
@ -441,15 +443,12 @@ class DB {
|
||||||
|
|
||||||
setActiveDB(database = '') {
|
setActiveDB(database = '') {
|
||||||
const path = database.replace(/(^\w+:|^)\/\//, '');
|
const path = database.replace(/(^\w+:|^)\/\//, '');
|
||||||
if (this.database) {
|
|
||||||
this.database.close();
|
|
||||||
}
|
|
||||||
return this.databases.activeDB = new Realm({
|
return this.databases.activeDB = new Realm({
|
||||||
path: `${ path }.realm`,
|
path: `${ path }.realm`,
|
||||||
schema,
|
schema,
|
||||||
schemaVersion: 6,
|
schemaVersion: 8,
|
||||||
migration: (oldRealm, newRealm) => {
|
migration: (oldRealm, newRealm) => {
|
||||||
if (oldRealm.schemaVersion >= 3 && newRealm.schemaVersion <= 6) {
|
if (oldRealm.schemaVersion >= 3 && newRealm.schemaVersion <= 8) {
|
||||||
const newSubs = newRealm.objects('subscriptions');
|
const newSubs = newRealm.objects('subscriptions');
|
||||||
|
|
||||||
// eslint-disable-next-line no-plusplus
|
// eslint-disable-next-line no-plusplus
|
||||||
|
@ -459,6 +458,10 @@ class DB {
|
||||||
}
|
}
|
||||||
const newMessages = newRealm.objects('messages');
|
const newMessages = newRealm.objects('messages');
|
||||||
newRealm.delete(newMessages);
|
newRealm.delete(newMessages);
|
||||||
|
const newThreads = newRealm.objects('threads');
|
||||||
|
newRealm.delete(newThreads);
|
||||||
|
const newThreadMessages = newRealm.objects('threadMessages');
|
||||||
|
newRealm.delete(newThreadMessages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -416,8 +416,6 @@ const RocketChat = {
|
||||||
data = data.filtered('t != $0', 'd');
|
data = data.filtered('t != $0', 'd');
|
||||||
}
|
}
|
||||||
data = data.slice(0, 7);
|
data = data.slice(0, 7);
|
||||||
const array = Array.from(data);
|
|
||||||
data = JSON.parse(JSON.stringify(array));
|
|
||||||
|
|
||||||
const usernames = data.map(sub => sub.name);
|
const usernames = data.map(sub => sub.name);
|
||||||
try {
|
try {
|
||||||
|
@ -782,9 +780,17 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
return this.sdk.methodCall('unfollowMessage', { mid });
|
return this.sdk.methodCall('unfollowMessage', { mid });
|
||||||
},
|
},
|
||||||
getThreadsList({ rid, limit, skip }) {
|
getThreadsList({ rid, count, offset }) {
|
||||||
// RC 1.0
|
// RC 1.0
|
||||||
return this.sdk.methodCall('getThreadsList', { rid, limit, skip });
|
return this.sdk.get('chat.getThreadsList', {
|
||||||
|
rid, count, offset, sort: { ts: -1 }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getSyncThreadsList({ rid, updatedSince }) {
|
||||||
|
// RC 1.0
|
||||||
|
return this.sdk.get('chat.syncThreadsList', {
|
||||||
|
rid, updatedSince
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ const formatMsg = ({
|
||||||
const arePropsEqual = (oldProps, newProps) => _.isEqual(oldProps, newProps);
|
const arePropsEqual = (oldProps, newProps) => _.isEqual(oldProps, newProps);
|
||||||
|
|
||||||
const LastMessage = React.memo(({
|
const LastMessage = React.memo(({
|
||||||
lastMessage, type, showLastMessage, username
|
lastMessage, type, showLastMessage, username, alert
|
||||||
}) => (
|
}) => (
|
||||||
<Text style={[styles.markdownText, alert && styles.markdownTextAlert]} numberOfLines={2}>
|
<Text style={[styles.markdownText, alert && styles.markdownTextAlert]} numberOfLines={2}>
|
||||||
{formatMsg({
|
{formatMsg({
|
||||||
|
@ -54,7 +54,8 @@ LastMessage.propTypes = {
|
||||||
lastMessage: PropTypes.object,
|
lastMessage: PropTypes.object,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
showLastMessage: PropTypes.bool,
|
showLastMessage: PropTypes.bool,
|
||||||
username: PropTypes.string
|
username: PropTypes.string,
|
||||||
|
alert: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LastMessage;
|
export default LastMessage;
|
||||||
|
|
|
@ -14,8 +14,8 @@ const UnreadBadge = React.memo(({ unread, userMentions, type }) => {
|
||||||
const mentioned = userMentions > 0 && type !== 'd';
|
const mentioned = userMentions > 0 && type !== 'd';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.unreadNumberContainer, mentioned && styles.unreadMentioned]}>
|
<View style={[styles.unreadNumberContainer, mentioned && styles.unreadMentionedContainer]}>
|
||||||
<Text style={styles.unreadNumberText}>{ unread }</Text>
|
<Text style={[styles.unreadText, mentioned && styles.unreadMentionedText]}>{ unread }</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -109,7 +109,7 @@ export default class RoomItem extends React.Component {
|
||||||
{_updatedAt ? <Text style={[styles.date, alert && styles.updateAlert]} ellipsizeMode='tail' numberOfLines={1}>{ date }</Text> : null}
|
{_updatedAt ? <Text style={[styles.date, alert && styles.updateAlert]} ellipsizeMode='tail' numberOfLines={1}>{ date }</Text> : null}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<LastMessage lastMessage={lastMessage} type={type} showLastMessage={showLastMessage} username={username} />
|
<LastMessage lastMessage={lastMessage} type={type} showLastMessage={showLastMessage} username={username} alert={alert} />
|
||||||
<UnreadBadge unread={unread} userMentions={userMentions} type={type} />
|
<UnreadBadge unread={unread} userMentions={userMentions} type={type} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { StyleSheet, PixelRatio } from 'react-native';
|
||||||
|
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import {
|
import {
|
||||||
COLOR_SEPARATOR, COLOR_PRIMARY, COLOR_WHITE, COLOR_TEXT
|
COLOR_SEPARATOR, COLOR_PRIMARY, COLOR_WHITE, COLOR_UNREAD, COLOR_TEXT
|
||||||
} from '../../constants/colors';
|
} from '../../constants/colors';
|
||||||
|
|
||||||
export const ROW_HEIGHT = 75 * PixelRatio.getFontScale();
|
export const ROW_HEIGHT = 75 * PixelRatio.getFontScale();
|
||||||
|
@ -53,27 +53,30 @@ export default StyleSheet.create({
|
||||||
...sharedStyles.textSemibold
|
...sharedStyles.textSemibold
|
||||||
},
|
},
|
||||||
unreadNumberContainer: {
|
unreadNumberContainer: {
|
||||||
minWidth: 22,
|
minWidth: 21,
|
||||||
height: 22,
|
height: 21,
|
||||||
paddingVertical: 3,
|
paddingVertical: 3,
|
||||||
paddingHorizontal: 5,
|
paddingHorizontal: 5,
|
||||||
borderRadius: 14,
|
borderRadius: 10.5,
|
||||||
backgroundColor: COLOR_TEXT,
|
backgroundColor: COLOR_UNREAD,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginLeft: 10
|
marginLeft: 10
|
||||||
},
|
},
|
||||||
unreadMentioned: {
|
unreadMentionedContainer: {
|
||||||
backgroundColor: COLOR_PRIMARY
|
backgroundColor: COLOR_PRIMARY
|
||||||
},
|
},
|
||||||
unreadNumberText: {
|
unreadText: {
|
||||||
color: COLOR_WHITE,
|
color: COLOR_TEXT,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
...sharedStyles.textRegular,
|
...sharedStyles.textMedium,
|
||||||
letterSpacing: 0.56,
|
letterSpacing: 0.56,
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
},
|
},
|
||||||
|
unreadMentionedText: {
|
||||||
|
color: COLOR_WHITE
|
||||||
|
},
|
||||||
status: {
|
status: {
|
||||||
marginRight: 7,
|
marginRight: 7,
|
||||||
marginTop: 3
|
marginTop: 3
|
||||||
|
|
|
@ -4,6 +4,7 @@ const initialState = {
|
||||||
message: {},
|
message: {},
|
||||||
actionMessage: {},
|
actionMessage: {},
|
||||||
replyMessage: {},
|
replyMessage: {},
|
||||||
|
replying: false,
|
||||||
editing: false,
|
editing: false,
|
||||||
showActions: false,
|
showActions: false,
|
||||||
showErrorActions: false,
|
showErrorActions: false,
|
||||||
|
@ -64,12 +65,14 @@ export default function messages(state = initialState, action) {
|
||||||
replyMessage: {
|
replyMessage: {
|
||||||
...action.message,
|
...action.message,
|
||||||
mention: action.mention
|
mention: action.mention
|
||||||
}
|
},
|
||||||
|
replying: true
|
||||||
};
|
};
|
||||||
case types.MESSAGES.REPLY_CANCEL:
|
case types.MESSAGES.REPLY_CANCEL:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
replyMessage: {}
|
replyMessage: {},
|
||||||
|
replying: false
|
||||||
};
|
};
|
||||||
case types.MESSAGES.SET_INPUT:
|
case types.MESSAGES.SET_INPUT:
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -10,10 +10,14 @@ import log from '../../../utils/log';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
more: {
|
more: {
|
||||||
marginHorizontal: 0, marginLeft: 0, marginRight: 5
|
marginHorizontal: 0,
|
||||||
|
marginLeft: 0,
|
||||||
|
marginRight: 5
|
||||||
},
|
},
|
||||||
thread: {
|
thread: {
|
||||||
marginHorizontal: 0, marginLeft: 0, marginRight: 10
|
marginHorizontal: 0,
|
||||||
|
marginLeft: 0,
|
||||||
|
marginRight: 15
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,6 +38,7 @@ class RightButtonsContainer extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
if (props.tmid) {
|
if (props.tmid) {
|
||||||
|
// FIXME: it may be empty if the thread header isn't fetched yet
|
||||||
this.thread = database.objectForPrimaryKey('messages', props.tmid);
|
this.thread = database.objectForPrimaryKey('messages', props.tmid);
|
||||||
safeAddListener(this.thread, this.updateThread);
|
safeAddListener(this.thread, this.updateThread);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ActivityIndicator, FlatList, InteractionManager } from 'react-native';
|
import { ActivityIndicator, FlatList, InteractionManager } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { emojify } from 'react-emojione';
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -30,7 +29,7 @@ export class List extends React.PureComponent {
|
||||||
.objects('threadMessages')
|
.objects('threadMessages')
|
||||||
.filtered('rid = $0', props.tmid)
|
.filtered('rid = $0', props.tmid)
|
||||||
.sorted('ts', true);
|
.sorted('ts', true);
|
||||||
this.threads = [];
|
this.threads = database.objects('threads').filtered('_id = $0', props.tmid);
|
||||||
} else {
|
} else {
|
||||||
this.data = database
|
this.data = database
|
||||||
.objects('messages')
|
.objects('messages')
|
||||||
|
@ -83,7 +82,7 @@ export class List extends React.PureComponent {
|
||||||
});
|
});
|
||||||
}, 300, { leading: true });
|
}, 300, { leading: true });
|
||||||
|
|
||||||
onEndReached = async() => {
|
onEndReached = debounce(async() => {
|
||||||
const {
|
const {
|
||||||
loading, end, messages
|
loading, end, messages
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
@ -96,17 +95,17 @@ export class List extends React.PureComponent {
|
||||||
try {
|
try {
|
||||||
let result;
|
let result;
|
||||||
if (tmid) {
|
if (tmid) {
|
||||||
result = await RocketChat.loadThreadMessages({ tmid, skip: messages.length });
|
result = await RocketChat.loadThreadMessages({ tmid, offset: messages.length });
|
||||||
} else {
|
} else {
|
||||||
result = await RocketChat.loadMessagesForRoom({ rid, t, latest: messages[messages.length - 1].ts });
|
result = await RocketChat.loadMessagesForRoom({ rid, t, latest: messages[messages.length - 1].ts });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ end: result.length < 50 });
|
this.setState({ end: result.length < 50, loading: false });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
log('ListView.onEndReached', e);
|
log('ListView.onEndReached', e);
|
||||||
}
|
}
|
||||||
}
|
}, 300)
|
||||||
|
|
||||||
renderFooter = () => {
|
renderFooter = () => {
|
||||||
const { loading } = this.state;
|
const { loading } = this.state;
|
||||||
|
@ -122,10 +121,7 @@ export class List extends React.PureComponent {
|
||||||
if (item.tmid) {
|
if (item.tmid) {
|
||||||
const thread = threads.find(t => t._id === item.tmid);
|
const thread = threads.find(t => t._id === item.tmid);
|
||||||
if (thread) {
|
if (thread) {
|
||||||
let tmsg = thread.msg || (thread.attachments && thread.attachments.length && thread.attachments[0].title);
|
const tmsg = thread.msg || (thread.attachments && thread.attachments.length && thread.attachments[0].title);
|
||||||
if (tmsg) {
|
|
||||||
tmsg = emojify(tmsg, { output: 'unicode' });
|
|
||||||
}
|
|
||||||
item = { ...item, tmsg };
|
item = { ...item, tmsg };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,15 +130,24 @@ export class List extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
console.count(`${ this.constructor.name }.render calls`);
|
console.count(`${ this.constructor.name }.render calls`);
|
||||||
const { messages } = this.state;
|
const { messages, threads } = this.state;
|
||||||
|
const { tmid } = this.props;
|
||||||
|
let data = [];
|
||||||
|
if (tmid) {
|
||||||
|
const thread = { ...threads[0] };
|
||||||
|
thread.tlm = null;
|
||||||
|
data = [...messages, thread];
|
||||||
|
} else {
|
||||||
|
data = messages;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<EmptyRoom length={messages.length} />
|
<EmptyRoom length={data.length} />
|
||||||
<FlatList
|
<FlatList
|
||||||
testID='room-view-messages'
|
testID='room-view-messages'
|
||||||
ref={ref => this.list = ref}
|
ref={ref => this.list = ref}
|
||||||
keyExtractor={item => item._id}
|
keyExtractor={item => item._id}
|
||||||
data={messages}
|
data={data}
|
||||||
extraData={this.state}
|
extraData={this.state}
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
contentContainerStyle={styles.contentContainer}
|
contentContainerStyle={styles.contentContainer}
|
||||||
|
|
|
@ -46,6 +46,8 @@ import buildMessage from '../../lib/methods/helpers/buildMessage';
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
},
|
},
|
||||||
actionMessage: state.messages.actionMessage,
|
actionMessage: state.messages.actionMessage,
|
||||||
|
editing: state.messages.editing,
|
||||||
|
replying: state.messages.replying,
|
||||||
showActions: state.messages.showActions,
|
showActions: state.messages.showActions,
|
||||||
showErrorActions: state.messages.showErrorActions,
|
showErrorActions: state.messages.showErrorActions,
|
||||||
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background',
|
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background',
|
||||||
|
@ -87,6 +89,8 @@ export default class RoomView extends LoggedView {
|
||||||
appState: PropTypes.string,
|
appState: PropTypes.string,
|
||||||
useRealName: PropTypes.bool,
|
useRealName: PropTypes.bool,
|
||||||
isAuthenticated: PropTypes.bool,
|
isAuthenticated: PropTypes.bool,
|
||||||
|
editing: PropTypes.bool,
|
||||||
|
replying: PropTypes.bool,
|
||||||
toggleReactionPicker: PropTypes.func.isRequired,
|
toggleReactionPicker: PropTypes.func.isRequired,
|
||||||
actionsShow: PropTypes.func,
|
actionsShow: PropTypes.func,
|
||||||
editCancel: PropTypes.func,
|
editCancel: PropTypes.func,
|
||||||
|
@ -176,12 +180,18 @@ export default class RoomView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.messagebox && this.messagebox.current && this.messagebox.current.text) {
|
const { editing, replying } = this.props;
|
||||||
|
if (!editing && this.messagebox && this.messagebox.current && this.messagebox.current.text) {
|
||||||
const { text } = this.messagebox.current;
|
const { text } = this.messagebox.current;
|
||||||
const [room] = this.rooms;
|
let obj;
|
||||||
if (room) {
|
if (this.tmid) {
|
||||||
|
obj = database.objectForPrimaryKey('threads', this.tmid);
|
||||||
|
} else {
|
||||||
|
[obj] = this.rooms;
|
||||||
|
}
|
||||||
|
if (obj) {
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
room.draftMessage = text;
|
obj.draftMessage = text;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,9 +202,14 @@ export default class RoomView extends LoggedView {
|
||||||
if (this.beginAnimatingTimeout) {
|
if (this.beginAnimatingTimeout) {
|
||||||
clearTimeout(this.beginAnimatingTimeout);
|
clearTimeout(this.beginAnimatingTimeout);
|
||||||
}
|
}
|
||||||
const { editCancel, replyCancel } = this.props;
|
if (editing) {
|
||||||
|
const { editCancel } = this.props;
|
||||||
editCancel();
|
editCancel();
|
||||||
|
}
|
||||||
|
if (replying) {
|
||||||
|
const { replyCancel } = this.props;
|
||||||
replyCancel();
|
replyCancel();
|
||||||
|
}
|
||||||
if (this.didMountInteraction && this.didMountInteraction.cancel) {
|
if (this.didMountInteraction && this.didMountInteraction.cancel) {
|
||||||
this.didMountInteraction.cancel();
|
this.didMountInteraction.cancel();
|
||||||
}
|
}
|
||||||
|
@ -217,7 +232,7 @@ export default class RoomView extends LoggedView {
|
||||||
this.initInteraction = InteractionManager.runAfterInteractions(async() => {
|
this.initInteraction = InteractionManager.runAfterInteractions(async() => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
if (this.tmid) {
|
if (this.tmid) {
|
||||||
RocketChat.loadThreadMessages({ tmid: this.tmid, t: this.t });
|
await this.getThreadMessages();
|
||||||
} else {
|
} else {
|
||||||
await this.getMessages(room);
|
await this.getMessages(room);
|
||||||
|
|
||||||
|
@ -241,7 +256,7 @@ export default class RoomView extends LoggedView {
|
||||||
|
|
||||||
onMessageLongPress = (message) => {
|
onMessageLongPress = (message) => {
|
||||||
const { actionsShow } = this.props;
|
const { actionsShow } = this.props;
|
||||||
actionsShow(message);
|
actionsShow({ ...message, rid: this.rid });
|
||||||
}
|
}
|
||||||
|
|
||||||
onReactionPress = (shortname, messageId) => {
|
onReactionPress = (shortname, messageId) => {
|
||||||
|
@ -311,6 +326,15 @@ export default class RoomView extends LoggedView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getThreadMessages = () => {
|
||||||
|
try {
|
||||||
|
return RocketChat.loadThreadMessages({ tmid: this.tmid });
|
||||||
|
} catch (e) {
|
||||||
|
console.log('TCL: getThreadMessages -> e', e);
|
||||||
|
log('getThreadMessages', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setLastOpen = lastOpen => this.setState({ lastOpen });
|
setLastOpen = lastOpen => this.setState({ lastOpen });
|
||||||
|
|
||||||
joinRoom = async() => {
|
joinRoom = async() => {
|
||||||
|
@ -420,6 +444,7 @@ export default class RoomView extends LoggedView {
|
||||||
|
|
||||||
renderFooter = () => {
|
renderFooter = () => {
|
||||||
const { joined, room } = this.state;
|
const { joined, room } = this.state;
|
||||||
|
const { navigation } = this.props;
|
||||||
|
|
||||||
if (!joined && !this.tmid) {
|
if (!joined && !this.tmid) {
|
||||||
return (
|
return (
|
||||||
|
@ -450,7 +475,16 @@ export default class RoomView extends LoggedView {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <MessageBox ref={this.messagebox} onSubmit={this.sendMessage} rid={this.rid} roomType={room.t} />;
|
return (
|
||||||
|
<MessageBox
|
||||||
|
ref={this.messagebox}
|
||||||
|
onSubmit={this.sendMessage}
|
||||||
|
rid={this.rid}
|
||||||
|
tmid={this.tmid}
|
||||||
|
roomType={room.t}
|
||||||
|
isFocused={navigation.isFocused()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderActions = () => {
|
renderActions = () => {
|
||||||
|
|
|
@ -175,45 +175,6 @@ export default class RoomsListView extends LoggedView {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { showUnread, showFavorites, groupByType } = this.props;
|
|
||||||
if (showUnread) {
|
|
||||||
const { unread } = this.state;
|
|
||||||
if (!isEqual(nextState.unread, unread)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (showFavorites) {
|
|
||||||
const { favorites } = this.state;
|
|
||||||
if (!isEqual(nextState.favorites, favorites)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (groupByType) {
|
|
||||||
const {
|
|
||||||
dicussions, channels, privateGroup, direct, livechat
|
|
||||||
} = this.state;
|
|
||||||
if (!isEqual(nextState.dicussions, dicussions)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!isEqual(nextState.channels, channels)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!isEqual(nextState.privateGroup, privateGroup)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!isEqual(nextState.direct, direct)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!isEqual(nextState.livechat, livechat)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const { chats } = this.state;
|
|
||||||
if (!isEqual(nextState.chats, chats)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { search } = this.state;
|
const { search } = this.state;
|
||||||
if (!isEqual(nextState.search, search)) {
|
if (!isEqual(nextState.search, search)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -311,27 +272,20 @@ export default class RoomsListView extends LoggedView {
|
||||||
updateState = debounce(() => {
|
updateState = debounce(() => {
|
||||||
this.updateStateInteraction = InteractionManager.runAfterInteractions(() => {
|
this.updateStateInteraction = InteractionManager.runAfterInteractions(() => {
|
||||||
this.internalSetState({
|
this.internalSetState({
|
||||||
chats: this.getSnapshot(this.chats),
|
chats: this.chats,
|
||||||
unread: this.getSnapshot(this.unread),
|
unread: this.unread,
|
||||||
favorites: this.getSnapshot(this.favorites),
|
favorites: this.favorites,
|
||||||
discussions: this.getSnapshot(this.discussions),
|
discussions: this.discussions,
|
||||||
channels: this.getSnapshot(this.channels),
|
channels: this.channels,
|
||||||
privateGroup: this.getSnapshot(this.privateGroup),
|
privateGroup: this.privateGroup,
|
||||||
direct: this.getSnapshot(this.direct),
|
direct: this.direct,
|
||||||
livechat: this.getSnapshot(this.livechat),
|
livechat: this.livechat,
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
|
this.forceUpdate();
|
||||||
});
|
});
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
getSnapshot = (data) => {
|
|
||||||
if (data && data.length) {
|
|
||||||
const array = Array.from(data);
|
|
||||||
return JSON.parse(JSON.stringify(array));
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
initSearchingAndroid = () => {
|
initSearchingAndroid = () => {
|
||||||
const { openSearchHeader, navigation } = this.props;
|
const { openSearchHeader, navigation } = this.props;
|
||||||
this.setState({ searching: true });
|
this.setState({ searching: true });
|
||||||
|
@ -441,13 +395,14 @@ export default class RoomsListView extends LoggedView {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const id = item.rid.replace(userId, '').trim();
|
const id = item.rid.replace(userId, '').trim();
|
||||||
|
|
||||||
|
if (item.search || (item.isValid && item.isValid())) {
|
||||||
return (
|
return (
|
||||||
<RoomItem
|
<RoomItem
|
||||||
alert={item.alert}
|
alert={item.alert}
|
||||||
unread={item.unread}
|
unread={item.unread}
|
||||||
userMentions={item.userMentions}
|
userMentions={item.userMentions}
|
||||||
favorite={item.f}
|
favorite={item.f}
|
||||||
lastMessage={item.lastMessage}
|
lastMessage={JSON.parse(JSON.stringify(item.lastMessage))}
|
||||||
name={this.getRoomTitle(item)}
|
name={this.getRoomTitle(item)}
|
||||||
_updatedAt={item.roomUpdatedAt}
|
_updatedAt={item.roomUpdatedAt}
|
||||||
key={item._id}
|
key={item._id}
|
||||||
|
@ -462,6 +417,8 @@ export default class RoomsListView extends LoggedView {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
renderSectionHeader = header => (
|
renderSectionHeader = header => (
|
||||||
<View style={styles.groupTitleContainer}>
|
<View style={styles.groupTitleContainer}>
|
||||||
|
@ -481,11 +438,10 @@ export default class RoomsListView extends LoggedView {
|
||||||
} else if (header === 'Chats' && groupByType) {
|
} else if (header === 'Chats' && groupByType) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (data.length > 0) {
|
if (data && data.length > 0) {
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={data}
|
data={data}
|
||||||
extraData={data}
|
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
style={styles.list}
|
style={styles.list}
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
|
@ -553,7 +509,6 @@ export default class RoomsListView extends LoggedView {
|
||||||
<FlatList
|
<FlatList
|
||||||
ref={this.getScrollRef}
|
ref={this.getScrollRef}
|
||||||
data={search.length ? search : chats}
|
data={search.length ? search : chats}
|
||||||
extraData={search.length ? search : chats}
|
|
||||||
contentOffset={isIOS ? { x: 0, y: SCROLL_OFFSET } : {}}
|
contentOffset={isIOS ? { x: 0, y: SCROLL_OFFSET } : {}}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
style={styles.list}
|
style={styles.list}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import {
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
import equal from 'deep-equal';
|
|
||||||
import EJSON from 'ejson';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import LoggedView from '../View';
|
import LoggedView from '../View';
|
||||||
|
@ -22,6 +20,7 @@ import log from '../../utils/log';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
|
|
||||||
const Separator = React.memo(() => <View style={styles.separator} />);
|
const Separator = React.memo(() => <View style={styles.separator} />);
|
||||||
|
const API_FETCH_COUNT = 50;
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
|
@ -47,72 +46,136 @@ export default class ThreadMessagesView extends LoggedView {
|
||||||
super('ThreadMessagesView', props);
|
super('ThreadMessagesView', props);
|
||||||
this.rid = props.navigation.getParam('rid');
|
this.rid = props.navigation.getParam('rid');
|
||||||
this.t = props.navigation.getParam('t');
|
this.t = props.navigation.getParam('t');
|
||||||
this.messages = database.objects('threads').filtered('rid = $0', this.rid);
|
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
||||||
|
this.messages = database.objects('threads').filtered('rid = $0', this.rid).sorted('ts', true);
|
||||||
safeAddListener(this.messages, this.updateMessages);
|
safeAddListener(this.messages, this.updateMessages);
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: false,
|
loading: false,
|
||||||
messages: this.messages.slice(),
|
|
||||||
end: false,
|
end: false,
|
||||||
total: 0
|
messages: this.messages
|
||||||
};
|
};
|
||||||
|
this.mounted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.mountInteraction = InteractionManager.runAfterInteractions(() => {
|
||||||
|
this.init();
|
||||||
|
this.mounted = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.messages.removeAllListeners();
|
||||||
|
if (this.mountInteraction && this.mountInteraction.cancel) {
|
||||||
|
this.mountInteraction.cancel();
|
||||||
|
}
|
||||||
|
if (this.loadInteraction && this.loadInteraction.cancel) {
|
||||||
|
this.loadInteraction.cancel();
|
||||||
|
}
|
||||||
|
if (this.syncInteraction && this.syncInteraction.cancel) {
|
||||||
|
this.syncInteraction.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/sort-comp
|
||||||
|
updateMessages = debounce(() => {
|
||||||
|
this.setState({ messages: this.messages });
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
init = () => {
|
||||||
|
const [room] = this.rooms;
|
||||||
|
const lastThreadSync = new Date();
|
||||||
|
if (room.lastThreadSync) {
|
||||||
|
this.sync(room.lastThreadSync);
|
||||||
|
} else {
|
||||||
this.load();
|
this.load();
|
||||||
}
|
}
|
||||||
|
database.write(() => {
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
room.lastThreadSync = lastThreadSync;
|
||||||
const { loading, messages, end } = this.state;
|
});
|
||||||
if (nextState.loading !== loading) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextState.messages, messages)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextState.end, end)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMessages = () => {
|
|
||||||
this.setState({ messages: this.messages.slice() });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
load = debounce(async() => {
|
load = debounce(async() => {
|
||||||
const {
|
const { loading, end } = this.state;
|
||||||
loading, end, total
|
if (end || loading || !this.mounted) {
|
||||||
} = this.state;
|
|
||||||
if (end || loading) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await RocketChat.getThreadsList({ rid: this.rid, limit: 50, skip: total });
|
const result = await RocketChat.getThreadsList({
|
||||||
|
rid: this.rid, count: API_FETCH_COUNT, offset: this.messages.length
|
||||||
database.write(() => result.forEach((message) => {
|
});
|
||||||
|
if (result.success) {
|
||||||
|
this.loadInteraction = InteractionManager.runAfterInteractions(() => {
|
||||||
|
database.write(() => result.threads.forEach((message) => {
|
||||||
try {
|
try {
|
||||||
database.create('threads', buildMessage(EJSON.fromJSONValue(message)), true);
|
database.create('threads', buildMessage(message), true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('ThreadMessagesView -> load -> create', e);
|
log('ThreadMessagesView -> load -> create', e);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
InteractionManager.runAfterInteractions(() => {
|
this.setState({
|
||||||
this.setState(prevState => ({
|
|
||||||
loading: false,
|
loading: false,
|
||||||
end: result.length < 50,
|
end: result.count < API_FETCH_COUNT
|
||||||
total: prevState.total + result.length
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('ThreadMessagesView -> catch -> error', error);
|
console.log('ThreadMessagesView -> load -> error', error);
|
||||||
this.setState({ loading: false, end: true });
|
this.setState({ loading: false, end: true });
|
||||||
}
|
}
|
||||||
}, 300, true)
|
}, 300)
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/sort-comp
|
||||||
|
sync = async(updatedSince) => {
|
||||||
|
this.setState({ loading: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await RocketChat.getSyncThreadsList({
|
||||||
|
rid: this.rid, updatedSince: updatedSince.toISOString()
|
||||||
|
});
|
||||||
|
if (result.success && result.threads) {
|
||||||
|
this.syncInteraction = InteractionManager.runAfterInteractions(() => {
|
||||||
|
const { update, remove } = result.threads;
|
||||||
|
database.write(() => {
|
||||||
|
if (update && update.length) {
|
||||||
|
update.forEach((message) => {
|
||||||
|
try {
|
||||||
|
database.create('threads', buildMessage(message), true);
|
||||||
|
} catch (e) {
|
||||||
|
log('ThreadMessagesView -> sync -> update', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remove && remove.length) {
|
||||||
|
remove.forEach((message) => {
|
||||||
|
const oldMessage = database.objectForPrimaryKey('threads', message._id);
|
||||||
|
if (oldMessage) {
|
||||||
|
try {
|
||||||
|
database.delete(oldMessage);
|
||||||
|
} catch (e) {
|
||||||
|
log('ThreadMessagesView -> sync -> delete', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('ThreadMessagesView -> sync -> error', error);
|
||||||
|
this.setState({ loading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
formatMessage = lm => (
|
formatMessage = lm => (
|
||||||
lm ? moment(lm).calendar(null, {
|
lm ? moment(lm).calendar(null, {
|
||||||
|
@ -133,6 +196,7 @@ export default class ThreadMessagesView extends LoggedView {
|
||||||
|
|
||||||
renderItem = ({ item }) => {
|
renderItem = ({ item }) => {
|
||||||
const { user, navigation } = this.props;
|
const { user, navigation } = this.props;
|
||||||
|
if (item.isValid && item.isValid()) {
|
||||||
return (
|
return (
|
||||||
<Message
|
<Message
|
||||||
key={item._id}
|
key={item._id}
|
||||||
|
@ -150,11 +214,13 @@ export default class ThreadMessagesView extends LoggedView {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { messages, loading } = this.state;
|
const { loading, messages } = this.state;
|
||||||
|
|
||||||
if (!loading && messages.length === 0) {
|
if (!loading && this.messages.length === 0) {
|
||||||
return this.renderEmpty();
|
return this.renderEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +229,7 @@ export default class ThreadMessagesView extends LoggedView {
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
<FlatList
|
<FlatList
|
||||||
data={messages}
|
data={messages}
|
||||||
|
extraData={this.state}
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
style={styles.list}
|
style={styles.list}
|
||||||
contentContainerStyle={styles.contentContainer}
|
contentContainerStyle={styles.contentContainer}
|
||||||
|
|
|
@ -284,10 +284,10 @@ describe('Room screen', () => {
|
||||||
await element(by.text('Reply')).tap();
|
await element(by.text('Reply')).tap();
|
||||||
await element(by.id('messagebox-input')).typeText('replied');
|
await element(by.id('messagebox-input')).typeText('replied');
|
||||||
await element(by.id('messagebox-send-message')).tap();
|
await element(by.id('messagebox-send-message')).tap();
|
||||||
await waitFor(element(by.id(`message-thread-button-${ thread }`))).toBeVisible().withTimeout(5000);
|
await waitFor(element(by.id(`message-thread-button-${ thread }`))).toExist().withTimeout(5000);
|
||||||
await expect(element(by.id(`message-thread-button-${ thread }`))).toBeVisible();
|
await expect(element(by.id(`message-thread-button-${ thread }`))).toExist();
|
||||||
await waitFor(element(by.id(`message-thread-replied-on-${ thread }`))).toBeVisible().withTimeout(5000);
|
await waitFor(element(by.id(`message-thread-replied-on-${ thread }`))).toExist().withTimeout(5000);
|
||||||
await expect(element(by.id(`message-thread-replied-on-${ thread }`))).toBeVisible();
|
await expect(element(by.id(`message-thread-replied-on-${ thread }`))).toExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should navigate to thread from button', async() => {
|
it('should navigate to thread from button', async() => {
|
||||||
|
|
|
@ -263,7 +263,6 @@ export default (
|
||||||
header={false}
|
header={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Legacy thread */}
|
|
||||||
<Separator title='Message with reply' />
|
<Separator title='Message with reply' />
|
||||||
<Message
|
<Message
|
||||||
msg="I'm fine!"
|
msg="I'm fine!"
|
||||||
|
@ -290,6 +289,11 @@ export default (
|
||||||
tcount={1}
|
tcount={1}
|
||||||
tlm={date}
|
tlm={date}
|
||||||
/>
|
/>
|
||||||
|
<Message
|
||||||
|
msg='How are you?'
|
||||||
|
tcount={9999}
|
||||||
|
tlm={date}
|
||||||
|
/>
|
||||||
<Message
|
<Message
|
||||||
msg="I'm fine!"
|
msg="I'm fine!"
|
||||||
tmid='1'
|
tmid='1'
|
||||||
|
@ -316,15 +320,15 @@ export default (
|
||||||
tmsg={longText}
|
tmsg={longText}
|
||||||
/>
|
/>
|
||||||
<Message
|
<Message
|
||||||
msg='How are you?'
|
tmid='1'
|
||||||
tcount={0}
|
tmsg='Thread with attachment'
|
||||||
tlm={date}
|
attachments={[{
|
||||||
/>
|
title: 'This is a title',
|
||||||
<Message
|
description: 'This is a description',
|
||||||
msg='How are you?'
|
audio_url: '/file-upload/c4wcNhrbXJLBvAJtN/1535569819516.aac'
|
||||||
tcount={9999}
|
}]}
|
||||||
tlm={date}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <Message
|
{/* <Message
|
||||||
msg='How are you?'
|
msg='How are you?'
|
||||||
tcount={9999}
|
tcount={9999}
|
||||||
|
|
Loading…
Reference in New Issue