[NEW] Discussions (#696)
This commit is contained in:
parent
7a80550d61
commit
1d9acdb700
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -26,17 +26,7 @@ export const ROOMS = createRequestTypes('ROOMS', [
|
||||||
'OPEN_SEARCH_HEADER',
|
'OPEN_SEARCH_HEADER',
|
||||||
'CLOSE_SEARCH_HEADER'
|
'CLOSE_SEARCH_HEADER'
|
||||||
]);
|
]);
|
||||||
export const ROOM = createRequestTypes('ROOM', [
|
export const ROOM = createRequestTypes('ROOM', ['LEAVE', 'ERASE', 'USER_TYPING']);
|
||||||
'ADD_USER_TYPING',
|
|
||||||
'REMOVE_USER_TYPING',
|
|
||||||
'SOMEONE_TYPING',
|
|
||||||
'OPEN',
|
|
||||||
'CLOSE',
|
|
||||||
'LEAVE',
|
|
||||||
'ERASE',
|
|
||||||
'USER_TYPING',
|
|
||||||
'MESSAGE_RECEIVED'
|
|
||||||
]);
|
|
||||||
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT']);
|
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT']);
|
||||||
export const MESSAGES = createRequestTypes('MESSAGES', [
|
export const MESSAGES = createRequestTypes('MESSAGES', [
|
||||||
...defaultTypes,
|
...defaultTypes,
|
||||||
|
|
|
@ -1,40 +1,5 @@
|
||||||
import * as types from './actionsTypes';
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
|
|
||||||
export function removeUserTyping(username) {
|
|
||||||
return {
|
|
||||||
type: types.ROOM.REMOVE_USER_TYPING,
|
|
||||||
username
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function someoneTyping(data) {
|
|
||||||
return {
|
|
||||||
type: types.ROOM.SOMEONE_TYPING,
|
|
||||||
...data
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addUserTyping(username) {
|
|
||||||
return {
|
|
||||||
type: types.ROOM.ADD_USER_TYPING,
|
|
||||||
username
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function openRoom(room) {
|
|
||||||
return {
|
|
||||||
type: types.ROOM.OPEN,
|
|
||||||
room
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeRoom() {
|
|
||||||
return {
|
|
||||||
type: types.ROOM.CLOSE
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function leaveRoom(rid, t) {
|
export function leaveRoom(rid, t) {
|
||||||
return {
|
return {
|
||||||
type: types.ROOM.LEAVE,
|
type: types.ROOM.LEAVE,
|
||||||
|
@ -51,16 +16,10 @@ export function eraseRoom(rid, t) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function userTyping(status = true) {
|
export function userTyping(rid, status = true) {
|
||||||
return {
|
return {
|
||||||
type: types.ROOM.USER_TYPING,
|
type: types.ROOM.USER_TYPING,
|
||||||
|
rid,
|
||||||
status
|
status
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function roomMessageReceived(message) {
|
|
||||||
return {
|
|
||||||
type: types.ROOM.MESSAGE_RECEIVED,
|
|
||||||
message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import I18n from '../i18n';
|
||||||
import debounce from '../utils/debounce';
|
import debounce from '../utils/debounce';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import {
|
import {
|
||||||
COLOR_BACKGROUND_CONTAINER, COLOR_DANGER, COLOR_SUCCESS, COLOR_WHITE
|
COLOR_BACKGROUND_CONTAINER, COLOR_DANGER, COLOR_SUCCESS, COLOR_WHITE, COLOR_TEXT_DESCRIPTION
|
||||||
} from '../constants/colors';
|
} from '../constants/colors';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -117,7 +117,7 @@ class ConnectionBadge extends Component {
|
||||||
if (connecting) {
|
if (connecting) {
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[styles.container, { transform: [{ translateY }] }]}>
|
<Animated.View style={[styles.container, { transform: [{ translateY }] }]}>
|
||||||
<ActivityIndicator color='#9EA2A8' style={styles.activityIndicator} />
|
<ActivityIndicator color={COLOR_TEXT_DESCRIPTION} style={styles.activityIndicator} />
|
||||||
<Text style={[styles.text, styles.textConnecting]}>{I18n.t('Connecting')}</Text>
|
<Text style={[styles.text, styles.textConnecting]}>{I18n.t('Connecting')}</Text>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -48,25 +48,7 @@ const imagePickerConfig = {
|
||||||
cropperCancelText: I18n.t('Cancel')
|
cropperCancelText: I18n.t('Cancel')
|
||||||
};
|
};
|
||||||
|
|
||||||
@connect(state => ({
|
class MessageBox extends Component {
|
||||||
roomType: state.room.t,
|
|
||||||
message: state.messages.message,
|
|
||||||
replyMessage: state.messages.replyMessage,
|
|
||||||
replying: state.messages.replyMessage && !!state.messages.replyMessage.msg,
|
|
||||||
editing: state.messages.editing,
|
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
|
||||||
user: {
|
|
||||||
id: state.login.user && state.login.user.id,
|
|
||||||
username: state.login.user && state.login.user.username,
|
|
||||||
token: state.login.user && state.login.user.token
|
|
||||||
}
|
|
||||||
}), dispatch => ({
|
|
||||||
editCancel: () => dispatch(editCancelAction()),
|
|
||||||
editRequest: message => dispatch(editRequestAction(message)),
|
|
||||||
typing: status => dispatch(userTypingAction(status)),
|
|
||||||
closeReply: () => dispatch(replyCancelAction())
|
|
||||||
}))
|
|
||||||
export default class MessageBox extends Component {
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
rid: PropTypes.string.isRequired,
|
rid: PropTypes.string.isRequired,
|
||||||
baseUrl: PropTypes.string.isRequired,
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
@ -113,6 +95,7 @@ export default class MessageBox extends Component {
|
||||||
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||||
if (room.draftMessage && room.draftMessage !== '') {
|
if (room.draftMessage && room.draftMessage !== '') {
|
||||||
this.setInput(room.draftMessage);
|
this.setInput(room.draftMessage);
|
||||||
|
this.setShowSend(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +103,9 @@ export default class MessageBox extends Component {
|
||||||
const { message, replyMessage } = this.props;
|
const { message, replyMessage } = this.props;
|
||||||
if (message !== nextProps.message && nextProps.message.msg) {
|
if (message !== nextProps.message && nextProps.message.msg) {
|
||||||
this.setInput(nextProps.message.msg);
|
this.setInput(nextProps.message.msg);
|
||||||
|
if (this.text) {
|
||||||
|
this.setShowSend(true);
|
||||||
|
}
|
||||||
this.focus();
|
this.focus();
|
||||||
} else if (replyMessage !== nextProps.replyMessage && nextProps.replyMessage.msg) {
|
} else if (replyMessage !== nextProps.replyMessage && nextProps.replyMessage.msg) {
|
||||||
this.focus();
|
this.focus();
|
||||||
|
@ -165,14 +151,6 @@ export default class MessageBox extends Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
const { rid } = this.props;
|
|
||||||
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
|
||||||
database.write(() => {
|
|
||||||
room.draftMessage = this.text;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangeText = (text) => {
|
onChangeText = (text) => {
|
||||||
const isTextEmpty = text.length === 0;
|
const isTextEmpty = text.length === 0;
|
||||||
this.setShowSend(!isTextEmpty);
|
this.setShowSend(!isTextEmpty);
|
||||||
|
@ -461,13 +439,13 @@ export default class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTyping = (isTyping) => {
|
handleTyping = (isTyping) => {
|
||||||
const { typing } = this.props;
|
const { typing, rid } = this.props;
|
||||||
if (!isTyping) {
|
if (!isTyping) {
|
||||||
if (this.typingTimeout) {
|
if (this.typingTimeout) {
|
||||||
clearTimeout(this.typingTimeout);
|
clearTimeout(this.typingTimeout);
|
||||||
this.typingTimeout = false;
|
this.typingTimeout = false;
|
||||||
}
|
}
|
||||||
typing(false);
|
typing(rid, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,7 +454,7 @@ export default class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.typingTimeout = setTimeout(() => {
|
this.typingTimeout = setTimeout(() => {
|
||||||
typing(true);
|
typing(rid, true);
|
||||||
this.typingTimeout = false;
|
this.typingTimeout = false;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
@ -835,3 +813,25 @@ export default class MessageBox extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
message: state.messages.message,
|
||||||
|
replyMessage: state.messages.replyMessage,
|
||||||
|
replying: state.messages.replyMessage && !!state.messages.replyMessage.msg,
|
||||||
|
editing: state.messages.editing,
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
|
user: {
|
||||||
|
id: state.login.user && state.login.user.id,
|
||||||
|
username: state.login.user && state.login.user.username,
|
||||||
|
token: state.login.user && state.login.user.token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const dispatchToProps = ({
|
||||||
|
editCancel: () => editCancelAction(),
|
||||||
|
editRequest: message => editRequestAction(message),
|
||||||
|
typing: (rid, status) => userTypingAction(rid, status),
|
||||||
|
closeReply: () => replyCancelAction()
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(MessageBox);
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Image, StyleSheet } from 'react-native';
|
import { Image, StyleSheet } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { CustomIcon } from '../lib/Icons';
|
||||||
|
import { COLOR_TEXT_DESCRIPTION } from '../constants/colors';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
style: {
|
style: {
|
||||||
marginRight: 7,
|
marginRight: 7,
|
||||||
marginTop: 3,
|
marginTop: 3,
|
||||||
tintColor: '#9EA2A8'
|
tintColor: COLOR_TEXT_DESCRIPTION,
|
||||||
|
color: COLOR_TEXT_DESCRIPTION
|
||||||
|
},
|
||||||
|
discussion: {
|
||||||
|
marginRight: 6
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,6 +21,11 @@ const RoomTypeIcon = React.memo(({ type, size, style }) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === 'discussion') {
|
||||||
|
// FIXME: These are temporary only. We should have all room icons on <Customicon />, but our design team is still working on this.
|
||||||
|
return <CustomIcon name='chat' size={13} style={[styles.style, styles.discussion]} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (type === 'c') {
|
if (type === 'c') {
|
||||||
return <Image source={{ uri: 'hashtag' }} style={[styles.style, style, { width: size, height: size }]} />;
|
return <Image source={{ uri: 'hashtag' }} style={[styles.style, style, { width: size, height: size }]} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import PhotoModal from './PhotoModal';
|
import PhotoModal from './PhotoModal';
|
||||||
import Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { COLOR_WHITE } from '../../constants/colors';
|
|
||||||
|
|
||||||
export default class extends Component {
|
export default class extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -37,7 +36,7 @@ export default class extends Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressButton() {
|
onPressButton = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
modalVisible: true
|
modalVisible: true
|
||||||
});
|
});
|
||||||
|
@ -67,20 +66,21 @@ export default class extends Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
[
|
[
|
||||||
<RectButton
|
<Touchable
|
||||||
key='image'
|
key='image'
|
||||||
onPress={() => this.onPressButton()}
|
onPress={this.onPressButton}
|
||||||
onActiveStateChange={this.isPressed}
|
|
||||||
style={styles.imageContainer}
|
style={styles.imageContainer}
|
||||||
underlayColor={COLOR_WHITE}
|
background={Touchable.Ripple('#fff')}
|
||||||
>
|
>
|
||||||
<FastImage
|
<React.Fragment>
|
||||||
style={[styles.image, isPressed && { opacity: 0.5 }]}
|
<FastImage
|
||||||
source={{ uri: encodeURI(img) }}
|
style={[styles.image, isPressed && { opacity: 0.5 }]}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
source={{ uri: encodeURI(img) }}
|
||||||
/>
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
{this.getDescription()}
|
/>
|
||||||
</RectButton>,
|
{this.getDescription()}
|
||||||
|
</React.Fragment>
|
||||||
|
</Touchable>,
|
||||||
<PhotoModal
|
<PhotoModal
|
||||||
key='modal'
|
key='modal'
|
||||||
title={file.title}
|
title={file.title}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, Image, Platform } from 'react-native';
|
import { Text, Image } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { emojify } from 'react-emojione';
|
import { emojify } from 'react-emojione';
|
||||||
import MarkdownRenderer, { PluginContainer } from 'react-native-markdown-renderer';
|
import MarkdownRenderer, { PluginContainer } from 'react-native-markdown-renderer';
|
||||||
|
@ -8,20 +8,12 @@ import styles from './styles';
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
import MarkdownEmojiPlugin from './MarkdownEmojiPlugin';
|
import MarkdownEmojiPlugin from './MarkdownEmojiPlugin';
|
||||||
|
|
||||||
import sharedStyles from '../../views/Styles';
|
|
||||||
import { COLOR_BACKGROUND_CONTAINER, COLOR_BORDER, COLOR_PRIMARY } from '../../constants/colors';
|
|
||||||
|
|
||||||
// Support <http://link|Text>
|
// Support <http://link|Text>
|
||||||
const formatText = text => text.replace(
|
const formatText = text => text.replace(
|
||||||
new RegExp('(?:<|<)((?:https|http):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)', 'gm'),
|
new RegExp('(?:<|<)((?:https|http):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)', 'gm'),
|
||||||
(match, url, title) => `[${ title }](${ url })`
|
(match, url, title) => `[${ title }](${ url })`
|
||||||
);
|
);
|
||||||
|
|
||||||
const codeFontFamily = Platform.select({
|
|
||||||
ios: { fontFamily: 'Courier New' },
|
|
||||||
android: { fontFamily: 'monospace' }
|
|
||||||
});
|
|
||||||
|
|
||||||
export default class Markdown extends React.Component {
|
export default class Markdown extends React.Component {
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
const { msg } = this.props;
|
const { msg } = this.props;
|
||||||
|
@ -94,31 +86,10 @@ export default class Markdown extends React.Component {
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
paragraph: styles.paragraph,
|
paragraph: styles.paragraph,
|
||||||
text: {
|
text: styles.text,
|
||||||
fontSize: 16,
|
codeInline: styles.codeInline,
|
||||||
...sharedStyles.textColorNormal,
|
codeBlock: styles.codeBlock,
|
||||||
...sharedStyles.textRegular
|
link: styles.link,
|
||||||
},
|
|
||||||
codeInline: {
|
|
||||||
...sharedStyles.textRegular,
|
|
||||||
...codeFontFamily,
|
|
||||||
borderWidth: 1,
|
|
||||||
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
|
||||||
borderRadius: 4
|
|
||||||
},
|
|
||||||
codeBlock: {
|
|
||||||
...sharedStyles.textRegular,
|
|
||||||
...codeFontFamily,
|
|
||||||
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
|
||||||
borderColor: COLOR_BORDER,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 4,
|
|
||||||
padding: 4
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
color: COLOR_PRIMARY,
|
|
||||||
...sharedStyles.textRegular
|
|
||||||
},
|
|
||||||
...style
|
...style
|
||||||
}}
|
}}
|
||||||
plugins={[
|
plugins={[
|
||||||
|
|
|
@ -6,8 +6,9 @@ import {
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { KeyboardUtils } from 'react-native-keyboard-input';
|
import { KeyboardUtils } from 'react-native-keyboard-input';
|
||||||
import {
|
import {
|
||||||
State, RectButton, LongPressGestureHandler, BorderlessButton
|
BorderlessButton
|
||||||
} from 'react-native-gesture-handler';
|
} from 'react-native-gesture-handler';
|
||||||
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import Image from './Image';
|
import Image from './Image';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
|
@ -23,7 +24,7 @@ import styles from './styles';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import messagesStatus from '../../constants/messagesStatus';
|
import messagesStatus from '../../constants/messagesStatus';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import { COLOR_DANGER, COLOR_TEXT_DESCRIPTION, COLOR_WHITE } from '../../constants/colors';
|
import { COLOR_DANGER } from '../../constants/colors';
|
||||||
|
|
||||||
const SYSTEM_MESSAGES = [
|
const SYSTEM_MESSAGES = [
|
||||||
'r',
|
'r',
|
||||||
|
@ -31,6 +32,7 @@ const SYSTEM_MESSAGES = [
|
||||||
'ru',
|
'ru',
|
||||||
'ul',
|
'ul',
|
||||||
'uj',
|
'uj',
|
||||||
|
'ut',
|
||||||
'rm',
|
'rm',
|
||||||
'user-muted',
|
'user-muted',
|
||||||
'user-unmuted',
|
'user-unmuted',
|
||||||
|
@ -41,7 +43,8 @@ const SYSTEM_MESSAGES = [
|
||||||
'room_changed_announcement',
|
'room_changed_announcement',
|
||||||
'room_changed_topic',
|
'room_changed_topic',
|
||||||
'room_changed_privacy',
|
'room_changed_privacy',
|
||||||
'message_snippeted'
|
'message_snippeted',
|
||||||
|
'thread-created'
|
||||||
];
|
];
|
||||||
|
|
||||||
const getInfoMessage = ({
|
const getInfoMessage = ({
|
||||||
|
@ -52,6 +55,8 @@ const getInfoMessage = ({
|
||||||
return I18n.t('Message_removed');
|
return I18n.t('Message_removed');
|
||||||
} else if (type === 'uj') {
|
} else if (type === 'uj') {
|
||||||
return I18n.t('Has_joined_the_channel');
|
return I18n.t('Has_joined_the_channel');
|
||||||
|
} else if (type === 'ut') {
|
||||||
|
return I18n.t('Has_joined_the_conversation');
|
||||||
} else if (type === 'r') {
|
} else if (type === 'r') {
|
||||||
return I18n.t('Room_name_changed', { name: msg, userBy: username });
|
return I18n.t('Room_name_changed', { name: msg, userBy: username });
|
||||||
} else if (type === 'message_pinned') {
|
} else if (type === 'message_pinned') {
|
||||||
|
@ -80,9 +85,14 @@ const getInfoMessage = ({
|
||||||
return I18n.t('Room_changed_privacy', { type: msg, userBy: username });
|
return I18n.t('Room_changed_privacy', { type: msg, userBy: username });
|
||||||
} else if (type === 'message_snippeted') {
|
} else if (type === 'message_snippeted') {
|
||||||
return I18n.t('Created_snippet');
|
return I18n.t('Created_snippet');
|
||||||
|
} else if (type === 'thread-created') {
|
||||||
|
return I18n.t('Thread_created', { name: msg });
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
const BUTTON_HIT_SLOP = {
|
||||||
|
top: 4, right: 4, bottom: 4, left: 4
|
||||||
|
};
|
||||||
|
|
||||||
export default class Message extends PureComponent {
|
export default class Message extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -125,12 +135,15 @@ export default class Message extends PureComponent {
|
||||||
PropTypes.object
|
PropTypes.object
|
||||||
]),
|
]),
|
||||||
useRealName: PropTypes.bool,
|
useRealName: PropTypes.bool,
|
||||||
|
dcount: PropTypes.number,
|
||||||
|
dlm: PropTypes.instanceOf(Date),
|
||||||
// methods
|
// methods
|
||||||
closeReactions: PropTypes.func,
|
closeReactions: PropTypes.func,
|
||||||
onErrorPress: PropTypes.func,
|
onErrorPress: PropTypes.func,
|
||||||
onLongPress: PropTypes.func,
|
onLongPress: PropTypes.func,
|
||||||
onReactionLongPress: PropTypes.func,
|
onReactionLongPress: PropTypes.func,
|
||||||
onReactionPress: PropTypes.func,
|
onReactionPress: PropTypes.func,
|
||||||
|
onDiscussionPress: PropTypes.func,
|
||||||
replyBroadcast: PropTypes.func,
|
replyBroadcast: PropTypes.func,
|
||||||
toggleReactionPicker: PropTypes.func
|
toggleReactionPicker: PropTypes.func
|
||||||
}
|
}
|
||||||
|
@ -282,31 +295,27 @@ export default class Message extends PureComponent {
|
||||||
user, onReactionLongPress, onReactionPress, customEmojis, baseUrl
|
user, onReactionLongPress, onReactionPress, customEmojis, baseUrl
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const reacted = reaction.usernames.findIndex(item => item.value === user.username) !== -1;
|
const reacted = reaction.usernames.findIndex(item => item.value === user.username) !== -1;
|
||||||
const underlayColor = reacted ? COLOR_WHITE : COLOR_TEXT_DESCRIPTION;
|
|
||||||
return (
|
return (
|
||||||
<LongPressGestureHandler
|
<Touchable
|
||||||
|
onPress={() => onReactionPress(reaction.emoji)}
|
||||||
|
onLongPress={onReactionLongPress}
|
||||||
key={reaction.emoji}
|
key={reaction.emoji}
|
||||||
onHandlerStateChange={({ nativeEvent }) => nativeEvent.state === State.ACTIVE && onReactionLongPress()}
|
testID={`message-reaction-${ reaction.emoji }`}
|
||||||
|
style={[styles.reactionButton, reacted && styles.reactionButtonReacted]}
|
||||||
|
background={Touchable.Ripple('#fff')}
|
||||||
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
>
|
>
|
||||||
<RectButton
|
<View style={[styles.reactionContainer, reacted && styles.reactedContainer]}>
|
||||||
onPress={() => onReactionPress(reaction.emoji)}
|
<Emoji
|
||||||
testID={`message-reaction-${ reaction.emoji }`}
|
content={reaction.emoji}
|
||||||
style={[styles.reactionButton, reacted && { backgroundColor: '#e8f2ff' }]}
|
customEmojis={customEmojis}
|
||||||
activeOpacity={0.8}
|
standardEmojiStyle={styles.reactionEmoji}
|
||||||
underlayColor={underlayColor}
|
customEmojiStyle={styles.reactionCustomEmoji}
|
||||||
>
|
baseUrl={baseUrl}
|
||||||
<View style={[styles.reactionContainer, reacted && styles.reactedContainer]}>
|
/>
|
||||||
<Emoji
|
<Text style={styles.reactionCount}>{ reaction.usernames.length }</Text>
|
||||||
content={reaction.emoji}
|
</View>
|
||||||
customEmojis={customEmojis}
|
</Touchable>
|
||||||
standardEmojiStyle={styles.reactionEmoji}
|
|
||||||
customEmojiStyle={styles.reactionCustomEmoji}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
/>
|
|
||||||
<Text style={styles.reactionCount}>{ reaction.usernames.length }</Text>
|
|
||||||
</View>
|
|
||||||
</RectButton>
|
|
||||||
</LongPressGestureHandler>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,18 +327,18 @@ export default class Message extends PureComponent {
|
||||||
return (
|
return (
|
||||||
<View style={styles.reactionsContainer}>
|
<View style={styles.reactionsContainer}>
|
||||||
{reactions.map(this.renderReaction)}
|
{reactions.map(this.renderReaction)}
|
||||||
<RectButton
|
<Touchable
|
||||||
onPress={toggleReactionPicker}
|
onPress={toggleReactionPicker}
|
||||||
key='message-add-reaction'
|
key='message-add-reaction'
|
||||||
testID='message-add-reaction'
|
testID='message-add-reaction'
|
||||||
style={styles.reactionButton}
|
style={styles.reactionButton}
|
||||||
activeOpacity={0.8}
|
background={Touchable.Ripple('#fff')}
|
||||||
underlayColor='#e1e5e8'
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
>
|
>
|
||||||
<View style={styles.reactionContainer}>
|
<View style={styles.reactionContainer}>
|
||||||
<CustomIcon name='add-reaction' size={21} style={styles.addReaction} />
|
<CustomIcon name='add-reaction' size={21} style={styles.addReaction} />
|
||||||
</View>
|
</View>
|
||||||
</RectButton>
|
</Touchable>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -338,20 +347,86 @@ export default class Message extends PureComponent {
|
||||||
const { broadcast, replyBroadcast } = this.props;
|
const { broadcast, replyBroadcast } = this.props;
|
||||||
if (broadcast && !this.isOwn()) {
|
if (broadcast && !this.isOwn()) {
|
||||||
return (
|
return (
|
||||||
<RectButton
|
<View style={styles.buttonContainer}>
|
||||||
onPress={replyBroadcast}
|
<Touchable
|
||||||
style={styles.broadcastButton}
|
onPress={replyBroadcast}
|
||||||
activeOpacity={0.5}
|
background={Touchable.Ripple('#fff')}
|
||||||
underlayColor={COLOR_WHITE}
|
style={styles.button}
|
||||||
>
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
<CustomIcon name='back' size={20} style={styles.broadcastButtonIcon} />
|
>
|
||||||
<Text style={styles.broadcastButtonText}>{I18n.t('Reply')}</Text>
|
<React.Fragment>
|
||||||
</RectButton>
|
<CustomIcon name='back' size={20} style={styles.buttonIcon} />
|
||||||
|
<Text style={styles.buttonText}>{I18n.t('Reply')}</Text>
|
||||||
|
</React.Fragment>
|
||||||
|
</Touchable>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderDiscussion = () => {
|
||||||
|
const {
|
||||||
|
msg, dcount, dlm, onDiscussionPress
|
||||||
|
} = this.props;
|
||||||
|
const time = dlm ? moment(dlm).calendar(null, {
|
||||||
|
lastDay: `[${ I18n.t('Yesterday') }]`,
|
||||||
|
sameDay: 'h:mm A',
|
||||||
|
lastWeek: 'dddd',
|
||||||
|
sameElse: 'MMM D'
|
||||||
|
}) : null;
|
||||||
|
let buttonText = 'No messages yet';
|
||||||
|
if (dcount === 1) {
|
||||||
|
buttonText = `${ dcount } message`;
|
||||||
|
} else if (dcount > 1 && dcount < 1000) {
|
||||||
|
buttonText = `${ dcount } messages`;
|
||||||
|
} else if (dcount > 999) {
|
||||||
|
buttonText = '+999 messages';
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Text style={styles.textInfo}>{I18n.t('Started_discussion')}</Text>
|
||||||
|
<Text style={styles.text}>{msg}</Text>
|
||||||
|
<View style={styles.buttonContainer}>
|
||||||
|
<Touchable
|
||||||
|
onPress={onDiscussionPress}
|
||||||
|
background={Touchable.Ripple('#fff')}
|
||||||
|
style={[styles.button, styles.smallButton]}
|
||||||
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
|
>
|
||||||
|
<React.Fragment>
|
||||||
|
<CustomIcon name='chat' size={20} style={styles.buttonIcon} />
|
||||||
|
<Text style={styles.buttonText}>{buttonText}</Text>
|
||||||
|
</React.Fragment>
|
||||||
|
</Touchable>
|
||||||
|
<Text style={styles.time}>{time}</Text>
|
||||||
|
</View>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderInner = () => {
|
||||||
|
const { type } = this.props;
|
||||||
|
if (type === 'discussion-created') {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{this.renderUsername()}
|
||||||
|
{this.renderDiscussion()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{this.renderUsername()}
|
||||||
|
{this.renderContent()}
|
||||||
|
{this.renderAttachment()}
|
||||||
|
{this.renderUrl()}
|
||||||
|
{this.renderReactions()}
|
||||||
|
{this.renderBroadcastReply()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
editing, style, header, reactionsModal, closeReactions, msg, ts, reactions, author, user, timeFormat, customEmojis, baseUrl
|
editing, style, header, reactionsModal, closeReactions, msg, ts, reactions, author, user, timeFormat, customEmojis, baseUrl
|
||||||
|
@ -380,12 +455,7 @@ export default class Message extends PureComponent {
|
||||||
this.isTemp() && styles.temp
|
this.isTemp() && styles.temp
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{this.renderUsername()}
|
{this.renderInner()}
|
||||||
{this.renderContent()}
|
|
||||||
{this.renderAttachment()}
|
|
||||||
{this.renderUrl()}
|
|
||||||
{this.renderReactions()}
|
|
||||||
{this.renderBroadcastReply()}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{reactionsModal
|
{reactionsModal
|
||||||
|
|
|
@ -2,12 +2,12 @@ import React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, StyleSheet } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { COLOR_BACKGROUND_CONTAINER, COLOR_BORDER, COLOR_WHITE } from '../../constants/colors';
|
import { COLOR_BACKGROUND_CONTAINER, COLOR_BORDER } from '../../constants/colors';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
|
@ -140,18 +140,17 @@ const Reply = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RectButton
|
<Touchable
|
||||||
onPress={() => onPress(attachment, baseUrl, user)}
|
onPress={() => onPress(attachment, baseUrl, user)}
|
||||||
style={[styles.button, index > 0 && styles.marginTop]}
|
style={[styles.button, index > 0 && styles.marginTop]}
|
||||||
activeOpacity={0.5}
|
background={Touchable.Ripple('#fff')}
|
||||||
underlayColor={COLOR_WHITE}
|
|
||||||
>
|
>
|
||||||
<View style={styles.attachmentContainer}>
|
<View style={styles.attachmentContainer}>
|
||||||
{renderTitle()}
|
{renderTitle()}
|
||||||
{renderText()}
|
{renderText()}
|
||||||
{renderFields()}
|
{renderFields()}
|
||||||
</View>
|
</View>
|
||||||
</RectButton>
|
</Touchable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,12 @@ import React from 'react';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, StyleSheet } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import {
|
import {
|
||||||
COLOR_BACKGROUND_CONTAINER, COLOR_BORDER, COLOR_PRIMARY, COLOR_WHITE
|
COLOR_BACKGROUND_CONTAINER, COLOR_BORDER, COLOR_PRIMARY
|
||||||
} from '../../constants/colors';
|
} from '../../constants/colors';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -58,18 +58,19 @@ const Url = ({ url, index }) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<RectButton
|
<Touchable
|
||||||
onPress={() => onPress(url.url)}
|
onPress={() => onPress(url.url)}
|
||||||
style={[styles.button, index > 0 && styles.marginTop, styles.container]}
|
style={[styles.button, index > 0 && styles.marginTop, styles.container]}
|
||||||
activeOpacity={0.5}
|
background={Touchable.Ripple('#fff')}
|
||||||
underlayColor={COLOR_WHITE}
|
|
||||||
>
|
>
|
||||||
{url.image ? <FastImage source={{ uri: url.image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} /> : null}
|
<React.Fragment>
|
||||||
<View style={styles.textContainer}>
|
{url.image ? <FastImage source={{ uri: url.image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} /> : null}
|
||||||
<Text style={styles.title} numberOfLines={2}>{url.title}</Text>
|
<View style={styles.textContainer}>
|
||||||
<Text style={styles.description} numberOfLines={2}>{url.description}</Text>
|
<Text style={styles.title} numberOfLines={2}>{url.title}</Text>
|
||||||
</View>
|
<Text style={styles.description} numberOfLines={2}>{url.description}</Text>
|
||||||
</RectButton>
|
</View>
|
||||||
|
</React.Fragment>
|
||||||
|
</Touchable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { View, Text, StyleSheet } from 'react-native';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import messageStyles from './styles';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -26,14 +27,6 @@ const styles = StyleSheet.create({
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
...sharedStyles.textColorDescription,
|
...sharedStyles.textColorDescription,
|
||||||
...sharedStyles.textRegular
|
...sharedStyles.textRegular
|
||||||
},
|
|
||||||
time: {
|
|
||||||
fontSize: 12,
|
|
||||||
paddingLeft: 10,
|
|
||||||
lineHeight: 22,
|
|
||||||
...sharedStyles.textColorDescription,
|
|
||||||
...sharedStyles.textRegular,
|
|
||||||
fontWeight: '300'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -70,7 +63,7 @@ export default class User extends React.PureComponent {
|
||||||
{aliasUsername}
|
{aliasUsername}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.time}>{time}</Text>
|
<Text style={messageStyles.time}>{time}</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,12 @@ import PropTypes from 'prop-types';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
import Modal from 'react-native-modal';
|
import Modal from 'react-native-modal';
|
||||||
import VideoPlayer from 'react-native-video-controls';
|
import VideoPlayer from 'react-native-video-controls';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
import { isIOS } from '../../utils/deviceInfo';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import { COLOR_WHITE } from '../../constants/colors';
|
|
||||||
|
|
||||||
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/webm', 'video/3gp', 'video/mkv'])];
|
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/webm', 'video/3gp', 'video/mkv'])];
|
||||||
const isTypeSupported = type => SUPPORTED_TYPES.indexOf(type) !== -1;
|
const isTypeSupported = type => SUPPORTED_TYPES.indexOf(type) !== -1;
|
||||||
|
@ -49,13 +48,13 @@ export default class Video extends React.PureComponent {
|
||||||
return `${ baseUrl }${ video_url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
return `${ baseUrl }${ video_url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleModal() {
|
toggleModal = () => {
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
isVisible: !prevState.isVisible
|
isVisible: !prevState.isVisible
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
open() {
|
open = () => {
|
||||||
const { file } = this.props;
|
const { file } = this.props;
|
||||||
if (isTypeSupported(file.video_type)) {
|
if (isTypeSupported(file.video_type)) {
|
||||||
return this.toggleModal();
|
return this.toggleModal();
|
||||||
|
@ -77,18 +76,17 @@ export default class Video extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
[
|
[
|
||||||
<View key='button'>
|
<View key='button'>
|
||||||
<RectButton
|
<Touchable
|
||||||
|
onPress={this.open}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
onPress={() => this.open()}
|
background={Touchable.Ripple('#fff')}
|
||||||
activeOpacity={0.5}
|
|
||||||
underlayColor={COLOR_WHITE}
|
|
||||||
>
|
>
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
name='play'
|
name='play'
|
||||||
size={54}
|
size={54}
|
||||||
style={styles.image}
|
style={styles.image}
|
||||||
/>
|
/>
|
||||||
</RectButton>
|
</Touchable>
|
||||||
<Markdown msg={description} customEmojis={customEmojis} baseUrl={baseUrl} username={user.username} />
|
<Markdown msg={description} customEmojis={customEmojis} baseUrl={baseUrl} username={user.username} />
|
||||||
</View>,
|
</View>,
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -100,7 +98,7 @@ export default class Video extends React.PureComponent {
|
||||||
>
|
>
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
source={{ uri: this.uri }}
|
source={{ uri: this.uri }}
|
||||||
onBack={() => this.toggleModal()}
|
onBack={this.toggleModal}
|
||||||
disableVolume
|
disableVolume
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -50,6 +50,7 @@ export default class MessageContainer extends React.Component {
|
||||||
// methods - props
|
// methods - props
|
||||||
onLongPress: PropTypes.func,
|
onLongPress: PropTypes.func,
|
||||||
onReactionPress: PropTypes.func,
|
onReactionPress: PropTypes.func,
|
||||||
|
onDiscussionPress: PropTypes.func,
|
||||||
// methods - redux
|
// methods - redux
|
||||||
errorActionsShow: PropTypes.func,
|
errorActionsShow: PropTypes.func,
|
||||||
replyBroadcast: PropTypes.func,
|
replyBroadcast: PropTypes.func,
|
||||||
|
@ -116,12 +117,16 @@ export default class MessageContainer extends React.Component {
|
||||||
onReactionPress(emoji, item._id);
|
onReactionPress(emoji, item._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onReactionLongPress = () => {
|
onReactionLongPress = () => {
|
||||||
this.setState({ reactionsModal: true });
|
this.setState({ reactionsModal: true });
|
||||||
vibrate();
|
vibrate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDiscussionPress = () => {
|
||||||
|
const { onDiscussionPress, item } = this.props;
|
||||||
|
onDiscussionPress(item);
|
||||||
|
}
|
||||||
|
|
||||||
get timeFormat() {
|
get timeFormat() {
|
||||||
const { customTimeFormat, Message_TimeFormat } = this.props;
|
const { customTimeFormat, Message_TimeFormat } = this.props;
|
||||||
return customTimeFormat || Message_TimeFormat;
|
return customTimeFormat || Message_TimeFormat;
|
||||||
|
@ -167,7 +172,7 @@ export default class MessageContainer extends React.Component {
|
||||||
item, editingMessage, user, style, archived, baseUrl, customEmojis, useRealName, broadcast
|
item, editingMessage, user, style, archived, baseUrl, customEmojis, useRealName, broadcast
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
msg, ts, attachments, urls, reactions, t, status, avatar, u, alias, editedBy, role
|
msg, ts, attachments, urls, reactions, t, status, avatar, u, alias, editedBy, role, drid, dcount, dlm
|
||||||
} = item;
|
} = item;
|
||||||
const isEditing = editingMessage._id === item._id;
|
const isEditing = editingMessage._id === item._id;
|
||||||
return (
|
return (
|
||||||
|
@ -195,6 +200,9 @@ export default class MessageContainer extends React.Component {
|
||||||
reactionsModal={reactionsModal}
|
reactionsModal={reactionsModal}
|
||||||
useRealName={useRealName}
|
useRealName={useRealName}
|
||||||
role={role}
|
role={role}
|
||||||
|
drid={drid}
|
||||||
|
dcount={dcount}
|
||||||
|
dlm={dlm}
|
||||||
closeReactions={this.closeReactions}
|
closeReactions={this.closeReactions}
|
||||||
onErrorPress={this.onErrorPress}
|
onErrorPress={this.onErrorPress}
|
||||||
onLongPress={this.onLongPress}
|
onLongPress={this.onLongPress}
|
||||||
|
@ -202,6 +210,7 @@ export default class MessageContainer extends React.Component {
|
||||||
onReactionPress={this.onReactionPress}
|
onReactionPress={this.onReactionPress}
|
||||||
replyBroadcast={this.replyBroadcast}
|
replyBroadcast={this.replyBroadcast}
|
||||||
toggleReactionPicker={this.toggleReactionPicker}
|
toggleReactionPicker={this.toggleReactionPicker}
|
||||||
|
onDiscussionPress={this.onDiscussionPress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet, Platform } from 'react-native';
|
||||||
|
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { COLOR_BORDER, COLOR_PRIMARY, COLOR_WHITE } from '../../constants/colors';
|
import {
|
||||||
|
COLOR_BORDER, COLOR_PRIMARY, COLOR_WHITE, COLOR_BACKGROUND_CONTAINER
|
||||||
|
} from '../../constants/colors';
|
||||||
|
|
||||||
|
const codeFontFamily = Platform.select({
|
||||||
|
ios: { fontFamily: 'Courier New' },
|
||||||
|
android: { fontFamily: 'monospace' }
|
||||||
|
});
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
root: {
|
root: {
|
||||||
|
@ -28,6 +35,11 @@ export default StyleSheet.create({
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
|
text: {
|
||||||
|
fontSize: 16,
|
||||||
|
...sharedStyles.textColorNormal,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
textInfo: {
|
textInfo: {
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
@ -55,6 +67,9 @@ export default StyleSheet.create({
|
||||||
marginBottom: 6,
|
marginBottom: 6,
|
||||||
borderRadius: 2
|
borderRadius: 2
|
||||||
},
|
},
|
||||||
|
reactionButtonReacted: {
|
||||||
|
backgroundColor: '#e8f2ff'
|
||||||
|
},
|
||||||
reactionContainer: {
|
reactionContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
@ -94,22 +109,28 @@ export default StyleSheet.create({
|
||||||
paddingHorizontal: 15,
|
paddingHorizontal: 15,
|
||||||
paddingVertical: 5
|
paddingVertical: 5
|
||||||
},
|
},
|
||||||
broadcastButton: {
|
buttonContainer: {
|
||||||
width: 107,
|
marginTop: 6,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
paddingHorizontal: 15,
|
||||||
height: 44,
|
height: 44,
|
||||||
marginTop: 15,
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
backgroundColor: COLOR_PRIMARY,
|
backgroundColor: COLOR_PRIMARY,
|
||||||
borderRadius: 4
|
borderRadius: 4
|
||||||
},
|
},
|
||||||
broadcastButtonIcon: {
|
smallButton: {
|
||||||
color: COLOR_WHITE,
|
height: 30
|
||||||
marginRight: 11
|
|
||||||
},
|
},
|
||||||
broadcastButtonText: {
|
buttonIcon: {
|
||||||
|
color: COLOR_WHITE,
|
||||||
|
marginRight: 6
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
color: COLOR_WHITE,
|
color: COLOR_WHITE,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
...sharedStyles.textMedium
|
...sharedStyles.textMedium
|
||||||
|
@ -139,8 +160,6 @@ export default StyleSheet.create({
|
||||||
imageContainer: {
|
imageContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
borderColor: COLOR_BORDER,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 4
|
borderRadius: 4
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
|
@ -148,7 +167,8 @@ export default StyleSheet.create({
|
||||||
maxWidth: 400,
|
maxWidth: 400,
|
||||||
minHeight: 200,
|
minHeight: 200,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
marginBottom: 6
|
borderColor: COLOR_BORDER,
|
||||||
|
borderWidth: 1
|
||||||
},
|
},
|
||||||
inlineImage: {
|
inlineImage: {
|
||||||
width: 300,
|
width: 300,
|
||||||
|
@ -159,5 +179,33 @@ export default StyleSheet.create({
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
...sharedStyles.textColorDescription,
|
...sharedStyles.textColorDescription,
|
||||||
...sharedStyles.textRegular
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
codeInline: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
...codeFontFamily,
|
||||||
|
borderWidth: 1,
|
||||||
|
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
codeBlock: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
...codeFontFamily,
|
||||||
|
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
||||||
|
borderColor: COLOR_BORDER,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 4,
|
||||||
|
padding: 4
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
color: COLOR_PRIMARY,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
time: {
|
||||||
|
fontSize: 12,
|
||||||
|
paddingLeft: 10,
|
||||||
|
lineHeight: 22,
|
||||||
|
...sharedStyles.textColorDescription,
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
fontWeight: '300'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -141,6 +141,7 @@ export default {
|
||||||
description: 'description',
|
description: 'description',
|
||||||
Description: 'Description',
|
Description: 'Description',
|
||||||
Disable_notifications: 'Disable notifications',
|
Disable_notifications: 'Disable notifications',
|
||||||
|
Discussions: 'Discussions',
|
||||||
Direct_Messages: 'Direct Messages',
|
Direct_Messages: 'Direct Messages',
|
||||||
Dont_Have_An_Account: 'Don\'t have an account?',
|
Dont_Have_An_Account: 'Don\'t have an account?',
|
||||||
Do_you_really_want_to_key_this_room_question_mark: 'Do you really want to {{key}} this room?',
|
Do_you_really_want_to_key_this_room_question_mark: 'Do you really want to {{key}} this room?',
|
||||||
|
@ -166,6 +167,7 @@ export default {
|
||||||
Group_by_favorites: 'Group favorites',
|
Group_by_favorites: 'Group favorites',
|
||||||
Group_by_type: 'Group by type',
|
Group_by_type: 'Group by type',
|
||||||
Has_joined_the_channel: 'Has joined the channel',
|
Has_joined_the_channel: 'Has joined the channel',
|
||||||
|
Has_joined_the_conversation: 'Has joined the conversation',
|
||||||
Has_left_the_channel: 'Has left the channel',
|
Has_left_the_channel: 'Has left the channel',
|
||||||
Invisible: 'Invisible',
|
Invisible: 'Invisible',
|
||||||
Invite: 'Invite',
|
Invite: 'Invite',
|
||||||
|
@ -299,6 +301,7 @@ export default {
|
||||||
starred: 'starred',
|
starred: 'starred',
|
||||||
Starred: 'Starred',
|
Starred: 'Starred',
|
||||||
Start_of_conversation: 'Start of conversation',
|
Start_of_conversation: 'Start of conversation',
|
||||||
|
Started_discussion: 'Started a discussion:',
|
||||||
Submit: 'Submit',
|
Submit: 'Submit',
|
||||||
Take_a_photo: 'Take a photo',
|
Take_a_photo: 'Take a photo',
|
||||||
tap_to_change_status: 'tap to change status',
|
tap_to_change_status: 'tap to change status',
|
||||||
|
@ -308,6 +311,7 @@ export default {
|
||||||
There_was_an_error_while_action: 'There was an error while {{action}}!',
|
There_was_an_error_while_action: 'There was an error while {{action}}!',
|
||||||
This_room_is_blocked: 'This room is blocked',
|
This_room_is_blocked: 'This room is blocked',
|
||||||
This_room_is_read_only: 'This room is read only',
|
This_room_is_read_only: 'This room is read only',
|
||||||
|
Thread_created: 'Started a new thread: "{{name}}"',
|
||||||
Timezone: 'Timezone',
|
Timezone: 'Timezone',
|
||||||
Toggle_Drawer: 'Toggle_Drawer',
|
Toggle_Drawer: 'Toggle_Drawer',
|
||||||
topic: 'topic',
|
topic: 'topic',
|
||||||
|
|
|
@ -148,6 +148,7 @@ export default {
|
||||||
description: 'descrição',
|
description: 'descrição',
|
||||||
Description: 'Descrição',
|
Description: 'Descrição',
|
||||||
Disable_notifications: 'Desabilitar notificações',
|
Disable_notifications: 'Desabilitar notificações',
|
||||||
|
Discussions: 'Discussões',
|
||||||
Direct_Messages: 'Mensagens Diretas',
|
Direct_Messages: 'Mensagens Diretas',
|
||||||
Dont_Have_An_Account: 'Não tem uma conta?',
|
Dont_Have_An_Account: 'Não tem uma conta?',
|
||||||
Do_you_really_want_to_key_this_room_question_mark: 'Você quer realmente {{key}} esta sala?',
|
Do_you_really_want_to_key_this_room_question_mark: 'Você quer realmente {{key}} esta sala?',
|
||||||
|
@ -173,6 +174,7 @@ export default {
|
||||||
Group_by_favorites: 'Agrupar favoritos',
|
Group_by_favorites: 'Agrupar favoritos',
|
||||||
Group_by_type: 'Agrupar por tipo',
|
Group_by_type: 'Agrupar por tipo',
|
||||||
Has_joined_the_channel: 'Entrou no canal',
|
Has_joined_the_channel: 'Entrou no canal',
|
||||||
|
Has_joined_the_conversation: 'Entrou na conversa',
|
||||||
Has_left_the_channel: 'Saiu da conversa',
|
Has_left_the_channel: 'Saiu da conversa',
|
||||||
Invisible: 'Invisível',
|
Invisible: 'Invisível',
|
||||||
Invite: 'Convidar',
|
Invite: 'Convidar',
|
||||||
|
@ -300,6 +302,7 @@ export default {
|
||||||
starred: 'favoritou',
|
starred: 'favoritou',
|
||||||
Starred: 'Mensagens Favoritas',
|
Starred: 'Mensagens Favoritas',
|
||||||
Start_of_conversation: 'Início da conversa',
|
Start_of_conversation: 'Início da conversa',
|
||||||
|
Started_discussion: 'Iniciou uma discussão:',
|
||||||
Submit: 'Enviar',
|
Submit: 'Enviar',
|
||||||
Take_a_photo: 'Tirar uma foto',
|
Take_a_photo: 'Tirar uma foto',
|
||||||
Terms_of_Service: ' Termos de Serviço ',
|
Terms_of_Service: ' Termos de Serviço ',
|
||||||
|
@ -307,6 +310,7 @@ export default {
|
||||||
There_was_an_error_while_action: 'Aconteceu um erro {{action}}!',
|
There_was_an_error_while_action: 'Aconteceu um erro {{action}}!',
|
||||||
This_room_is_blocked: 'Este quarto está bloqueado',
|
This_room_is_blocked: 'Este quarto está bloqueado',
|
||||||
This_room_is_read_only: 'Este quarto é apenas de leitura',
|
This_room_is_read_only: 'Este quarto é apenas de leitura',
|
||||||
|
Thread_created: 'Iniciou uma thread: "{{name}}"',
|
||||||
Timezone: 'Fuso horário',
|
Timezone: 'Fuso horário',
|
||||||
topic: 'tópico',
|
topic: 'tópico',
|
||||||
Topic: 'Tópico',
|
Topic: 'Tópico',
|
||||||
|
|
|
@ -166,6 +166,7 @@ export default {
|
||||||
Group_by_favorites: 'Agrupar por favoritos',
|
Group_by_favorites: 'Agrupar por favoritos',
|
||||||
Group_by_type: 'Agrupar por tipo',
|
Group_by_type: 'Agrupar por tipo',
|
||||||
Has_joined_the_channel: 'Entrou no canal',
|
Has_joined_the_channel: 'Entrou no canal',
|
||||||
|
Has_joined_the_conversation: 'Entrou na conversa',
|
||||||
Has_left_the_channel: 'Saiu do canal',
|
Has_left_the_channel: 'Saiu do canal',
|
||||||
Invisible: 'Invisível',
|
Invisible: 'Invisível',
|
||||||
Invite: 'Convidar',
|
Invite: 'Convidar',
|
||||||
|
|
|
@ -19,7 +19,7 @@ async function load({ rid: roomId, lastOpen }) {
|
||||||
lastUpdate = getLastUpdate(roomId);
|
lastUpdate = getLastUpdate(roomId);
|
||||||
}
|
}
|
||||||
// RC 0.60.0
|
// RC 0.60.0
|
||||||
const { result } = await this.sdk.get('chat.syncMessages', { roomId, lastUpdate, count: 50 });
|
const { result } = await this.sdk.get('chat.syncMessages', { roomId, lastUpdate });
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,11 +39,10 @@ export async function sendMessageCall(message) {
|
||||||
export default async function(rid, msg) {
|
export default async function(rid, msg) {
|
||||||
try {
|
try {
|
||||||
const message = getMessage(rid, msg);
|
const message = getMessage(rid, msg);
|
||||||
const room = database.objects('subscriptions').filtered('rid == $0', rid);
|
const [room] = database.objects('subscriptions').filtered('rid == $0', rid);
|
||||||
|
|
||||||
// TODO: do we need this?
|
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
room.lastMessage = message;
|
room.draftMessage = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
|
import EJSON from 'ejson';
|
||||||
|
|
||||||
import log from '../../../utils/log';
|
import log from '../../../utils/log';
|
||||||
|
import protectedFunction from '../helpers/protectedFunction';
|
||||||
|
import buildMessage from '../helpers/buildMessage';
|
||||||
|
import database from '../../realm';
|
||||||
|
|
||||||
const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch(() => console.log('unsubscribeRoom')));
|
const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch(() => console.log('unsubscribeRoom')));
|
||||||
const removeListener = listener => listener.stop();
|
const removeListener = listener => listener.stop();
|
||||||
|
|
||||||
let promises;
|
|
||||||
let timer = null;
|
|
||||||
let connectedListener;
|
|
||||||
let disconnectedListener;
|
|
||||||
|
|
||||||
export default function subscribeRoom({ rid }) {
|
export default function subscribeRoom({ rid }) {
|
||||||
if (promises) {
|
let promises;
|
||||||
promises.then(unsubscribe);
|
let timer = null;
|
||||||
promises = false;
|
let connectedListener;
|
||||||
}
|
let disconnectedListener;
|
||||||
|
let notifyRoomListener;
|
||||||
|
let messageReceivedListener;
|
||||||
|
const typingTimeouts = {};
|
||||||
const loop = () => {
|
const loop = () => {
|
||||||
if (timer) {
|
if (timer) {
|
||||||
return;
|
return;
|
||||||
|
@ -41,6 +44,96 @@ export default function subscribeRoom({ rid }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getUserTyping = username => (
|
||||||
|
database
|
||||||
|
.memoryDatabase.objects('usersTyping')
|
||||||
|
.filtered('rid = $0 AND username = $1', rid, username)
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeUserTyping = (username) => {
|
||||||
|
const userTyping = getUserTyping(username);
|
||||||
|
try {
|
||||||
|
database.memoryDatabase.write(() => {
|
||||||
|
database.memoryDatabase.delete(userTyping);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typingTimeouts[username]) {
|
||||||
|
clearTimeout(typingTimeouts[username]);
|
||||||
|
typingTimeouts[username] = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('TCL: removeUserTyping -> error', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addUserTyping = (username) => {
|
||||||
|
const userTyping = getUserTyping(username);
|
||||||
|
// prevent duplicated
|
||||||
|
if (userTyping.length === 0) {
|
||||||
|
try {
|
||||||
|
database.memoryDatabase.write(() => {
|
||||||
|
database.memoryDatabase.create('usersTyping', { rid, username });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typingTimeouts[username]) {
|
||||||
|
clearTimeout(typingTimeouts[username]);
|
||||||
|
typingTimeouts[username] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
typingTimeouts[username] = setTimeout(() => {
|
||||||
|
removeUserTyping(username);
|
||||||
|
}, 10000);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('TCL: addUserTyping -> error', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNotifyRoomReceived = protectedFunction((ddpMessage) => {
|
||||||
|
const [_rid, ev] = ddpMessage.fields.eventName.split('/');
|
||||||
|
if (rid !== _rid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ev === 'typing') {
|
||||||
|
const [username, typing] = ddpMessage.fields.args;
|
||||||
|
if (typing) {
|
||||||
|
addUserTyping(username);
|
||||||
|
} else {
|
||||||
|
removeUserTyping(username);
|
||||||
|
}
|
||||||
|
} else if (ev === 'deleteMessage') {
|
||||||
|
database.write(() => {
|
||||||
|
if (ddpMessage && ddpMessage.fields && ddpMessage.fields.args.length > 0) {
|
||||||
|
const { _id } = ddpMessage.fields.args[0];
|
||||||
|
const message = database.objects('messages').filtered('_id = $0', _id);
|
||||||
|
database.delete(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleMessageReceived = protectedFunction((ddpMessage) => {
|
||||||
|
const message = buildMessage(ddpMessage.fields.args[0]);
|
||||||
|
if (rid !== message.rid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
try {
|
||||||
|
database.write(() => {
|
||||||
|
database.create('messages', EJSON.fromJSONValue(message), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||||
|
|
||||||
|
if (room._id) {
|
||||||
|
this.readMessages(rid);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('handleMessageReceived', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
if (promises) {
|
if (promises) {
|
||||||
promises.then(unsubscribe);
|
promises.then(unsubscribe);
|
||||||
|
@ -54,12 +147,28 @@ export default function subscribeRoom({ rid }) {
|
||||||
disconnectedListener.then(removeListener);
|
disconnectedListener.then(removeListener);
|
||||||
disconnectedListener = false;
|
disconnectedListener = false;
|
||||||
}
|
}
|
||||||
|
if (notifyRoomListener) {
|
||||||
|
notifyRoomListener.then(removeListener);
|
||||||
|
notifyRoomListener = false;
|
||||||
|
}
|
||||||
|
if (messageReceivedListener) {
|
||||||
|
messageReceivedListener.then(removeListener);
|
||||||
|
messageReceivedListener = false;
|
||||||
|
}
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
timer = false;
|
timer = false;
|
||||||
|
Object.keys(typingTimeouts).forEach((key) => {
|
||||||
|
if (typingTimeouts[key]) {
|
||||||
|
clearTimeout(typingTimeouts[key]);
|
||||||
|
typingTimeouts[key] = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
connectedListener = this.sdk.onStreamData('connected', handleConnected);
|
connectedListener = this.sdk.onStreamData('connected', handleConnected);
|
||||||
disconnectedListener = this.sdk.onStreamData('close', handleDisconnected);
|
disconnectedListener = this.sdk.onStreamData('close', handleDisconnected);
|
||||||
|
notifyRoomListener = this.sdk.onStreamData('stream-notify-room', handleNotifyRoomReceived);
|
||||||
|
messageReceivedListener = this.sdk.onStreamData('stream-room-messages', handleMessageReceived);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
promises = this.sdk.subscribeRoom(rid);
|
promises = this.sdk.subscribeRoom(rid);
|
||||||
|
|
|
@ -102,6 +102,7 @@ const subscriptionSchema = {
|
||||||
notifications: { type: 'bool', optional: true },
|
notifications: { type: 'bool', optional: true },
|
||||||
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 },
|
||||||
draftMessage: { type: 'string', optional: true }
|
draftMessage: { type: 'string', optional: true }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -219,7 +220,10 @@ const messagesSchema = {
|
||||||
starred: { type: 'bool', optional: true },
|
starred: { type: 'bool', optional: true },
|
||||||
editedBy: 'messagesEditedBy',
|
editedBy: 'messagesEditedBy',
|
||||||
reactions: { type: 'list', objectType: 'messagesReactions' },
|
reactions: { type: 'list', objectType: 'messagesReactions' },
|
||||||
role: { type: 'string', optional: true }
|
role: { type: 'string', optional: true },
|
||||||
|
drid: { type: 'string', optional: true },
|
||||||
|
dcount: { type: 'int', optional: true },
|
||||||
|
dlm: { type: 'date', optional: true }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -279,6 +283,14 @@ const uploadsSchema = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const usersTypingSchema = {
|
||||||
|
name: 'usersTyping',
|
||||||
|
properties: {
|
||||||
|
rid: { type: 'string', indexed: true },
|
||||||
|
username: { type: 'string', optional: true }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const schema = [
|
const schema = [
|
||||||
settingsSchema,
|
settingsSchema,
|
||||||
subscriptionSchema,
|
subscriptionSchema,
|
||||||
|
@ -302,6 +314,8 @@ const schema = [
|
||||||
uploadsSchema
|
uploadsSchema
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const inMemorySchema = [usersTypingSchema];
|
||||||
|
|
||||||
class DB {
|
class DB {
|
||||||
databases = {
|
databases = {
|
||||||
serversDB: new Realm({
|
serversDB: new Realm({
|
||||||
|
@ -309,7 +323,23 @@ class DB {
|
||||||
schema: [
|
schema: [
|
||||||
serversSchema
|
serversSchema
|
||||||
],
|
],
|
||||||
schemaVersion: 1
|
schemaVersion: 2,
|
||||||
|
migration: (oldRealm, newRealm) => {
|
||||||
|
if (oldRealm.schemaVersion === 1 && newRealm.schemaVersion === 2) {
|
||||||
|
const newServers = newRealm.objects('servers');
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-plusplus
|
||||||
|
for (let i = 0; i < newServers.length; i++) {
|
||||||
|
newServers[i].roomsUpdatedAt = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
inMemoryDB: new Realm({
|
||||||
|
path: 'memory.realm',
|
||||||
|
schema: inMemorySchema,
|
||||||
|
schemaVersion: 1,
|
||||||
|
inMemory: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,12 +367,29 @@ class DB {
|
||||||
return this.databases.activeDB;
|
return this.databases.activeDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get memoryDatabase() {
|
||||||
|
return this.databases.inMemoryDB;
|
||||||
|
}
|
||||||
|
|
||||||
setActiveDB(database = '') {
|
setActiveDB(database = '') {
|
||||||
const path = database.replace(/(^\w+:|^)\/\//, '');
|
const path = database.replace(/(^\w+:|^)\/\//, '');
|
||||||
return this.databases.activeDB = new Realm({
|
return this.databases.activeDB = new Realm({
|
||||||
path: `${ path }.realm`,
|
path: `${ path }.realm`,
|
||||||
schema,
|
schema,
|
||||||
schemaVersion: 2
|
schemaVersion: 4,
|
||||||
|
migration: (oldRealm, newRealm) => {
|
||||||
|
if (oldRealm.schemaVersion === 3 && newRealm.schemaVersion === 4) {
|
||||||
|
const newSubs = newRealm.objects('subscriptions');
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-plusplus
|
||||||
|
for (let i = 0; i < newSubs.length; i++) {
|
||||||
|
newSubs[i].lastOpen = null;
|
||||||
|
newSubs[i].ls = null;
|
||||||
|
}
|
||||||
|
const newMessages = newRealm.objects('messages');
|
||||||
|
newRealm.delete(newMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
} from '../actions/login';
|
} from '../actions/login';
|
||||||
import { disconnect, connectSuccess, connectRequest } from '../actions/connect';
|
import { disconnect, connectSuccess, connectRequest } from '../actions/connect';
|
||||||
import { setActiveUser } from '../actions/activeUsers';
|
import { setActiveUser } from '../actions/activeUsers';
|
||||||
import { someoneTyping, roomMessageReceived } from '../actions/room';
|
|
||||||
import { setRoles } from '../actions/roles';
|
import { setRoles } from '../actions/roles';
|
||||||
|
|
||||||
import subscribeRooms from './methods/subscriptions/rooms';
|
import subscribeRooms from './methods/subscriptions/rooms';
|
||||||
|
@ -30,7 +29,6 @@ import getPermissions from './methods/getPermissions';
|
||||||
import getCustomEmoji from './methods/getCustomEmojis';
|
import getCustomEmoji from './methods/getCustomEmojis';
|
||||||
import canOpenRoom from './methods/canOpenRoom';
|
import canOpenRoom from './methods/canOpenRoom';
|
||||||
|
|
||||||
import _buildMessage from './methods/helpers/buildMessage';
|
|
||||||
import loadMessagesForRoom from './methods/loadMessagesForRoom';
|
import loadMessagesForRoom from './methods/loadMessagesForRoom';
|
||||||
import loadMissedMessages from './methods/loadMissedMessages';
|
import loadMissedMessages from './methods/loadMissedMessages';
|
||||||
|
|
||||||
|
@ -202,27 +200,6 @@ const RocketChat = {
|
||||||
|
|
||||||
this.sdk.onStreamData('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage)));
|
this.sdk.onStreamData('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage)));
|
||||||
|
|
||||||
this.sdk.onStreamData('stream-room-messages', (ddpMessage) => {
|
|
||||||
// TODO: debounce
|
|
||||||
const message = _buildMessage(ddpMessage.fields.args[0]);
|
|
||||||
requestAnimationFrame(() => reduxStore.dispatch(roomMessageReceived(message)));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sdk.onStreamData('stream-notify-room', protectedFunction((ddpMessage) => {
|
|
||||||
const [_rid, ev] = ddpMessage.fields.eventName.split('/');
|
|
||||||
if (ev === 'typing') {
|
|
||||||
reduxStore.dispatch(someoneTyping({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] }));
|
|
||||||
} else if (ev === 'deleteMessage') {
|
|
||||||
database.write(() => {
|
|
||||||
if (ddpMessage && ddpMessage.fields && ddpMessage.fields.args.length > 0) {
|
|
||||||
const { _id } = ddpMessage.fields.args[0];
|
|
||||||
const message = database.objects('messages').filtered('_id = $0', _id);
|
|
||||||
database.delete(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.sdk.onStreamData('rocketchat_roles', protectedFunction((ddpMessage) => {
|
this.sdk.onStreamData('rocketchat_roles', protectedFunction((ddpMessage) => {
|
||||||
this.roles = this.roles || {};
|
this.roles = this.roles || {};
|
||||||
|
|
||||||
|
@ -567,6 +544,9 @@ const RocketChat = {
|
||||||
unsubscribe(subscription) {
|
unsubscribe(subscription) {
|
||||||
return this.sdk.unsubscribe(subscription);
|
return this.sdk.unsubscribe(subscription);
|
||||||
},
|
},
|
||||||
|
onStreamData(...args) {
|
||||||
|
return this.sdk.onStreamData(...args);
|
||||||
|
},
|
||||||
emitTyping(room, t = true) {
|
emitTyping(room, t = true) {
|
||||||
const { login } = reduxStore.getState();
|
const { login } = reduxStore.getState();
|
||||||
return this.sdk.methodCall('stream-notify-room', `${ room }/typing`, login.user.username, t);
|
return this.sdk.methodCall('stream-notify-room', `${ room }/typing`, login.user.username, t);
|
||||||
|
@ -600,6 +580,10 @@ const RocketChat = {
|
||||||
// RC 0.65.0
|
// RC 0.65.0
|
||||||
return this.sdk.get(`${ this.roomTypeToApiType(t) }.counters`, { roomId });
|
return this.sdk.get(`${ this.roomTypeToApiType(t) }.counters`, { roomId });
|
||||||
},
|
},
|
||||||
|
getChannelInfo(roomId) {
|
||||||
|
// RC 0.48.0
|
||||||
|
return this.sdk.get('channels.info', { roomId });
|
||||||
|
},
|
||||||
async getRoomMember(rid, currentUserId) {
|
async getRoomMember(rid, currentUserId) {
|
||||||
try {
|
try {
|
||||||
if (rid === `${ currentUserId }${ currentUserId }`) {
|
if (rid === `${ currentUserId }${ currentUserId }`) {
|
||||||
|
@ -669,7 +653,13 @@ const RocketChat = {
|
||||||
let roles = [];
|
let roles = [];
|
||||||
try {
|
try {
|
||||||
// get the room from realm
|
// get the room from realm
|
||||||
const room = database.objects('subscriptions').filtered('rid = $0', rid)[0];
|
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||||
|
if (!room) {
|
||||||
|
return permissions.reduce((result, permission) => {
|
||||||
|
result[permission] = false;
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
// get room roles
|
// get room roles
|
||||||
roles = room.roles; // eslint-disable-line prefer-destructuring
|
roles = room.roles; // eslint-disable-line prefer-destructuring
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -137,6 +137,7 @@ export default class RoomItem extends React.Component {
|
||||||
unread: PropTypes.number,
|
unread: PropTypes.number,
|
||||||
userMentions: PropTypes.number,
|
userMentions: PropTypes.number,
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
|
prid: PropTypes.string,
|
||||||
onPress: PropTypes.func,
|
onPress: PropTypes.func,
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
|
@ -209,11 +210,11 @@ export default class RoomItem extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
get type() {
|
get type() {
|
||||||
const { type, id } = this.props;
|
const { type, id, prid } = this.props;
|
||||||
if (type === 'd') {
|
if (type === 'd') {
|
||||||
return <Status style={styles.status} size={10} id={id} />;
|
return <Status style={styles.status} size={10} id={id} />;
|
||||||
}
|
}
|
||||||
return <RoomTypeIcon type={type} />;
|
return <RoomTypeIcon type={prid ? 'discussion' : type} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
formatDate = date => moment(date).calendar(null, {
|
formatDate = date => moment(date).calendar(null, {
|
||||||
|
|
|
@ -3,7 +3,6 @@ import settings from './reducers';
|
||||||
import login from './login';
|
import login from './login';
|
||||||
import meteor from './connect';
|
import meteor from './connect';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
import room from './room';
|
|
||||||
import rooms from './rooms';
|
import rooms from './rooms';
|
||||||
import server from './server';
|
import server from './server';
|
||||||
import selectedUsers from './selectedUsers';
|
import selectedUsers from './selectedUsers';
|
||||||
|
@ -23,7 +22,6 @@ export default combineReducers({
|
||||||
selectedUsers,
|
selectedUsers,
|
||||||
createChannel,
|
createChannel,
|
||||||
app,
|
app,
|
||||||
room,
|
|
||||||
rooms,
|
rooms,
|
||||||
customEmojis,
|
customEmojis,
|
||||||
activeUsers,
|
activeUsers,
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import * as types from '../actions/actionsTypes';
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
usersTyping: []
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function room(state = initialState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case types.ROOM.OPEN:
|
|
||||||
return {
|
|
||||||
...initialState,
|
|
||||||
...action.room
|
|
||||||
};
|
|
||||||
case types.ROOM.CLOSE:
|
|
||||||
return {
|
|
||||||
...initialState
|
|
||||||
};
|
|
||||||
case types.ROOM.ADD_USER_TYPING:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
usersTyping: [...state.usersTyping.filter(user => user !== action.username), action.username]
|
|
||||||
};
|
|
||||||
case types.ROOM.REMOVE_USER_TYPING:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
usersTyping: [...state.usersTyping.filter(user => user !== action.username)]
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,122 +1,30 @@
|
||||||
import { Alert } from 'react-native';
|
import { Alert } from 'react-native';
|
||||||
import {
|
import {
|
||||||
put, call, takeLatest, take, select, race, fork, cancel, takeEvery
|
call, takeLatest, take, select
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
import { delay } from 'redux-saga';
|
import { delay } from 'redux-saga';
|
||||||
import EJSON from 'ejson';
|
|
||||||
|
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import { addUserTyping, removeUserTyping } from '../actions/room';
|
|
||||||
import { messagesRequest, editCancel, replyCancel } from '../actions/messages';
|
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/realm';
|
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
|
|
||||||
|
const watchUserTyping = function* watchUserTyping({ rid, status }) {
|
||||||
let sub;
|
|
||||||
let thread;
|
|
||||||
|
|
||||||
const cancelTyping = function* cancelTyping(username) {
|
|
||||||
while (true) {
|
|
||||||
const { typing, timeout } = yield race({
|
|
||||||
typing: take(types.ROOM.SOMEONE_TYPING),
|
|
||||||
timeout: call(delay, 5000)
|
|
||||||
});
|
|
||||||
if (timeout || (typing.username === username && !typing.typing)) {
|
|
||||||
return yield put(removeUserTyping(username));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const usersTyping = function* usersTyping({ rid }) {
|
|
||||||
while (true) {
|
|
||||||
const { _rid, username, typing } = yield take(types.ROOM.SOMEONE_TYPING);
|
|
||||||
if (_rid === rid) {
|
|
||||||
yield (typing ? put(addUserTyping(username)) : put(removeUserTyping(username)));
|
|
||||||
if (typing) {
|
|
||||||
yield fork(cancelTyping, username);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleMessageReceived = function* handleMessageReceived({ message }) {
|
|
||||||
try {
|
|
||||||
const room = yield select(state => state.room);
|
|
||||||
|
|
||||||
if (message.rid === room.rid) {
|
|
||||||
database.write(() => {
|
|
||||||
database.create('messages', EJSON.fromJSONValue(message), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (room._id) {
|
|
||||||
RocketChat.readMessages(room.rid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('handleMessageReceived', e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let opened = false;
|
|
||||||
|
|
||||||
const watchRoomOpen = function* watchRoomOpen({ room }) {
|
|
||||||
try {
|
|
||||||
if (opened) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
opened = true;
|
|
||||||
|
|
||||||
const auth = yield select(state => state.login.isAuthenticated);
|
|
||||||
if (!auth) {
|
|
||||||
yield take(types.LOGIN.SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield put(messagesRequest({ ...room }));
|
|
||||||
|
|
||||||
if (room._id) {
|
|
||||||
RocketChat.readMessages(room.rid);
|
|
||||||
}
|
|
||||||
|
|
||||||
sub = yield RocketChat.subscribeRoom(room);
|
|
||||||
|
|
||||||
thread = yield fork(usersTyping, { rid: room.rid });
|
|
||||||
yield race({
|
|
||||||
open: take(types.ROOM.OPEN),
|
|
||||||
close: take(types.ROOM.CLOSE)
|
|
||||||
});
|
|
||||||
opened = false;
|
|
||||||
cancel(thread);
|
|
||||||
sub.stop();
|
|
||||||
yield put(editCancel());
|
|
||||||
yield put(replyCancel());
|
|
||||||
} catch (e) {
|
|
||||||
log('watchRoomOpen', e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const watchuserTyping = function* watchuserTyping({ status }) {
|
|
||||||
const auth = yield select(state => state.login.isAuthenticated);
|
const auth = yield select(state => state.login.isAuthenticated);
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
yield take(types.LOGIN.SUCCESS);
|
yield take(types.LOGIN.SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
const room = yield select(state => state.room);
|
|
||||||
|
|
||||||
if (!room) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
yield RocketChat.emitTyping(room.rid, status);
|
yield RocketChat.emitTyping(rid, status);
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
yield call(delay, 5000);
|
yield call(delay, 5000);
|
||||||
yield RocketChat.emitTyping(room.rid, false);
|
yield RocketChat.emitTyping(rid, false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('watchuserTyping', e);
|
log('watchUserTyping', e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -147,9 +55,7 @@ const handleEraseRoom = function* handleEraseRoom({ rid, t }) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.ROOM.USER_TYPING, watchuserTyping);
|
yield takeLatest(types.ROOM.USER_TYPING, watchUserTyping);
|
||||||
yield takeEvery(types.ROOM.OPEN, watchRoomOpen);
|
|
||||||
yield takeEvery(types.ROOM.MESSAGE_RECEIVED, handleMessageReceived);
|
|
||||||
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
|
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
|
||||||
yield takeLatest(types.ROOM.ERASE, handleEraseRoom);
|
yield takeLatest(types.ROOM.ERASE, handleEraseRoom);
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,7 +16,6 @@ import StatusBar from '../../containers/StatusBar';
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
customEmojis: state.customEmojis,
|
customEmojis: state.customEmojis,
|
||||||
room: state.room,
|
|
||||||
user: {
|
user: {
|
||||||
id: state.login.user && state.login.user.id,
|
id: state.login.user && state.login.user.id,
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
|
@ -33,7 +32,7 @@ export default class MentionedMessagesView extends LoggedView {
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
customEmojis: PropTypes.object,
|
customEmojis: PropTypes.object,
|
||||||
room: PropTypes.object
|
navigation: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -42,6 +41,8 @@ export default class MentionedMessagesView extends LoggedView {
|
||||||
loading: false,
|
loading: false,
|
||||||
messages: []
|
messages: []
|
||||||
};
|
};
|
||||||
|
this.rid = props.navigation.getParam('rid');
|
||||||
|
this.t = props.navigation.getParam('t');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -71,10 +72,9 @@ export default class MentionedMessagesView extends LoggedView {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { room } = this.props;
|
|
||||||
const result = await RocketChat.getMessages(
|
const result = await RocketChat.getMessages(
|
||||||
room.rid,
|
this.rid,
|
||||||
room.t,
|
this.t,
|
||||||
{ 'mentions._id': { $in: [user.id] } },
|
{ 'mentions._id': { $in: [user.id] } },
|
||||||
messages.length
|
messages.length
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,7 +21,6 @@ const options = [I18n.t('Unpin'), I18n.t('Cancel')];
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
customEmojis: state.customEmojis,
|
customEmojis: state.customEmojis,
|
||||||
room: state.room,
|
|
||||||
user: {
|
user: {
|
||||||
id: state.login.user && state.login.user.id,
|
id: state.login.user && state.login.user.id,
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
|
@ -38,7 +37,7 @@ export default class PinnedMessagesView extends LoggedView {
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
customEmojis: PropTypes.object,
|
customEmojis: PropTypes.object,
|
||||||
room: PropTypes.object
|
navigation: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -47,6 +46,8 @@ export default class PinnedMessagesView extends LoggedView {
|
||||||
loading: false,
|
loading: false,
|
||||||
messages: []
|
messages: []
|
||||||
};
|
};
|
||||||
|
this.rid = props.navigation.getParam('rid');
|
||||||
|
this.t = props.navigation.getParam('t');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -114,8 +115,7 @@ export default class PinnedMessagesView extends LoggedView {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { room } = this.props;
|
const result = await RocketChat.getMessages(this.rid, this.t, { pinned: true }, messages.length);
|
||||||
const result = await RocketChat.getMessages(room.rid, room.t, { pinned: true }, messages.length);
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
messages: [...prevState.messages, ...result.messages],
|
messages: [...prevState.messages, ...result.messages],
|
||||||
|
|
|
@ -32,8 +32,7 @@ const renderSeparator = () => <View style={styles.separator} />;
|
||||||
id: state.login.user && state.login.user.id,
|
id: state.login.user && state.login.user.id,
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
},
|
},
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
room: state.room
|
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
leaveRoom: (rid, t) => dispatch(leaveRoomAction(rid, t))
|
leaveRoom: (rid, t) => dispatch(leaveRoomAction(rid, t))
|
||||||
}))
|
}))
|
||||||
|
@ -50,25 +49,36 @@ export default class RoomActionsView extends LoggedView {
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
token: PropTypes.string
|
token: PropTypes.string
|
||||||
}),
|
}),
|
||||||
room: PropTypes.object,
|
|
||||||
leaveRoom: PropTypes.func
|
leaveRoom: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super('RoomActionsView', props);
|
super('RoomActionsView', props);
|
||||||
this.rid = props.navigation.getParam('rid');
|
this.rid = props.navigation.getParam('rid');
|
||||||
|
this.t = props.navigation.getParam('t');
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
||||||
this.state = {
|
this.state = {
|
||||||
room: this.rooms[0] || props.room,
|
room: this.rooms[0] || { rid: this.rid, t: this.t },
|
||||||
membersCount: 0,
|
membersCount: 0,
|
||||||
member: {},
|
member: {},
|
||||||
joined: false,
|
joined: this.rooms.length > 0,
|
||||||
canViewMembers: false
|
canViewMembers: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
|
if (!room._id) {
|
||||||
|
try {
|
||||||
|
const result = await RocketChat.getChannelInfo(room.rid);
|
||||||
|
if (result.success) {
|
||||||
|
this.setState({ room: { ...result.channel, rid: result.channel._id } });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('RoomActionsView -> getChannelInfo -> error', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (room && room.t !== 'd' && this.canViewMembers) {
|
if (room && room.t !== 'd' && this.canViewMembers) {
|
||||||
try {
|
try {
|
||||||
const counters = await RocketChat.getRoomCounters(room.rid, room.t);
|
const counters = await RocketChat.getRoomCounters(room.rid, room.t);
|
||||||
|
@ -120,6 +130,7 @@ export default class RoomActionsView extends LoggedView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move to componentDidMount
|
||||||
get canAddUser() {
|
get canAddUser() {
|
||||||
const { room, joined } = this.state;
|
const { room, joined } = this.state;
|
||||||
const { rid, t } = room;
|
const { rid, t } = room;
|
||||||
|
@ -139,6 +150,7 @@ export default class RoomActionsView extends LoggedView {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move to componentDidMount
|
||||||
get canViewMembers() {
|
get canViewMembers() {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { rid, t, broadcast } = room;
|
const { rid, t, broadcast } = room;
|
||||||
|
@ -177,7 +189,8 @@ export default class RoomActionsView extends LoggedView {
|
||||||
icon: 'star',
|
icon: 'star',
|
||||||
name: I18n.t('Room_Info'),
|
name: I18n.t('Room_Info'),
|
||||||
route: 'RoomInfoView',
|
route: 'RoomInfoView',
|
||||||
params: { rid },
|
// forward room only if room isn't joined
|
||||||
|
params: { rid, t, room: joined ? null : room },
|
||||||
testID: 'room-actions-info'
|
testID: 'room-actions-info'
|
||||||
}],
|
}],
|
||||||
renderItem: this.renderRoomInfo
|
renderItem: this.renderRoomInfo
|
||||||
|
@ -203,18 +216,21 @@ export default class RoomActionsView extends LoggedView {
|
||||||
icon: 'file-generic',
|
icon: 'file-generic',
|
||||||
name: I18n.t('Files'),
|
name: I18n.t('Files'),
|
||||||
route: 'RoomFilesView',
|
route: 'RoomFilesView',
|
||||||
|
params: { rid, t },
|
||||||
testID: 'room-actions-files'
|
testID: 'room-actions-files'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'at',
|
icon: 'at',
|
||||||
name: I18n.t('Mentions'),
|
name: I18n.t('Mentions'),
|
||||||
route: 'MentionedMessagesView',
|
route: 'MentionedMessagesView',
|
||||||
|
params: { rid, t },
|
||||||
testID: 'room-actions-mentioned'
|
testID: 'room-actions-mentioned'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'star',
|
icon: 'star',
|
||||||
name: I18n.t('Starred'),
|
name: I18n.t('Starred'),
|
||||||
route: 'StarredMessagesView',
|
route: 'StarredMessagesView',
|
||||||
|
params: { rid, t },
|
||||||
testID: 'room-actions-starred'
|
testID: 'room-actions-starred'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -234,6 +250,7 @@ export default class RoomActionsView extends LoggedView {
|
||||||
icon: 'pin',
|
icon: 'pin',
|
||||||
name: I18n.t('Pinned'),
|
name: I18n.t('Pinned'),
|
||||||
route: 'PinnedMessagesView',
|
route: 'PinnedMessagesView',
|
||||||
|
params: { rid, t },
|
||||||
testID: 'room-actions-pinned'
|
testID: 'room-actions-pinned'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -389,8 +406,8 @@ export default class RoomActionsView extends LoggedView {
|
||||||
? <Text style={styles.roomTitle}>{room.fname}</Text>
|
? <Text style={styles.roomTitle}>{room.fname}</Text>
|
||||||
: (
|
: (
|
||||||
<View style={styles.roomTitleRow}>
|
<View style={styles.roomTitleRow}>
|
||||||
<RoomTypeIcon type={room.t} />
|
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} />
|
||||||
<Text style={styles.roomTitle}>{room.name}</Text>
|
<Text style={styles.roomTitle}>{room.prid ? room.fname : room.name}</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import StatusBar from '../../containers/StatusBar';
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
customEmojis: state.customEmojis,
|
customEmojis: state.customEmojis,
|
||||||
room: state.room,
|
|
||||||
user: {
|
user: {
|
||||||
id: state.login.user && state.login.user.id,
|
id: state.login.user && state.login.user.id,
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
|
@ -33,7 +32,7 @@ export default class RoomFilesView extends LoggedView {
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
customEmojis: PropTypes.object,
|
customEmojis: PropTypes.object,
|
||||||
room: PropTypes.object
|
navigation: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -42,6 +41,8 @@ export default class RoomFilesView extends LoggedView {
|
||||||
loading: false,
|
loading: false,
|
||||||
messages: []
|
messages: []
|
||||||
};
|
};
|
||||||
|
this.rid = props.navigation.getParam('rid');
|
||||||
|
this.t = props.navigation.getParam('t');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -70,8 +71,7 @@ export default class RoomFilesView extends LoggedView {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { room } = this.props;
|
const result = await RocketChat.getFiles(this.rid, this.t, messages.length);
|
||||||
const result = await RocketChat.getFiles(room.rid, room.t, messages.length);
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
messages: [...prevState.messages, ...result.files],
|
messages: [...prevState.messages, ...result.files],
|
||||||
|
|
|
@ -26,8 +26,8 @@ const getRoomTitle = room => (room.t === 'd'
|
||||||
? <Text testID='room-info-view-name' style={styles.roomTitle}>{room.fname}</Text>
|
? <Text testID='room-info-view-name' style={styles.roomTitle}>{room.fname}</Text>
|
||||||
: (
|
: (
|
||||||
<View style={styles.roomTitleRow}>
|
<View style={styles.roomTitleRow}>
|
||||||
<RoomTypeIcon type={room.t} key='room-info-type' />
|
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} key='room-info-type' />
|
||||||
<Text testID='room-info-view-name' style={styles.roomTitle} key='room-info-name'>{room.name}</Text>
|
<Text testID='room-info-view-name' style={styles.roomTitle} key='room-info-name'>{room.prid ? room.fname : room.name}</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -40,8 +40,7 @@ const getRoomTitle = room => (room.t === 'd'
|
||||||
},
|
},
|
||||||
activeUsers: state.activeUsers, // TODO: remove it
|
activeUsers: state.activeUsers, // TODO: remove it
|
||||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||||
allRoles: state.roles,
|
allRoles: state.roles
|
||||||
room: state.room
|
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class RoomInfoView extends LoggedView {
|
export default class RoomInfoView extends LoggedView {
|
||||||
|
@ -75,12 +74,13 @@ export default class RoomInfoView extends LoggedView {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super('RoomInfoView', props);
|
super('RoomInfoView', props);
|
||||||
const rid = props.navigation.getParam('rid');
|
const rid = props.navigation.getParam('rid');
|
||||||
|
const room = props.navigation.getParam('room');
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
|
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||||
this.sub = {
|
this.sub = {
|
||||||
unsubscribe: () => {}
|
unsubscribe: () => {}
|
||||||
};
|
};
|
||||||
this.state = {
|
this.state = {
|
||||||
room: this.rooms[0] || {},
|
room: this.rooms[0] || room || {},
|
||||||
roomUser: {},
|
roomUser: {},
|
||||||
roles: []
|
roles: []
|
||||||
};
|
};
|
||||||
|
@ -90,7 +90,7 @@ export default class RoomInfoView extends LoggedView {
|
||||||
safeAddListener(this.rooms, this.updateRoom);
|
safeAddListener(this.rooms, this.updateRoom);
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
|
const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
|
||||||
if (permissions[PERMISSION_EDIT_ROOM]) {
|
if (permissions[PERMISSION_EDIT_ROOM] && !room.prid) {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
navigation.setParams({ showEdit: true });
|
navigation.setParams({ showEdit: true });
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ import StatusBar from '../../containers/StatusBar';
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
room: state.room,
|
|
||||||
user: {
|
user: {
|
||||||
id: state.login.user && state.login.user.id,
|
id: state.login.user && state.login.user.id,
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
|
@ -191,10 +190,15 @@ export default class RoomMembersView extends LoggedView {
|
||||||
this.setState({ isLoading: true });
|
this.setState({ isLoading: true });
|
||||||
const { rid } = this.state;
|
const { rid } = this.state;
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
const membersResult = await RocketChat.getRoomMembers(rid, status);
|
try {
|
||||||
const members = membersResult.records;
|
const membersResult = await RocketChat.getRoomMembers(rid, status);
|
||||||
this.setState({ allUsers: status, members, isLoading: false });
|
const members = membersResult.records;
|
||||||
navigation.setParams({ allUsers: status });
|
this.setState({ allUsers: status, members, isLoading: false });
|
||||||
|
navigation.setParams({ allUsers: status });
|
||||||
|
} catch (error) {
|
||||||
|
console.log('TCL: fetchMembers -> error', error);
|
||||||
|
this.setState({ isLoading: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRoom = () => {
|
updateRoom = () => {
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
View, Text, StyleSheet, ScrollView
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
|
import I18n from '../../../i18n';
|
||||||
|
import sharedStyles from '../../Styles';
|
||||||
|
import { isIOS } from '../../../utils/deviceInfo';
|
||||||
|
import Icon from './Icon';
|
||||||
|
import { COLOR_TEXT_DESCRIPTION, HEADER_TITLE, COLOR_WHITE } from '../../../constants/colors';
|
||||||
|
|
||||||
|
const TITLE_SIZE = 16;
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
height: '100%'
|
||||||
|
},
|
||||||
|
titleContainer: {
|
||||||
|
flex: 6,
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
...sharedStyles.textSemibold,
|
||||||
|
color: HEADER_TITLE,
|
||||||
|
fontSize: TITLE_SIZE
|
||||||
|
},
|
||||||
|
scroll: {
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
typing: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
color: isIOS ? COLOR_TEXT_DESCRIPTION : COLOR_WHITE,
|
||||||
|
fontSize: 12,
|
||||||
|
flex: 4
|
||||||
|
},
|
||||||
|
typingUsers: {
|
||||||
|
...sharedStyles.textSemibold
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Typing = React.memo(({ usersTyping }) => {
|
||||||
|
const users = usersTyping.map(item => item.username);
|
||||||
|
let usersText;
|
||||||
|
if (!users.length) {
|
||||||
|
return null;
|
||||||
|
} else if (users.length === 2) {
|
||||||
|
usersText = users.join(` ${ I18n.t('and') } `);
|
||||||
|
} else {
|
||||||
|
usersText = users.join(', ');
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Text style={styles.typing} numberOfLines={1}>
|
||||||
|
<Text style={styles.typingUsers}>{usersText} </Text>
|
||||||
|
{ users.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing') }...
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Typing.propTypes = {
|
||||||
|
usersTyping: PropTypes.array
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = React.memo(({
|
||||||
|
prid, title, type, status, usersTyping, width, height
|
||||||
|
}) => {
|
||||||
|
const portrait = height > width;
|
||||||
|
let scale = 1;
|
||||||
|
|
||||||
|
if (!portrait) {
|
||||||
|
if (usersTyping.length > 0) {
|
||||||
|
scale = 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.titleContainer}>
|
||||||
|
<ScrollView
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
horizontal
|
||||||
|
bounces={false}
|
||||||
|
contentContainerStyle={styles.scroll}
|
||||||
|
>
|
||||||
|
<Icon type={prid ? 'discussion' : type} status={status} />
|
||||||
|
<Text style={[styles.title, { fontSize: TITLE_SIZE * scale }]} numberOfLines={1}>{title}</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
<Typing usersTyping={usersTyping} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Header.propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
width: PropTypes.number.isRequired,
|
||||||
|
height: PropTypes.number.isRequired,
|
||||||
|
prid: PropTypes.string,
|
||||||
|
status: PropTypes.string,
|
||||||
|
usersTyping: PropTypes.array
|
||||||
|
};
|
||||||
|
|
||||||
|
Header.defaultProps = {
|
||||||
|
usersTyping: []
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
|
@ -17,7 +17,8 @@ const styles = StyleSheet.create({
|
||||||
color: isIOS ? COLOR_TEXT_DESCRIPTION : COLOR_WHITE
|
color: isIOS ? COLOR_TEXT_DESCRIPTION : COLOR_WHITE
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
marginRight: 8
|
marginLeft: 4,
|
||||||
|
marginRight: 12
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -26,7 +27,14 @@ const Icon = React.memo(({ type, status }) => {
|
||||||
return <Status size={10} style={styles.status} status={status} />;
|
return <Status size={10} style={styles.status} status={status} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const icon = type === 'c' ? 'hashtag' : 'lock';
|
let icon;
|
||||||
|
if (type === 'discussion') {
|
||||||
|
icon = 'chat';
|
||||||
|
} else if (type === 'c') {
|
||||||
|
icon = 'hashtag';
|
||||||
|
} else {
|
||||||
|
icon = 'lock';
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
name={icon}
|
name={icon}
|
||||||
|
|
|
@ -1,57 +1,20 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
|
||||||
View, Text, StyleSheet, ScrollView
|
|
||||||
} from 'react-native';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { responsive } from 'react-native-responsive-ui';
|
import { responsive } from 'react-native-responsive-ui';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
|
|
||||||
import I18n from '../../../i18n';
|
import database from '../../../lib/realm';
|
||||||
import sharedStyles from '../../Styles';
|
import Header from './Header';
|
||||||
import { isIOS } from '../../../utils/deviceInfo';
|
|
||||||
import { headerIconSize } from '../../../containers/HeaderButton';
|
|
||||||
import Icon from './Icon';
|
|
||||||
import { COLOR_TEXT_DESCRIPTION, HEADER_TITLE, COLOR_WHITE } from '../../../constants/colors';
|
|
||||||
|
|
||||||
const TITLE_SIZE = 16;
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
height: '100%'
|
|
||||||
},
|
|
||||||
titleContainer: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row'
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
...sharedStyles.textSemibold,
|
|
||||||
color: HEADER_TITLE,
|
|
||||||
fontSize: TITLE_SIZE
|
|
||||||
},
|
|
||||||
scroll: {
|
|
||||||
alignItems: 'center'
|
|
||||||
},
|
|
||||||
typing: {
|
|
||||||
...sharedStyles.textRegular,
|
|
||||||
color: isIOS ? COLOR_TEXT_DESCRIPTION : COLOR_WHITE,
|
|
||||||
fontSize: 12,
|
|
||||||
marginBottom: 2
|
|
||||||
},
|
|
||||||
typingUsers: {
|
|
||||||
...sharedStyles.textSemibold
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
@responsive
|
@responsive
|
||||||
@connect((state) => {
|
@connect((state, ownProps) => {
|
||||||
let status = '';
|
let status = '';
|
||||||
let title = '';
|
const { rid, type } = ownProps;
|
||||||
const roomType = state.room.t;
|
if (type === 'd') {
|
||||||
if (roomType === 'd') {
|
|
||||||
if (state.login.user && state.login.user.id) {
|
if (state.login.user && state.login.user.id) {
|
||||||
const { id: loggedUserId } = state.login.user;
|
const { id: loggedUserId } = state.login.user;
|
||||||
const userId = state.room.rid.replace(loggedUserId, '').trim();
|
const userId = rid.replace(loggedUserId, '').trim();
|
||||||
if (userId === loggedUserId) {
|
if (userId === loggedUserId) {
|
||||||
status = state.login.user.status; // eslint-disable-line
|
status = state.login.user.status; // eslint-disable-line
|
||||||
} else {
|
} else {
|
||||||
|
@ -59,22 +22,9 @@ const styles = StyleSheet.create({
|
||||||
status = (user && user.status) || 'offline';
|
status = (user && user.status) || 'offline';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
title = state.settings.UI_Use_Real_Name ? state.room.fname : state.room.name;
|
|
||||||
} else {
|
|
||||||
title = state.room.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
let otherUsersTyping = [];
|
|
||||||
if (state.login.user && state.login.user.username) {
|
|
||||||
const { username } = state.login.user;
|
|
||||||
const { usersTyping } = state.room;
|
|
||||||
otherUsersTyping = usersTyping.filter(_username => _username !== username);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
usersTyping: otherUsersTyping,
|
|
||||||
type: roomType,
|
|
||||||
title,
|
|
||||||
status
|
status
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
@ -82,14 +32,25 @@ export default class RoomHeaderView extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
|
prid: PropTypes.string,
|
||||||
|
rid: PropTypes.string,
|
||||||
window: PropTypes.object,
|
window: PropTypes.object,
|
||||||
usersTyping: PropTypes.array,
|
|
||||||
status: PropTypes.string
|
status: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.usersTyping = database.memoryDatabase.objects('usersTyping').filtered('rid = $0', props.rid);
|
||||||
|
this.state = {
|
||||||
|
usersTyping: this.usersTyping.slice() || []
|
||||||
|
};
|
||||||
|
this.usersTyping.addListener(this.updateState);
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
const { usersTyping } = this.state;
|
||||||
const {
|
const {
|
||||||
type, title, status, usersTyping, window
|
type, title, status, window
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (nextProps.type !== type) {
|
if (nextProps.type !== type) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -106,7 +67,7 @@ export default class RoomHeaderView extends Component {
|
||||||
if (nextProps.window.height !== window.height) {
|
if (nextProps.window.height !== window.height) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!equal(nextProps.usersTyping, usersTyping)) {
|
if (!equal(nextState.usersTyping, usersTyping)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -121,53 +82,26 @@ export default class RoomHeaderView extends Component {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
get typing() {
|
updateState = () => {
|
||||||
const { usersTyping } = this.props;
|
this.setState({ usersTyping: this.usersTyping.slice() });
|
||||||
let usersText;
|
|
||||||
if (!usersTyping.length) {
|
|
||||||
return null;
|
|
||||||
} else if (usersTyping.length === 2) {
|
|
||||||
usersText = usersTyping.join(` ${ I18n.t('and') } `);
|
|
||||||
} else {
|
|
||||||
usersText = usersTyping.join(', ');
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Text style={styles.typing} numberOfLines={1}>
|
|
||||||
<Text style={styles.typingUsers}>{usersText} </Text>
|
|
||||||
{ usersTyping.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing') }...
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { usersTyping } = this.state;
|
||||||
const {
|
const {
|
||||||
window, title, usersTyping, type, status
|
window, title, type, status, prid
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const portrait = window.height > window.width;
|
|
||||||
const widthScrollView = window.width - 6.5 * headerIconSize;
|
|
||||||
let scale = 1;
|
|
||||||
|
|
||||||
if (!portrait) {
|
|
||||||
if (usersTyping.length > 0) {
|
|
||||||
scale = 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<Header
|
||||||
<View style={[styles.titleContainer, { width: widthScrollView }]}>
|
prid={prid}
|
||||||
<ScrollView
|
title={title}
|
||||||
showsHorizontalScrollIndicator={false}
|
type={type}
|
||||||
horizontal
|
status={status}
|
||||||
bounces={false}
|
width={window.width}
|
||||||
contentContainerStyle={styles.scroll}
|
height={window.height}
|
||||||
>
|
usersTyping={usersTyping}
|
||||||
<Icon type={type} status={status} />
|
/>
|
||||||
<Text style={[styles.title, { fontSize: TITLE_SIZE * scale }]} numberOfLines={1}>{title}</Text>
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
{this.typing}
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ActivityIndicator, FlatList } from 'react-native';
|
import { ActivityIndicator, FlatList, InteractionManager } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { responsive } from 'react-native-responsive-ui';
|
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
import database, { safeAddListener } from '../../lib/realm';
|
||||||
|
@ -10,33 +9,35 @@ import debounce from '../../utils/debounce';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import EmptyRoom from './EmptyRoom';
|
import EmptyRoom from './EmptyRoom';
|
||||||
import ScrollBottomButton from './ScrollBottomButton';
|
// import ScrollBottomButton from './ScrollBottomButton';
|
||||||
import { isNotch } from '../../utils/deviceInfo';
|
|
||||||
|
|
||||||
@responsive
|
|
||||||
export class List extends React.Component {
|
export class List extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onEndReached: PropTypes.func,
|
onEndReached: PropTypes.func,
|
||||||
renderFooter: PropTypes.func,
|
renderFooter: PropTypes.func,
|
||||||
renderRow: PropTypes.func,
|
renderRow: PropTypes.func,
|
||||||
room: PropTypes.object,
|
rid: PropTypes.string,
|
||||||
|
t: PropTypes.string,
|
||||||
window: PropTypes.object
|
window: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
console.time(`${ this.constructor.name } init`);
|
||||||
|
console.time(`${ this.constructor.name } mount`);
|
||||||
this.data = database
|
this.data = database
|
||||||
.objects('messages')
|
.objects('messages')
|
||||||
.filtered('rid = $0', props.room.rid)
|
.filtered('rid = $0', props.rid)
|
||||||
.sorted('ts', true);
|
.sorted('ts', true);
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: true,
|
loading: true,
|
||||||
loadingMore: false,
|
loadingMore: false,
|
||||||
end: false,
|
end: false,
|
||||||
messages: this.data.slice(),
|
messages: this.data.slice()
|
||||||
showScollToBottomButton: false
|
// showScollToBottomButton: false
|
||||||
};
|
};
|
||||||
safeAddListener(this.data, this.updateState);
|
safeAddListener(this.data, this.updateState);
|
||||||
|
console.timeEnd(`${ this.constructor.name } init`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// shouldComponentUpdate(nextProps, nextState) {
|
// shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
@ -53,14 +54,26 @@ export class List extends React.Component {
|
||||||
// || window.width !== nextProps.window.width;
|
// || window.width !== nextProps.window.width;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
console.timeEnd(`${ this.constructor.name } mount`);
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.data.removeAllListeners();
|
this.data.removeAllListeners();
|
||||||
this.updateState.stop();
|
if (this.updateState && this.updateState.stop) {
|
||||||
|
this.updateState.stop();
|
||||||
|
}
|
||||||
|
if (this.interactionManager && this.interactionManager.cancel) {
|
||||||
|
this.interactionManager.cancel();
|
||||||
|
}
|
||||||
|
console.countReset(`${ this.constructor.name }.render calls`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
updateState = debounce(() => {
|
updateState = debounce(() => {
|
||||||
this.setState({ messages: this.data.slice(), loading: false, loadingMore: false });
|
this.interactionManager = InteractionManager.runAfterInteractions(() => {
|
||||||
|
this.setState({ messages: this.data.slice(), loading: false, loadingMore: false });
|
||||||
|
});
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
onEndReached = async() => {
|
onEndReached = async() => {
|
||||||
|
@ -72,9 +85,9 @@ export class List extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ loadingMore: true });
|
this.setState({ loadingMore: true });
|
||||||
const { room } = this.props;
|
const { rid, t } = this.props;
|
||||||
try {
|
try {
|
||||||
const result = await RocketChat.loadMessagesForRoom({ rid: room.rid, t: room.t, latest: this.data[this.data.length - 1].ts });
|
const result = await RocketChat.loadMessagesForRoom({ rid, t, latest: this.data[this.data.length - 1].ts });
|
||||||
this.setState({ end: result.length < 50 });
|
this.setState({ end: result.length < 50 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({ loadingMore: false });
|
this.setState({ loadingMore: false });
|
||||||
|
@ -82,19 +95,19 @@ export class List extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToBottom = () => {
|
// scrollToBottom = () => {
|
||||||
requestAnimationFrame(() => {
|
// requestAnimationFrame(() => {
|
||||||
this.list.scrollToOffset({ offset: isNotch ? -90 : -60 });
|
// this.list.scrollToOffset({ offset: isNotch ? -90 : -60 });
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
handleScroll = (event) => {
|
// handleScroll = (event) => {
|
||||||
if (event.nativeEvent.contentOffset.y > 0) {
|
// if (event.nativeEvent.contentOffset.y > 0) {
|
||||||
this.setState({ showScollToBottomButton: true });
|
// this.setState({ showScollToBottomButton: true });
|
||||||
} else {
|
// } else {
|
||||||
this.setState({ showScollToBottomButton: false });
|
// this.setState({ showScollToBottomButton: false });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
renderFooter = () => {
|
renderFooter = () => {
|
||||||
const { loadingMore, loading } = this.state;
|
const { loadingMore, loading } = this.state;
|
||||||
|
@ -105,8 +118,9 @@ export class List extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { renderRow, window } = this.props;
|
console.count(`${ this.constructor.name }.render calls`);
|
||||||
const { showScollToBottomButton, messages } = this.state;
|
const { renderRow } = this.props;
|
||||||
|
const { messages } = this.state;
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<EmptyRoom length={messages.length} />
|
<EmptyRoom length={messages.length} />
|
||||||
|
@ -119,21 +133,22 @@ export class List extends React.Component {
|
||||||
renderItem={({ item, index }) => renderRow(item, messages[index + 1])}
|
renderItem={({ item, index }) => renderRow(item, messages[index + 1])}
|
||||||
contentContainerStyle={styles.contentContainer}
|
contentContainerStyle={styles.contentContainer}
|
||||||
style={styles.list}
|
style={styles.list}
|
||||||
onScroll={this.handleScroll}
|
// onScroll={this.handleScroll}
|
||||||
inverted
|
inverted
|
||||||
removeClippedSubviews
|
removeClippedSubviews
|
||||||
initialNumToRender={10}
|
initialNumToRender={1}
|
||||||
onEndReached={this.onEndReached}
|
onEndReached={this.onEndReached}
|
||||||
onEndReachedThreshold={0.5}
|
onEndReachedThreshold={0.5}
|
||||||
maxToRenderPerBatch={20}
|
maxToRenderPerBatch={5}
|
||||||
|
windowSize={21}
|
||||||
ListFooterComponent={this.renderFooter}
|
ListFooterComponent={this.renderFooter}
|
||||||
{...scrollPersistTaps}
|
{...scrollPersistTaps}
|
||||||
/>
|
/>
|
||||||
<ScrollBottomButton
|
{/* <ScrollBottomButton
|
||||||
show={showScollToBottomButton}
|
show={showScollToBottomButton}
|
||||||
onPress={this.scrollToBottom}
|
onPress={this.scrollToBottom}
|
||||||
landscape={window.width > window.height}
|
landscape={window.width > window.height}
|
||||||
/>
|
/> */}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
Text, View, LayoutAnimation, ActivityIndicator
|
Text, View, LayoutAnimation, InteractionManager
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
import { RectButton } from 'react-native-gesture-handler';
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import 'react-native-console-time-polyfill';
|
||||||
|
|
||||||
import { openRoom as openRoomAction, closeRoom as closeRoomAction } from '../../actions/room';
|
import {
|
||||||
import { toggleReactionPicker as toggleReactionPickerAction, actionsShow as actionsShowAction } from '../../actions/messages';
|
toggleReactionPicker as toggleReactionPickerAction,
|
||||||
|
actionsShow as actionsShowAction,
|
||||||
|
messagesRequest as messagesRequestAction,
|
||||||
|
editCancel as editCancelAction,
|
||||||
|
replyCancel as replyCancelAction
|
||||||
|
} from '../../actions/messages';
|
||||||
import LoggedView from '../View';
|
import LoggedView from '../View';
|
||||||
import { List } from './List';
|
import { List } from './List';
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
import database, { safeAddListener } from '../../lib/realm';
|
||||||
|
@ -31,6 +37,7 @@ import RoomHeaderView from './Header';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import Separator from './Separator';
|
import Separator from './Separator';
|
||||||
import { COLOR_WHITE } from '../../constants/colors';
|
import { COLOR_WHITE } from '../../constants/colors';
|
||||||
|
import debounce from '../../utils/debounce';
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
user: {
|
user: {
|
||||||
|
@ -41,29 +48,29 @@ import { COLOR_WHITE } from '../../constants/colors';
|
||||||
actionMessage: state.messages.actionMessage,
|
actionMessage: state.messages.actionMessage,
|
||||||
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',
|
||||||
|
useRealName: state.settings.UI_Use_Real_Name
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
openRoom: room => dispatch(openRoomAction(room)),
|
editCancel: () => dispatch(editCancelAction()),
|
||||||
|
replyCancel: () => dispatch(replyCancelAction()),
|
||||||
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message)),
|
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message)),
|
||||||
actionsShow: actionMessage => dispatch(actionsShowAction(actionMessage)),
|
actionsShow: actionMessage => dispatch(actionsShowAction(actionMessage)),
|
||||||
closeRoom: () => dispatch(closeRoomAction())
|
messagesRequest: room => dispatch(messagesRequestAction(room))
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class RoomView extends LoggedView {
|
export default class RoomView extends LoggedView {
|
||||||
static navigationOptions = ({ navigation }) => {
|
static navigationOptions = ({ navigation }) => {
|
||||||
const rid = navigation.getParam('rid');
|
const rid = navigation.getParam('rid');
|
||||||
|
const prid = navigation.getParam('prid');
|
||||||
|
const title = navigation.getParam('name');
|
||||||
const t = navigation.getParam('t');
|
const t = navigation.getParam('t');
|
||||||
const f = navigation.getParam('f');
|
|
||||||
const toggleFav = navigation.getParam('toggleFav', () => {});
|
|
||||||
const starIcon = f ? 'Star-filled' : 'star';
|
|
||||||
return {
|
return {
|
||||||
headerTitle: <RoomHeaderView />,
|
headerTitle: <RoomHeaderView rid={rid} prid={prid} title={title} type={t} />,
|
||||||
headerRight: t === 'l'
|
headerRight: t === 'l'
|
||||||
? null
|
? null
|
||||||
: (
|
: (
|
||||||
<CustomHeaderButtons>
|
<CustomHeaderButtons>
|
||||||
<Item title='star' iconName={starIcon} onPress={toggleFav} testID='room-view-header-star' />
|
<Item title='more' iconName='menu' onPress={() => navigation.navigate('RoomActionsView', { rid, t })} testID='room-view-header-actions' />
|
||||||
<Item title='more' iconName='menu' onPress={() => navigation.navigate('RoomActionsView', { rid })} testID='room-view-header-actions' />
|
|
||||||
</CustomHeaderButtons>
|
</CustomHeaderButtons>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -71,7 +78,6 @@ export default class RoomView extends LoggedView {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigation: PropTypes.object,
|
navigation: PropTypes.object,
|
||||||
openRoom: PropTypes.func.isRequired,
|
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
username: PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
|
@ -81,46 +87,57 @@ export default class RoomView extends LoggedView {
|
||||||
showErrorActions: PropTypes.bool,
|
showErrorActions: PropTypes.bool,
|
||||||
actionMessage: PropTypes.object,
|
actionMessage: PropTypes.object,
|
||||||
appState: PropTypes.string,
|
appState: PropTypes.string,
|
||||||
|
useRealName: PropTypes.bool,
|
||||||
toggleReactionPicker: PropTypes.func.isRequired,
|
toggleReactionPicker: PropTypes.func.isRequired,
|
||||||
actionsShow: PropTypes.func,
|
actionsShow: PropTypes.func,
|
||||||
closeRoom: PropTypes.func
|
messagesRequest: PropTypes.func,
|
||||||
|
editCancel: PropTypes.func,
|
||||||
|
replyCancel: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super('RoomView', props);
|
super('RoomView', props);
|
||||||
|
console.time(`${ this.constructor.name } init`);
|
||||||
|
console.time(`${ this.constructor.name } mount`);
|
||||||
this.rid = props.navigation.getParam('rid');
|
this.rid = props.navigation.getParam('rid');
|
||||||
|
this.t = props.navigation.getParam('t');
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
||||||
this.state = {
|
this.state = {
|
||||||
loaded: false,
|
|
||||||
joined: this.rooms.length > 0,
|
joined: this.rooms.length > 0,
|
||||||
room: {},
|
room: this.rooms[0] || { rid: this.rid, t: this.t },
|
||||||
lastOpen: null
|
lastOpen: null
|
||||||
};
|
};
|
||||||
this.beginAnimating = false;
|
this.beginAnimating = false;
|
||||||
this.onReactionPress = this.onReactionPress.bind(this);
|
this.beginAnimatingTimeout = setTimeout(() => this.beginAnimating = true, 300);
|
||||||
setTimeout(() => {
|
this.messagebox = React.createRef();
|
||||||
this.beginAnimating = true;
|
console.timeEnd(`${ this.constructor.name } init`);
|
||||||
}, 300);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { navigation } = this.props;
|
this.didMountInteraction = InteractionManager.runAfterInteractions(async() => {
|
||||||
navigation.setParams({ toggleFav: this.toggleFav });
|
const { room } = this.state;
|
||||||
|
const { messagesRequest, navigation } = this.props;
|
||||||
|
messagesRequest(room);
|
||||||
|
|
||||||
if (this.rooms.length === 0 && this.rid) {
|
// if room is joined
|
||||||
const { rid, name, t } = navigation.state.params;
|
if (room._id) {
|
||||||
this.setState(
|
navigation.setParams({ name: this.getRoomTitle(room), t: room.t });
|
||||||
{ room: { rid, name, t } },
|
this.sub = await RocketChat.subscribeRoom(room);
|
||||||
() => this.updateRoom()
|
RocketChat.readMessages(room.rid);
|
||||||
);
|
if (room.alert || room.unread || room.userMentions) {
|
||||||
}
|
this.setLastOpen(room.ls);
|
||||||
safeAddListener(this.rooms, this.updateRoom);
|
} else {
|
||||||
this.internalSetState({ loaded: true });
|
this.setLastOpen(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
safeAddListener(this.rooms, this.updateRoom);
|
||||||
|
});
|
||||||
|
console.timeEnd(`${ this.constructor.name } mount`);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const {
|
const {
|
||||||
room, loaded, joined
|
room, joined
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { showActions, showErrorActions, appState } = this.props;
|
const { showActions, showErrorActions, appState } = this.props;
|
||||||
|
|
||||||
|
@ -134,8 +151,6 @@ export default class RoomView extends LoggedView {
|
||||||
return true;
|
return true;
|
||||||
} else if (room.archived !== nextState.room.archived) {
|
} else if (room.archived !== nextState.room.archived) {
|
||||||
return true;
|
return true;
|
||||||
} else if (loaded !== nextState.loaded) {
|
|
||||||
return true;
|
|
||||||
} else if (joined !== nextState.joined) {
|
} else if (joined !== nextState.joined) {
|
||||||
return true;
|
return true;
|
||||||
} else if (showActions !== nextProps.showActions) {
|
} else if (showActions !== nextProps.showActions) {
|
||||||
|
@ -150,22 +165,46 @@ export default class RoomView extends LoggedView {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps) {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { appState, navigation } = this.props;
|
const { appState } = this.props;
|
||||||
|
|
||||||
if (prevState.room.f !== room.f) {
|
if (appState === 'foreground' && appState !== prevProps.appState) {
|
||||||
navigation.setParams({ f: room.f });
|
this.onForegroundInteraction = InteractionManager.runAfterInteractions(() => {
|
||||||
} else if (appState === 'foreground' && appState !== prevProps.appState) {
|
RocketChat.loadMissedMessages(room).catch(e => console.log(e));
|
||||||
RocketChat.loadMissedMessages(room).catch(e => console.log(e));
|
RocketChat.readMessages(room.rid).catch(e => console.log(e));
|
||||||
RocketChat.readMessages(room.rid).catch(e => console.log(e));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
const { closeRoom } = this.props;
|
if (this.messagebox && this.messagebox.current && this.messagebox.current.text) {
|
||||||
closeRoom();
|
const { text } = this.messagebox.current;
|
||||||
|
database.write(() => {
|
||||||
|
const [room] = this.rooms;
|
||||||
|
room.draftMessage = text;
|
||||||
|
});
|
||||||
|
}
|
||||||
this.rooms.removeAllListeners();
|
this.rooms.removeAllListeners();
|
||||||
|
if (this.sub && this.sub.stop) {
|
||||||
|
this.sub.stop();
|
||||||
|
}
|
||||||
|
if (this.beginAnimatingTimeout) {
|
||||||
|
clearTimeout(this.beginAnimatingTimeout);
|
||||||
|
}
|
||||||
|
const { editCancel, replyCancel } = this.props;
|
||||||
|
editCancel();
|
||||||
|
replyCancel();
|
||||||
|
if (this.didMountInteraction && this.didMountInteraction.cancel) {
|
||||||
|
this.didMountInteraction.cancel();
|
||||||
|
}
|
||||||
|
if (this.onForegroundInteraction && this.onForegroundInteraction.cancel) {
|
||||||
|
this.onForegroundInteraction.cancel();
|
||||||
|
}
|
||||||
|
if (this.updateStateInteraction && this.updateStateInteraction.cancel) {
|
||||||
|
this.updateStateInteraction.cancel();
|
||||||
|
}
|
||||||
|
console.countReset(`${ this.constructor.name }.render calls`);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessageLongPress = (message) => {
|
onMessageLongPress = (message) => {
|
||||||
|
@ -186,6 +225,13 @@ export default class RoomView extends LoggedView {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onDiscussionPress = debounce((item) => {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
navigation.push('RoomView', {
|
||||||
|
rid: item.drid, prid: item.rid, name: item.msg, t: 'p'
|
||||||
|
});
|
||||||
|
}, 1000, true)
|
||||||
|
|
||||||
internalSetState = (...args) => {
|
internalSetState = (...args) => {
|
||||||
if (isIOS && this.beginAnimating) {
|
if (isIOS && this.beginAnimating) {
|
||||||
LayoutAnimation.easeInEaseOut();
|
LayoutAnimation.easeInEaseOut();
|
||||||
|
@ -193,42 +239,11 @@ export default class RoomView extends LoggedView {
|
||||||
this.setState(...args);
|
this.setState(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
|
||||||
updateRoom = () => {
|
updateRoom = () => {
|
||||||
const { openRoom } = this.props;
|
this.updateStateInteraction = InteractionManager.runAfterInteractions(() => {
|
||||||
|
|
||||||
if (this.rooms.length > 0) {
|
|
||||||
const { room: prevRoom } = this.state;
|
|
||||||
const room = JSON.parse(JSON.stringify(this.rooms[0] || {}));
|
const room = JSON.parse(JSON.stringify(this.rooms[0] || {}));
|
||||||
this.internalSetState({ room });
|
this.internalSetState({ room });
|
||||||
|
});
|
||||||
if (!prevRoom._id) {
|
|
||||||
openRoom({
|
|
||||||
...room
|
|
||||||
});
|
|
||||||
if (room.alert || room.unread || room.userMentions) {
|
|
||||||
this.setLastOpen(room.ls);
|
|
||||||
} else {
|
|
||||||
this.setLastOpen(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const { room } = this.state;
|
|
||||||
if (room.rid) {
|
|
||||||
openRoom(room);
|
|
||||||
this.internalSetState({ joined: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleFav = () => {
|
|
||||||
try {
|
|
||||||
const { room } = this.state;
|
|
||||||
const { rid, f } = room;
|
|
||||||
RocketChat.toggleFavorite(rid, !f);
|
|
||||||
} catch (e) {
|
|
||||||
log('toggleFavorite', e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage = (message) => {
|
sendMessage = (message) => {
|
||||||
|
@ -238,6 +253,11 @@ export default class RoomView extends LoggedView {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getRoomTitle = (room) => {
|
||||||
|
const { useRealName } = this.props;
|
||||||
|
return ((room.prid || useRealName) && room.fname) || room.name;
|
||||||
|
}
|
||||||
|
|
||||||
setLastOpen = lastOpen => this.setState({ lastOpen });
|
setLastOpen = lastOpen => this.setState({ lastOpen });
|
||||||
|
|
||||||
joinRoom = async() => {
|
joinRoom = async() => {
|
||||||
|
@ -295,7 +315,7 @@ export default class RoomView extends LoggedView {
|
||||||
&& moment(item.ts).isAfter(lastOpen)
|
&& moment(item.ts).isAfter(lastOpen)
|
||||||
&& moment(previousItem.ts).isBefore(lastOpen);
|
&& moment(previousItem.ts).isBefore(lastOpen);
|
||||||
if (!moment(item.ts).isSame(previousItem.ts, 'day')) {
|
if (!moment(item.ts).isSame(previousItem.ts, 'day')) {
|
||||||
dateSeparator = previousItem.ts;
|
dateSeparator = item.ts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,6 +334,7 @@ export default class RoomView extends LoggedView {
|
||||||
_updatedAt={item._updatedAt}
|
_updatedAt={item._updatedAt}
|
||||||
onReactionPress={this.onReactionPress}
|
onReactionPress={this.onReactionPress}
|
||||||
onLongPress={this.onMessageLongPress}
|
onLongPress={this.onMessageLongPress}
|
||||||
|
onDiscussionPress={this.onDiscussionPress}
|
||||||
/>
|
/>
|
||||||
<Separator
|
<Separator
|
||||||
ts={dateSeparator}
|
ts={dateSeparator}
|
||||||
|
@ -336,12 +357,13 @@ export default class RoomView extends LoggedView {
|
||||||
_updatedAt={item._updatedAt}
|
_updatedAt={item._updatedAt}
|
||||||
onReactionPress={this.onReactionPress}
|
onReactionPress={this.onReactionPress}
|
||||||
onLongPress={this.onMessageLongPress}
|
onLongPress={this.onMessageLongPress}
|
||||||
|
onDiscussionPress={this.onDiscussionPress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFooter = () => {
|
renderFooter = () => {
|
||||||
const { joined } = this.state;
|
const { joined, room } = this.state;
|
||||||
|
|
||||||
if (!joined) {
|
if (!joined) {
|
||||||
return (
|
return (
|
||||||
|
@ -360,39 +382,34 @@ export default class RoomView extends LoggedView {
|
||||||
}
|
}
|
||||||
if (this.isReadOnly()) {
|
if (this.isReadOnly()) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.readOnly} key='room-view-read-only'>
|
<View style={styles.readOnly}>
|
||||||
<Text style={styles.previewMode}>{I18n.t('This_room_is_read_only')}</Text>
|
<Text style={styles.previewMode}>{I18n.t('This_room_is_read_only')}</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.isBlocked()) {
|
if (this.isBlocked()) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.readOnly} key='room-view-block'>
|
<View style={styles.readOnly}>
|
||||||
<Text style={styles.previewMode}>{I18n.t('This_room_is_blocked')}</Text>
|
<Text style={styles.previewMode}>{I18n.t('This_room_is_blocked')}</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <MessageBox key='room-view-messagebox' onSubmit={this.sendMessage} rid={this.rid} />;
|
return <MessageBox ref={this.messagebox} onSubmit={this.sendMessage} rid={this.rid} roomType={room.t} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderList = () => {
|
renderList = () => {
|
||||||
const { loaded, room } = this.state;
|
const { room } = this.state;
|
||||||
if (!loaded || !room.rid) {
|
const { rid, t } = room;
|
||||||
return <ActivityIndicator style={styles.loading} />;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
[
|
<React.Fragment>
|
||||||
<List
|
<List rid={rid} t={t} renderRow={this.renderItem} />
|
||||||
key='room-view-messages'
|
{this.renderFooter()}
|
||||||
room={room}
|
</React.Fragment>
|
||||||
renderRow={this.renderItem}
|
|
||||||
/>,
|
|
||||||
this.renderFooter()
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
console.count(`${ this.constructor.name }.render calls`);
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { user, showActions, showErrorActions } = this.props;
|
const { user, showActions, showErrorActions } = this.props;
|
||||||
|
|
||||||
|
|
|
@ -128,6 +128,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
chats: [],
|
chats: [],
|
||||||
unread: [],
|
unread: [],
|
||||||
favorites: [],
|
favorites: [],
|
||||||
|
discussions: [],
|
||||||
channels: [],
|
channels: [],
|
||||||
privateGroup: [],
|
privateGroup: [],
|
||||||
direct: [],
|
direct: [],
|
||||||
|
@ -188,8 +189,11 @@ export default class RoomsListView extends LoggedView {
|
||||||
}
|
}
|
||||||
if (groupByType) {
|
if (groupByType) {
|
||||||
const {
|
const {
|
||||||
channels, privateGroup, direct, livechat
|
dicussions, channels, privateGroup, direct, livechat
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
if (!isEqual(nextState.dicussions, dicussions)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (!isEqual(nextState.channels, channels)) {
|
if (!isEqual(nextState.channels, channels)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -239,6 +243,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
this.removeListener(this.data);
|
this.removeListener(this.data);
|
||||||
this.removeListener(this.unread);
|
this.removeListener(this.unread);
|
||||||
this.removeListener(this.favorites);
|
this.removeListener(this.favorites);
|
||||||
|
this.removeListener(this.discussions);
|
||||||
this.removeListener(this.channels);
|
this.removeListener(this.channels);
|
||||||
this.removeListener(this.privateGroup);
|
this.removeListener(this.privateGroup);
|
||||||
this.removeListener(this.direct);
|
this.removeListener(this.direct);
|
||||||
|
@ -268,6 +273,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
let chats = [];
|
let chats = [];
|
||||||
let unread = [];
|
let unread = [];
|
||||||
let favorites = [];
|
let favorites = [];
|
||||||
|
let discussions = [];
|
||||||
let channels = [];
|
let channels = [];
|
||||||
let privateGroup = [];
|
let privateGroup = [];
|
||||||
let direct = [];
|
let direct = [];
|
||||||
|
@ -291,22 +297,27 @@ export default class RoomsListView extends LoggedView {
|
||||||
}
|
}
|
||||||
// type
|
// type
|
||||||
if (groupByType) {
|
if (groupByType) {
|
||||||
|
// discussions
|
||||||
|
this.discussions = this.data.filtered('prid != null');
|
||||||
|
discussions = this.removeRealmInstance(this.discussions);
|
||||||
|
|
||||||
// channels
|
// channels
|
||||||
this.channels = this.data.filtered('t == $0', 'c');
|
this.channels = this.data.filtered('t == $0 AND prid == null', 'c');
|
||||||
channels = this.removeRealmInstance(this.channels);
|
channels = this.removeRealmInstance(this.channels);
|
||||||
|
|
||||||
// private
|
// private
|
||||||
this.privateGroup = this.data.filtered('t == $0', 'p');
|
this.privateGroup = this.data.filtered('t == $0 AND prid == null', 'p');
|
||||||
privateGroup = this.removeRealmInstance(this.privateGroup);
|
privateGroup = this.removeRealmInstance(this.privateGroup);
|
||||||
|
|
||||||
// direct
|
// direct
|
||||||
this.direct = this.data.filtered('t == $0', 'd');
|
this.direct = this.data.filtered('t == $0 AND prid == null', 'd');
|
||||||
direct = this.removeRealmInstance(this.direct);
|
direct = this.removeRealmInstance(this.direct);
|
||||||
|
|
||||||
// livechat
|
// livechat
|
||||||
this.livechat = this.data.filtered('t == $0', 'l');
|
this.livechat = this.data.filtered('t == $0 AND prid == null', 'l');
|
||||||
livechat = this.removeRealmInstance(this.livechat);
|
livechat = this.removeRealmInstance(this.livechat);
|
||||||
|
|
||||||
|
safeAddListener(this.discussions, debounce(() => this.internalSetState({ discussions: this.removeRealmInstance(this.discussions) }), 300));
|
||||||
safeAddListener(this.channels, debounce(() => this.internalSetState({ channels: this.removeRealmInstance(this.channels) }), 300));
|
safeAddListener(this.channels, debounce(() => this.internalSetState({ channels: this.removeRealmInstance(this.channels) }), 300));
|
||||||
safeAddListener(this.privateGroup, debounce(() => this.internalSetState({ privateGroup: this.removeRealmInstance(this.privateGroup) }), 300));
|
safeAddListener(this.privateGroup, debounce(() => this.internalSetState({ privateGroup: this.removeRealmInstance(this.privateGroup) }), 300));
|
||||||
safeAddListener(this.direct, debounce(() => this.internalSetState({ direct: this.removeRealmInstance(this.direct) }), 300));
|
safeAddListener(this.direct, debounce(() => this.internalSetState({ direct: this.removeRealmInstance(this.direct) }), 300));
|
||||||
|
@ -322,6 +333,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
chats = this.removeRealmInstance(this.chats);
|
chats = this.removeRealmInstance(this.chats);
|
||||||
|
|
||||||
safeAddListener(this.chats, debounce(() => this.internalSetState({ chats: this.removeRealmInstance(this.chats) }), 300));
|
safeAddListener(this.chats, debounce(() => this.internalSetState({ chats: this.removeRealmInstance(this.chats) }), 300));
|
||||||
|
this.removeListener(this.discussions);
|
||||||
this.removeListener(this.channels);
|
this.removeListener(this.channels);
|
||||||
this.removeListener(this.privateGroup);
|
this.removeListener(this.privateGroup);
|
||||||
this.removeListener(this.direct);
|
this.removeListener(this.direct);
|
||||||
|
@ -330,7 +342,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
|
|
||||||
// setState
|
// setState
|
||||||
this.internalSetState({
|
this.internalSetState({
|
||||||
chats, unread, favorites, channels, privateGroup, direct, livechat, loading: false
|
chats, unread, favorites, discussions, channels, privateGroup, direct, livechat, loading: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -387,16 +399,22 @@ export default class RoomsListView extends LoggedView {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
goRoom = ({ rid, name, t }) => {
|
getRoomTitle = (item) => {
|
||||||
|
const { useRealName } = this.props;
|
||||||
|
return ((item.prid || useRealName) && item.fname) || item.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
goRoom = (item) => {
|
||||||
this.cancelSearchingAndroid();
|
this.cancelSearchingAndroid();
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
navigation.navigate('RoomView', { rid, name, t });
|
navigation.navigate('RoomView', {
|
||||||
|
rid: item.rid, name: this.getRoomTitle(item), t: item.t, prid: item.prid
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPressItem = async(item = {}) => {
|
_onPressItem = async(item = {}) => {
|
||||||
if (!item.search) {
|
if (!item.search) {
|
||||||
const { rid, name, t } = item;
|
return this.goRoom(item);
|
||||||
return this.goRoom({ rid, name, t });
|
|
||||||
}
|
}
|
||||||
if (item.t === 'd') {
|
if (item.t === 'd') {
|
||||||
// if user is using the search we need first to join/create room
|
// if user is using the search we need first to join/create room
|
||||||
|
@ -410,8 +428,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
log('RoomsListView._onPressItem', e);
|
log('RoomsListView._onPressItem', e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const { rid, name, t } = item;
|
return this.goRoom(item);
|
||||||
return this.goRoom({ rid, name, t });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,7 +488,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
|
|
||||||
renderItem = ({ item }) => {
|
renderItem = ({ item }) => {
|
||||||
const {
|
const {
|
||||||
useRealName, userId, baseUrl, StoreLastMessage
|
userId, baseUrl, StoreLastMessage
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const id = item.rid.replace(userId, '').trim();
|
const id = item.rid.replace(userId, '').trim();
|
||||||
|
|
||||||
|
@ -482,12 +499,13 @@ export default class RoomsListView extends LoggedView {
|
||||||
userMentions={item.userMentions}
|
userMentions={item.userMentions}
|
||||||
favorite={item.f}
|
favorite={item.f}
|
||||||
lastMessage={item.lastMessage}
|
lastMessage={item.lastMessage}
|
||||||
name={(useRealName && item.fname) || item.name}
|
name={this.getRoomTitle(item)}
|
||||||
_updatedAt={item.roomUpdatedAt}
|
_updatedAt={item.roomUpdatedAt}
|
||||||
key={item._id}
|
key={item._id}
|
||||||
id={id}
|
id={id}
|
||||||
type={item.t}
|
type={item.t}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
|
prid={item.prid}
|
||||||
showLastMessage={StoreLastMessage}
|
showLastMessage={StoreLastMessage}
|
||||||
onPress={() => this._onPressItem(item)}
|
onPress={() => this._onPressItem(item)}
|
||||||
testID={`rooms-list-view-item-${ item.name }`}
|
testID={`rooms-list-view-item-${ item.name }`}
|
||||||
|
@ -509,7 +527,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
return null;
|
return null;
|
||||||
} else if (header === 'Favorites' && !showFavorites) {
|
} else if (header === 'Favorites' && !showFavorites) {
|
||||||
return null;
|
return null;
|
||||||
} else if (['Channels', 'Direct_Messages', 'Private_Groups', 'Livechat'].includes(header) && !groupByType) {
|
} else if (['Discussions', 'Channels', 'Direct_Messages', 'Private_Groups', 'Livechat'].includes(header) && !groupByType) {
|
||||||
return null;
|
return null;
|
||||||
} else if (header === 'Chats' && groupByType) {
|
} else if (header === 'Chats' && groupByType) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -537,7 +555,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
|
|
||||||
renderList = () => {
|
renderList = () => {
|
||||||
const {
|
const {
|
||||||
search, chats, unread, favorites, channels, direct, privateGroup, livechat
|
search, chats, unread, favorites, discussions, channels, direct, privateGroup, livechat
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
if (search.length > 0) {
|
if (search.length > 0) {
|
||||||
|
@ -562,6 +580,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
{this.renderSection(unread, 'Unread')}
|
{this.renderSection(unread, 'Unread')}
|
||||||
{this.renderSection(favorites, 'Favorites')}
|
{this.renderSection(favorites, 'Favorites')}
|
||||||
|
{this.renderSection(discussions, 'Discussions')}
|
||||||
{this.renderSection(channels, 'Channels')}
|
{this.renderSection(channels, 'Channels')}
|
||||||
{this.renderSection(direct, 'Direct_Messages')}
|
{this.renderSection(direct, 'Direct_Messages')}
|
||||||
{this.renderSection(privateGroup, 'Private_Groups')}
|
{this.renderSection(privateGroup, 'Private_Groups')}
|
||||||
|
@ -592,11 +611,10 @@ export default class RoomsListView extends LoggedView {
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
ListHeaderComponent={this.renderListHeader}
|
ListHeaderComponent={this.renderListHeader}
|
||||||
getItemLayout={getItemLayout}
|
getItemLayout={getItemLayout}
|
||||||
enableEmptySections
|
|
||||||
removeClippedSubviews
|
removeClippedSubviews
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
initialNumToRender={12}
|
initialNumToRender={9}
|
||||||
windowSize={7}
|
windowSize={9}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ export default class SearchMessagesView extends LoggedView {
|
||||||
messages: [],
|
messages: [],
|
||||||
searchText: ''
|
searchText: ''
|
||||||
};
|
};
|
||||||
|
this.rid = props.navigation.getParam('rid');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -72,12 +73,10 @@ export default class SearchMessagesView extends LoggedView {
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
search = debounce(async(searchText) => {
|
search = debounce(async(searchText) => {
|
||||||
const { navigation } = this.props;
|
|
||||||
const rid = navigation.getParam('rid');
|
|
||||||
this.setState({ searchText, loading: true, messages: [] });
|
this.setState({ searchText, loading: true, messages: [] });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await RocketChat.searchMessages(rid, searchText);
|
const result = await RocketChat.searchMessages(this.rid, searchText);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.setState({
|
this.setState({
|
||||||
messages: result.messages || [],
|
messages: result.messages || [],
|
||||||
|
|
|
@ -164,7 +164,7 @@ export default class Sidebar extends Component {
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
text={I18n.t('Chats')}
|
text={I18n.t('Chats')}
|
||||||
left={<CustomIcon name='chat' size={20} color={COLOR_TEXT} />}
|
left={<CustomIcon name='message' size={20} color={COLOR_TEXT} />}
|
||||||
onPress={() => this.sidebarNavigate('RoomsListView')}
|
onPress={() => this.sidebarNavigate('RoomsListView')}
|
||||||
testID='sidebar-chats'
|
testID='sidebar-chats'
|
||||||
current={activeItemKey === 'ChatsStack'}
|
current={activeItemKey === 'ChatsStack'}
|
||||||
|
|
|
@ -21,7 +21,6 @@ const options = [I18n.t('Unstar'), I18n.t('Cancel')];
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
customEmojis: state.customEmojis,
|
customEmojis: state.customEmojis,
|
||||||
room: state.room,
|
|
||||||
user: {
|
user: {
|
||||||
id: state.login.user && state.login.user.id,
|
id: state.login.user && state.login.user.id,
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
|
@ -38,7 +37,7 @@ export default class StarredMessagesView extends LoggedView {
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
customEmojis: PropTypes.object,
|
customEmojis: PropTypes.object,
|
||||||
room: PropTypes.object
|
navigation: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -47,6 +46,8 @@ export default class StarredMessagesView extends LoggedView {
|
||||||
loading: false,
|
loading: false,
|
||||||
messages: []
|
messages: []
|
||||||
};
|
};
|
||||||
|
this.rid = props.navigation.getParam('rid');
|
||||||
|
this.t = props.navigation.getParam('t');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -115,10 +116,9 @@ export default class StarredMessagesView extends LoggedView {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { room } = this.props;
|
|
||||||
const result = await RocketChat.getMessages(
|
const result = await RocketChat.getMessages(
|
||||||
room.rid,
|
this.rid,
|
||||||
room.t,
|
this.t,
|
||||||
{ 'starred._id': { $in: [user.id] } },
|
{ 'starred._id': { $in: [user.id] } },
|
||||||
messages.length
|
messages.length
|
||||||
);
|
);
|
||||||
|
|
|
@ -36,10 +36,6 @@ describe('Room screen', () => {
|
||||||
|
|
||||||
// Render - Header
|
// Render - Header
|
||||||
describe('Header', async() => {
|
describe('Header', async() => {
|
||||||
it('should have star button', async() => {
|
|
||||||
await expect(element(by.id('room-view-header-star'))).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have actions button ', async() => {
|
it('should have actions button ', async() => {
|
||||||
await expect(element(by.id('room-view-header-actions'))).toBeVisible();
|
await expect(element(by.id('room-view-header-actions'))).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,8 +31,8 @@
|
||||||
7A32C246206D791D001C80E9 /* Fabric.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A32C20F206D791D001C80E9 /* Fabric.framework */; };
|
7A32C246206D791D001C80E9 /* Fabric.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A32C20F206D791D001C80E9 /* Fabric.framework */; };
|
||||||
7A32C247206D791D001C80E9 /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A32C245206D791D001C80E9 /* Crashlytics.framework */; };
|
7A32C247206D791D001C80E9 /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A32C245206D791D001C80E9 /* Crashlytics.framework */; };
|
||||||
7A430E4F20238C46008F55BC /* libRCTCustomInputController.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A430E1E20238C02008F55BC /* libRCTCustomInputController.a */; };
|
7A430E4F20238C46008F55BC /* libRCTCustomInputController.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A430E1E20238C02008F55BC /* libRCTCustomInputController.a */; };
|
||||||
|
7A55F1C52236D541005109A0 /* custom.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7A55F1C42236D541005109A0 /* custom.ttf */; };
|
||||||
7A8DEB5A20ED0BEC00C5DCE4 /* libRNNotifications.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A8DEB5220ED0BDE00C5DCE4 /* libRNNotifications.a */; };
|
7A8DEB5A20ED0BEC00C5DCE4 /* libRNNotifications.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A8DEB5220ED0BDE00C5DCE4 /* libRNNotifications.a */; };
|
||||||
7A9B5BCF221F32FA00478E23 /* custom.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7A9B5BCE221F32F400478E23 /* custom.ttf */; };
|
|
||||||
7ACD4897222860DE00442C55 /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */; };
|
7ACD4897222860DE00442C55 /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */; };
|
||||||
7AFB806E205AE65700D004E7 /* libRCTToast.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AFB804C205AE63100D004E7 /* libRCTToast.a */; };
|
7AFB806E205AE65700D004E7 /* libRCTToast.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AFB804C205AE63100D004E7 /* libRCTToast.a */; };
|
||||||
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
|
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
|
||||||
|
@ -257,6 +257,13 @@
|
||||||
remoteGlobalIDString = 39DF4FE71E00394E00F5B4B2;
|
remoteGlobalIDString = 39DF4FE71E00394E00F5B4B2;
|
||||||
remoteInfo = RCTCustomInputController;
|
remoteInfo = RCTCustomInputController;
|
||||||
};
|
};
|
||||||
|
7A55F1B92236D50F005109A0 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = B1A58A7ACB0E4453A44AEC38 /* RNGestureHandler.xcodeproj */;
|
||||||
|
proxyType = 2;
|
||||||
|
remoteGlobalIDString = B5C32A36220C603B000FFB8D;
|
||||||
|
remoteInfo = "RNGestureHandler-tvOS";
|
||||||
|
};
|
||||||
7A770EC120BECDC7001AD51A /* PBXContainerItemProxy */ = {
|
7A770EC120BECDC7001AD51A /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 1845C223DA364898A8400573 /* FastImage.xcodeproj */;
|
containerPortal = 1845C223DA364898A8400573 /* FastImage.xcodeproj */;
|
||||||
|
@ -494,8 +501,8 @@
|
||||||
7A32C20F206D791D001C80E9 /* Fabric.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Fabric.framework; path = "../../../../Downloads/com.crashlytics.ios-manual/Fabric.framework"; sourceTree = "<group>"; };
|
7A32C20F206D791D001C80E9 /* Fabric.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Fabric.framework; path = "../../../../Downloads/com.crashlytics.ios-manual/Fabric.framework"; sourceTree = "<group>"; };
|
||||||
7A32C245206D791D001C80E9 /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Crashlytics.framework; path = "../../../../Downloads/com.crashlytics.ios-manual/Crashlytics.framework"; sourceTree = "<group>"; };
|
7A32C245206D791D001C80E9 /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Crashlytics.framework; path = "../../../../Downloads/com.crashlytics.ios-manual/Crashlytics.framework"; sourceTree = "<group>"; };
|
||||||
7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCustomInputController.xcodeproj; path = "../node_modules/react-native-keyboard-input/lib/ios/RCTCustomInputController.xcodeproj"; sourceTree = "<group>"; };
|
7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCustomInputController.xcodeproj; path = "../node_modules/react-native-keyboard-input/lib/ios/RCTCustomInputController.xcodeproj"; sourceTree = "<group>"; };
|
||||||
|
7A55F1C42236D541005109A0 /* custom.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = custom.ttf; sourceTree = "<group>"; };
|
||||||
7A8DEB1B20ED0BDE00C5DCE4 /* RNNotifications.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNNotifications.xcodeproj; path = "../node_modules/react-native-notifications/RNNotifications/RNNotifications.xcodeproj"; sourceTree = "<group>"; };
|
7A8DEB1B20ED0BDE00C5DCE4 /* RNNotifications.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNNotifications.xcodeproj; path = "../node_modules/react-native-notifications/RNNotifications/RNNotifications.xcodeproj"; sourceTree = "<group>"; };
|
||||||
7A9B5BCE221F32F400478E23 /* custom.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = custom.ttf; sourceTree = "<group>"; };
|
|
||||||
7ACD4853222860DE00442C55 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
7ACD4853222860DE00442C55 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||||
7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTToast.xcodeproj; path = "../node_modules/@remobile/react-native-toast/ios/RCTToast.xcodeproj"; sourceTree = "<group>"; };
|
7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTToast.xcodeproj; path = "../node_modules/@remobile/react-native-toast/ios/RCTToast.xcodeproj"; sourceTree = "<group>"; };
|
||||||
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
|
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
|
||||||
|
@ -847,7 +854,7 @@
|
||||||
AF5E16F0398347E6A80C8CBE /* Resources */ = {
|
AF5E16F0398347E6A80C8CBE /* Resources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7A9B5BCE221F32F400478E23 /* custom.ttf */,
|
7A55F1C42236D541005109A0 /* custom.ttf */,
|
||||||
);
|
);
|
||||||
name = Resources;
|
name = Resources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1297,6 +1304,13 @@
|
||||||
remoteRef = 7A430E1D20238C02008F55BC /* PBXContainerItemProxy */;
|
remoteRef = 7A430E1D20238C02008F55BC /* PBXContainerItemProxy */;
|
||||||
sourceTree = BUILT_PRODUCTS_DIR;
|
sourceTree = BUILT_PRODUCTS_DIR;
|
||||||
};
|
};
|
||||||
|
7A55F1BA2236D50F005109A0 /* libRNGestureHandler-tvOS.a */ = {
|
||||||
|
isa = PBXReferenceProxy;
|
||||||
|
fileType = archive.ar;
|
||||||
|
path = "libRNGestureHandler-tvOS.a";
|
||||||
|
remoteRef = 7A55F1B92236D50F005109A0 /* PBXContainerItemProxy */;
|
||||||
|
sourceTree = BUILT_PRODUCTS_DIR;
|
||||||
|
};
|
||||||
7A770EC220BECDC7001AD51A /* libFastImage.a */ = {
|
7A770EC220BECDC7001AD51A /* libFastImage.a */ = {
|
||||||
isa = PBXReferenceProxy;
|
isa = PBXReferenceProxy;
|
||||||
fileType = archive.ar;
|
fileType = archive.ar;
|
||||||
|
@ -1360,6 +1374,13 @@
|
||||||
remoteRef = 7AA7B71B2229AE520039764A /* PBXContainerItemProxy */;
|
remoteRef = 7AA7B71B2229AE520039764A /* PBXContainerItemProxy */;
|
||||||
sourceTree = BUILT_PRODUCTS_DIR;
|
sourceTree = BUILT_PRODUCTS_DIR;
|
||||||
};
|
};
|
||||||
|
7A9B5BC9221F2D0900478E23 /* libRNVectorIcons-tvOS.a */ = {
|
||||||
|
isa = PBXReferenceProxy;
|
||||||
|
fileType = archive.ar;
|
||||||
|
path = "libRNVectorIcons-tvOS.a";
|
||||||
|
remoteRef = 7A9B5BC8221F2D0900478E23 /* PBXContainerItemProxy */;
|
||||||
|
sourceTree = BUILT_PRODUCTS_DIR;
|
||||||
|
};
|
||||||
7ACD4880222860DE00442C55 /* libjsi.a */ = {
|
7ACD4880222860DE00442C55 /* libjsi.a */ = {
|
||||||
isa = PBXReferenceProxy;
|
isa = PBXReferenceProxy;
|
||||||
fileType = archive.ar;
|
fileType = archive.ar;
|
||||||
|
@ -1486,8 +1507,8 @@
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7A9B5BCF221F32FA00478E23 /* custom.ttf in Resources */,
|
|
||||||
7A309C9C20724870000C6B13 /* Fabric.sh in Resources */,
|
7A309C9C20724870000C6B13 /* Fabric.sh in Resources */,
|
||||||
|
7A55F1C52236D541005109A0 /* custom.ttf in Resources */,
|
||||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
BIN
ios/custom.ttf
BIN
ios/custom.ttf
Binary file not shown.
|
@ -35,6 +35,7 @@
|
||||||
"react-native": "0.58.6",
|
"react-native": "0.58.6",
|
||||||
"react-native-action-sheet": "^2.1.0",
|
"react-native-action-sheet": "^2.1.0",
|
||||||
"react-native-audio": "^4.3.0",
|
"react-native-audio": "^4.3.0",
|
||||||
|
"react-native-console-time-polyfill": "^1.2.1",
|
||||||
"react-native-device-info": "^0.25.1",
|
"react-native-device-info": "^0.25.1",
|
||||||
"react-native-dialog": "^5.5.0",
|
"react-native-dialog": "^5.5.0",
|
||||||
"react-native-fabric": "github:corymsmith/react-native-fabric#523a4edab3b2bf55ea9eeea2cf0dde82c5c29dd4",
|
"react-native-fabric": "github:corymsmith/react-native-fabric#523a4edab3b2bf55ea9eeea2cf0dde82c5c29dd4",
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
"react-native-optimized-flatlist": "^1.0.4",
|
"react-native-optimized-flatlist": "^1.0.4",
|
||||||
"react-native-orientation-locker": "^1.1.3",
|
"react-native-orientation-locker": "^1.1.3",
|
||||||
"react-native-picker-select": "^5.2.3",
|
"react-native-picker-select": "^5.2.3",
|
||||||
|
"react-native-platform-touchable": "^1.1.1",
|
||||||
"react-native-responsive-ui": "^1.1.1",
|
"react-native-responsive-ui": "^1.1.1",
|
||||||
"react-native-safari-view": "^2.1.0",
|
"react-native-safari-view": "^2.1.0",
|
||||||
"react-native-screens": "^1.0.0-alpha.22",
|
"react-native-screens": "^1.0.0-alpha.22",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ScrollView, StyleSheet } from 'react-native';
|
import { ScrollView, StyleSheet } from 'react-native';
|
||||||
|
// import moment from 'moment';
|
||||||
|
|
||||||
import MessageComponent from '../../app/containers/message/Message';
|
import MessageComponent from '../../app/containers/message/Message';
|
||||||
import StoriesSeparator from './StoriesSeparator';
|
import StoriesSeparator from './StoriesSeparator';
|
||||||
|
@ -359,6 +360,64 @@ export default (
|
||||||
<Separator title='Broadcast' />
|
<Separator title='Broadcast' />
|
||||||
<Message msg='Broadcasted message' broadcast replyBroadcast={() => alert('broadcast!')} />
|
<Message msg='Broadcasted message' broadcast replyBroadcast={() => alert('broadcast!')} />
|
||||||
|
|
||||||
|
<Separator title='Discussion' />
|
||||||
|
<Message
|
||||||
|
type='discussion-created'
|
||||||
|
drid='aisduhasidhs'
|
||||||
|
dcount={null}
|
||||||
|
dlm={null}
|
||||||
|
msg='This is a discussion'
|
||||||
|
/>
|
||||||
|
<Message
|
||||||
|
type='discussion-created'
|
||||||
|
drid='aisduhasidhs'
|
||||||
|
dcount={1}
|
||||||
|
dlm={date}
|
||||||
|
msg='This is a discussion'
|
||||||
|
/>
|
||||||
|
<Message
|
||||||
|
type='discussion-created'
|
||||||
|
drid='aisduhasidhs'
|
||||||
|
dcount={10}
|
||||||
|
dlm={date}
|
||||||
|
msg='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
|
||||||
|
/>
|
||||||
|
<Message
|
||||||
|
type='discussion-created'
|
||||||
|
drid='aisduhasidhs'
|
||||||
|
dcount={1000}
|
||||||
|
dlm={date}
|
||||||
|
msg='This is a discussion'
|
||||||
|
/>
|
||||||
|
{/* <Message
|
||||||
|
type='discussion-created'
|
||||||
|
drid='aisduhasidhs'
|
||||||
|
dcount={1000}
|
||||||
|
dlm={moment().subtract(1, 'hour')}
|
||||||
|
msg='This is a discussion'
|
||||||
|
/>
|
||||||
|
<Message
|
||||||
|
type='discussion-created'
|
||||||
|
drid='aisduhasidhs'
|
||||||
|
dcount={1000}
|
||||||
|
dlm={moment().subtract(1, 'day')}
|
||||||
|
msg='This is a discussion'
|
||||||
|
/>
|
||||||
|
<Message
|
||||||
|
type='discussion-created'
|
||||||
|
drid='aisduhasidhs'
|
||||||
|
dcount={1000}
|
||||||
|
dlm={moment().subtract(5, 'day')}
|
||||||
|
msg='This is a discussion'
|
||||||
|
/>
|
||||||
|
<Message
|
||||||
|
type='discussion-created'
|
||||||
|
drid='aisduhasidhs'
|
||||||
|
dcount={1000}
|
||||||
|
dlm={moment().subtract(30, 'day')}
|
||||||
|
msg='This is a discussion'
|
||||||
|
/> */}
|
||||||
|
|
||||||
<Separator title='Archived' />
|
<Separator title='Archived' />
|
||||||
<Message msg='This message is inside an archived room' archived />
|
<Message msg='This message is inside an archived room' archived />
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { ScrollView, View, StyleSheet } from 'react-native';
|
||||||
|
import { HeaderBackButton } from 'react-navigation';
|
||||||
|
|
||||||
|
import HeaderComponent from '../../app/views/RoomView/Header/Header';
|
||||||
|
import { CustomHeaderButtons, Item } from '../../app/containers/HeaderButton';
|
||||||
|
import { COLOR_SEPARATOR, HEADER_BACKGROUND } from '../../app/constants/colors';
|
||||||
|
import StoriesSeparator from './StoriesSeparator';
|
||||||
|
import { isIOS } from '../../app/utils/deviceInfo';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: isIOS ? 44 : 56,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderColor: COLOR_SEPARATOR,
|
||||||
|
marginVertical: 6,
|
||||||
|
backgroundColor: HEADER_BACKGROUND
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Header = props => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<HeaderBackButton />
|
||||||
|
<HeaderComponent
|
||||||
|
title='test'
|
||||||
|
type='d'
|
||||||
|
width={375}
|
||||||
|
height={480}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<CustomHeaderButtons>
|
||||||
|
<Item title='more' iconName='menu' />
|
||||||
|
</CustomHeaderButtons>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default (
|
||||||
|
<ScrollView>
|
||||||
|
<StoriesSeparator title='Basic' />
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<StoriesSeparator title='Types' />
|
||||||
|
<Header type='d' />
|
||||||
|
<Header type='c' />
|
||||||
|
<Header type='p' />
|
||||||
|
<Header type='discussion' />
|
||||||
|
|
||||||
|
<StoriesSeparator title='Typing' />
|
||||||
|
<Header usersTyping={[{ username: 'diego.mello' }]} />
|
||||||
|
<Header usersTyping={[{ username: 'diego.mello' }, { username: 'rocket.cat' }]} />
|
||||||
|
<Header usersTyping={[{ username: 'diego.mello' }, { username: 'rocket.cat' }, { username: 'detoxrn' }]} />
|
||||||
|
|
||||||
|
<StoriesSeparator title='Title scroll' />
|
||||||
|
<Header title='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' />
|
||||||
|
<Header
|
||||||
|
title='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
|
||||||
|
usersTyping={[{ username: 'diego.mello' }, { username: 'rocket.cat' }, { username: 'detoxrn' }]}
|
||||||
|
/>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
|
@ -7,6 +7,7 @@ import { storiesOf } from '@storybook/react-native';
|
||||||
import RoomItem from './RoomItem';
|
import RoomItem from './RoomItem';
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
import Message from './Message';
|
import Message from './Message';
|
||||||
|
// import RoomViewHeader from './RoomViewHeader';
|
||||||
|
|
||||||
const reducers = combineReducers({
|
const reducers = combineReducers({
|
||||||
settings: () => ({}),
|
settings: () => ({}),
|
||||||
|
@ -27,3 +28,6 @@ storiesOf('RoomItem', module)
|
||||||
.add('list', () => RoomItem);
|
.add('list', () => RoomItem);
|
||||||
storiesOf('Message', module)
|
storiesOf('Message', module)
|
||||||
.add('list', () => Message);
|
.add('list', () => Message);
|
||||||
|
// FIXME: I couldn't make these pass on jest :(
|
||||||
|
// storiesOf('RoomViewHeader', module)
|
||||||
|
// .add('list', () => RoomViewHeader);
|
||||||
|
|
|
@ -10102,6 +10102,11 @@ react-native-audio@^4.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/react-native-audio/-/react-native-audio-4.3.0.tgz#fae22b81f6a4dda706fd4837d0c6a89c66cf2e7e"
|
resolved "https://registry.yarnpkg.com/react-native-audio/-/react-native-audio-4.3.0.tgz#fae22b81f6a4dda706fd4837d0c6a89c66cf2e7e"
|
||||||
integrity sha512-QQYq28eSJy+y/Ukvry0AkbwMVELAj+LcEwCVRH+7sKLqlnoBBxGd4ilhgJHjwOiC70192LueGbjXJjPPEwW3iA==
|
integrity sha512-QQYq28eSJy+y/Ukvry0AkbwMVELAj+LcEwCVRH+7sKLqlnoBBxGd4ilhgJHjwOiC70192LueGbjXJjPPEwW3iA==
|
||||||
|
|
||||||
|
react-native-console-time-polyfill@^1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-console-time-polyfill/-/react-native-console-time-polyfill-1.2.1.tgz#3bf9a1d1d1ce3a05325fe1f2e5c4e5a1c25d910f"
|
||||||
|
integrity sha512-NKWZ1a/kzMOjPYAcCKEws2FAUnB9Eg70rbEhvdWU4Ure0H2v/ffqy47qooCZOWkrlAvlIR/sg1Apml4dzuE54A==
|
||||||
|
|
||||||
react-native-device-info@^0.25.1:
|
react-native-device-info@^0.25.1:
|
||||||
version "0.25.1"
|
version "0.25.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-device-info/-/react-native-device-info-0.25.1.tgz#bde3be9fe0e06d0c07ab5837e4fc1af90f66696b"
|
resolved "https://registry.yarnpkg.com/react-native-device-info/-/react-native-device-info-0.25.1.tgz#bde3be9fe0e06d0c07ab5837e4fc1af90f66696b"
|
||||||
|
|
Loading…
Reference in New Issue