[FIX] Avatar cache invalidation (#2311)
* [WIP] Avatar cache invalidation * [WIP] Avatar container * [IMPROVEMENT] Avatar container * [CHORE] Improve code * Allow static image on Avatar * Fix avatar changing while change username (#1583) Co-authored-by: Prateek93a <prateek93a@gmail.com> * Add default props to properly update on Sidebar and ProfileView * Fix subscribing on the wrong moment * Storyshots update * RoomItem using Avatar Component * use iife to unsubscribe from user * Use component on avatar container * RoomItem as a React.Component * Move servers models to servers folder * Avatar -> AvatarContainer * Users indexed fields * Initialize author and check if u is present * Not was found -> User not found (turn comments more relevant) * RoomItemInner -> Wrapper * Revert Avatar Touchable logic * Revert responsability of LeftButton on Tablet Mode * Prevent setState on constructor * Run avatarURL only when its not static * Add streams RC Version * Move entire add user logic to result.success * Reorder init on RoomItem * onPress as a class function * Fix roomItem using same username * Add avatar Stories * Fix pick an image from gallery on ProfileView * get avatar etag on select users of create discussion * invalidate ci cache * Fix migration * Fix sidebar avatar not updating Co-authored-by: Prateek93a <prateek93a@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
b8474286a8
commit
734039191f
File diff suppressed because it is too large
Load Diff
|
@ -1,87 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { View } from 'react-native';
|
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
|
||||||
import Touchable from 'react-native-platform-touchable';
|
|
||||||
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
|
||||||
|
|
||||||
import { avatarURL } from '../utils/avatar';
|
|
||||||
import Emoji from './markdown/Emoji';
|
|
||||||
|
|
||||||
const Avatar = React.memo(({
|
|
||||||
text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token, onPress, theme, emoji, getCustomEmoji
|
|
||||||
}) => {
|
|
||||||
const avatarStyle = {
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
borderRadius
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!text && !avatar) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uri = avatarURL({
|
|
||||||
type, text, size, userId, token, avatar, baseUrl
|
|
||||||
});
|
|
||||||
|
|
||||||
let image = emoji ? (
|
|
||||||
<Emoji
|
|
||||||
theme={theme}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
getCustomEmoji={getCustomEmoji}
|
|
||||||
isMessageContainsOnlyEmoji
|
|
||||||
literal={emoji}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<FastImage
|
|
||||||
style={avatarStyle}
|
|
||||||
source={{
|
|
||||||
uri,
|
|
||||||
headers: RocketChatSettings.customHeaders,
|
|
||||||
priority: FastImage.priority.high
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (onPress) {
|
|
||||||
image = (
|
|
||||||
<Touchable onPress={onPress}>
|
|
||||||
{image}
|
|
||||||
</Touchable>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[avatarStyle, style]}>
|
|
||||||
{image}
|
|
||||||
{children}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
Avatar.propTypes = {
|
|
||||||
baseUrl: PropTypes.string.isRequired,
|
|
||||||
style: PropTypes.any,
|
|
||||||
text: PropTypes.string,
|
|
||||||
avatar: PropTypes.string,
|
|
||||||
emoji: PropTypes.string,
|
|
||||||
size: PropTypes.number,
|
|
||||||
borderRadius: PropTypes.number,
|
|
||||||
type: PropTypes.string,
|
|
||||||
children: PropTypes.object,
|
|
||||||
userId: PropTypes.string,
|
|
||||||
token: PropTypes.string,
|
|
||||||
theme: PropTypes.string,
|
|
||||||
onPress: PropTypes.func,
|
|
||||||
getCustomEmoji: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
Avatar.defaultProps = {
|
|
||||||
text: '',
|
|
||||||
size: 25,
|
|
||||||
type: 'd',
|
|
||||||
borderRadius: 4
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Avatar;
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||||
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||||
|
|
||||||
|
import { avatarURL } from '../../utils/avatar';
|
||||||
|
import Emoji from '../markdown/Emoji';
|
||||||
|
|
||||||
|
const Avatar = React.memo(({
|
||||||
|
text,
|
||||||
|
size,
|
||||||
|
server,
|
||||||
|
borderRadius,
|
||||||
|
style,
|
||||||
|
avatar,
|
||||||
|
type,
|
||||||
|
children,
|
||||||
|
user,
|
||||||
|
onPress,
|
||||||
|
emoji,
|
||||||
|
theme,
|
||||||
|
getCustomEmoji,
|
||||||
|
avatarETag,
|
||||||
|
isStatic
|
||||||
|
}) => {
|
||||||
|
if ((!text && !avatar && !emoji) || !server) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarStyle = {
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
borderRadius
|
||||||
|
};
|
||||||
|
|
||||||
|
let image;
|
||||||
|
if (emoji) {
|
||||||
|
image = (
|
||||||
|
<Emoji
|
||||||
|
theme={theme}
|
||||||
|
baseUrl={server}
|
||||||
|
getCustomEmoji={getCustomEmoji}
|
||||||
|
isMessageContainsOnlyEmoji
|
||||||
|
literal={emoji}
|
||||||
|
style={avatarStyle}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let uri = avatar;
|
||||||
|
if (!isStatic) {
|
||||||
|
uri = avatarURL({
|
||||||
|
type,
|
||||||
|
text,
|
||||||
|
size,
|
||||||
|
user,
|
||||||
|
avatar,
|
||||||
|
server,
|
||||||
|
avatarETag
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
image = (
|
||||||
|
<FastImage
|
||||||
|
style={avatarStyle}
|
||||||
|
source={{
|
||||||
|
uri,
|
||||||
|
headers: RocketChatSettings.customHeaders,
|
||||||
|
priority: FastImage.priority.high
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onPress) {
|
||||||
|
image = (
|
||||||
|
<Touchable onPress={onPress}>
|
||||||
|
{image}
|
||||||
|
</Touchable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[avatarStyle, style]}>
|
||||||
|
{image}
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Avatar.propTypes = {
|
||||||
|
server: PropTypes.string,
|
||||||
|
style: PropTypes.any,
|
||||||
|
text: PropTypes.string,
|
||||||
|
avatar: PropTypes.string,
|
||||||
|
emoji: PropTypes.string,
|
||||||
|
size: PropTypes.number,
|
||||||
|
borderRadius: PropTypes.number,
|
||||||
|
type: PropTypes.string,
|
||||||
|
children: PropTypes.object,
|
||||||
|
user: PropTypes.shape({
|
||||||
|
id: PropTypes.string,
|
||||||
|
token: PropTypes.string
|
||||||
|
}),
|
||||||
|
theme: PropTypes.string,
|
||||||
|
onPress: PropTypes.func,
|
||||||
|
getCustomEmoji: PropTypes.func,
|
||||||
|
avatarETag: PropTypes.string,
|
||||||
|
isStatic: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
Avatar.defaultProps = {
|
||||||
|
text: '',
|
||||||
|
size: 25,
|
||||||
|
type: 'd',
|
||||||
|
borderRadius: 4
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Avatar;
|
|
@ -0,0 +1,82 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
|
import database from '../../lib/database';
|
||||||
|
import { getUserSelector } from '../../selectors/login';
|
||||||
|
import Avatar from './Avatar';
|
||||||
|
|
||||||
|
class AvatarContainer extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
text: PropTypes.string,
|
||||||
|
type: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
text: '',
|
||||||
|
type: 'd'
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.mounted = false;
|
||||||
|
this.state = { avatarETag: '' };
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.userSubscription?.unsubscribe) {
|
||||||
|
this.userSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDirect() {
|
||||||
|
const { type } = this.props;
|
||||||
|
return type === 'd';
|
||||||
|
}
|
||||||
|
|
||||||
|
init = async() => {
|
||||||
|
if (this.isDirect) {
|
||||||
|
const { text } = this.props;
|
||||||
|
const db = database.active;
|
||||||
|
const usersCollection = db.collections.get('users');
|
||||||
|
try {
|
||||||
|
const [user] = await usersCollection.query(Q.where('username', text)).fetch();
|
||||||
|
if (user) {
|
||||||
|
const observable = user.observe();
|
||||||
|
this.userSubscription = observable.subscribe((u) => {
|
||||||
|
const { avatarETag } = u;
|
||||||
|
if (this.mounted) {
|
||||||
|
this.setState({ avatarETag });
|
||||||
|
} else {
|
||||||
|
this.state.avatarETag = avatarETag;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// User was not found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { avatarETag } = this.state;
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
avatarETag={avatarETag}
|
||||||
|
{...this.props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
user: getUserSelector(state),
|
||||||
|
server: state.share.server || state.server.server
|
||||||
|
});
|
||||||
|
export default connect(mapStateToProps)(AvatarContainer);
|
|
@ -11,7 +11,6 @@ import { CustomIcon } from '../../lib/Icons';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
|
||||||
import { ROW_HEIGHT } from '../../presentation/RoomItem';
|
import { ROW_HEIGHT } from '../../presentation/RoomItem';
|
||||||
import { goRoom } from '../../utils/goRoom';
|
import { goRoom } from '../../utils/goRoom';
|
||||||
import Navigation from '../../lib/Navigation';
|
import Navigation from '../../lib/Navigation';
|
||||||
|
@ -65,14 +64,11 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
const hideNotification = () => Notifier.hideNotification();
|
const hideNotification = () => Notifier.hideNotification();
|
||||||
|
|
||||||
const NotifierComponent = React.memo(({
|
const NotifierComponent = React.memo(({ notification, isMasterDetail }) => {
|
||||||
baseUrl, user, notification, isMasterDetail
|
|
||||||
}) => {
|
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { isLandscape } = useOrientation();
|
const { isLandscape } = useOrientation();
|
||||||
|
|
||||||
const { id: userId, token } = user;
|
|
||||||
const { text, payload } = notification;
|
const { text, payload } = notification;
|
||||||
const { type } = payload;
|
const { type } = payload;
|
||||||
const name = type === 'd' ? payload.sender.username : payload.name;
|
const name = type === 'd' ? payload.sender.username : payload.name;
|
||||||
|
@ -115,7 +111,7 @@ const NotifierComponent = React.memo(({
|
||||||
background={Touchable.SelectableBackgroundBorderless()}
|
background={Touchable.SelectableBackgroundBorderless()}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<Avatar text={avatar} size={AVATAR_SIZE} type={type} baseUrl={baseUrl} style={styles.avatar} userId={userId} token={token} />
|
<Avatar text={avatar} size={AVATAR_SIZE} type={type} style={styles.avatar} />
|
||||||
<View style={styles.inner}>
|
<View style={styles.inner}>
|
||||||
<Text style={[styles.roomName, { color: themes[theme].titleText }]} numberOfLines={1}>{title}</Text>
|
<Text style={[styles.roomName, { color: themes[theme].titleText }]} numberOfLines={1}>{title}</Text>
|
||||||
<Text style={[styles.message, { color: themes[theme].titleText }]} numberOfLines={1}>{text}</Text>
|
<Text style={[styles.message, { color: themes[theme].titleText }]} numberOfLines={1}>{text}</Text>
|
||||||
|
@ -134,15 +130,11 @@ const NotifierComponent = React.memo(({
|
||||||
});
|
});
|
||||||
|
|
||||||
NotifierComponent.propTypes = {
|
NotifierComponent.propTypes = {
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
user: PropTypes.object,
|
|
||||||
notification: PropTypes.object,
|
notification: PropTypes.object,
|
||||||
isMasterDetail: PropTypes.bool
|
isMasterDetail: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
user: getUserSelector(state),
|
|
||||||
baseUrl: state.server.server,
|
|
||||||
isMasterDetail: state.app.isMasterDetail
|
isMasterDetail: state.app.isMasterDetail
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ const MentionItem = ({
|
||||||
item, trackingType, theme
|
item, trackingType, theme
|
||||||
}) => {
|
}) => {
|
||||||
const context = useContext(MessageboxContext);
|
const context = useContext(MessageboxContext);
|
||||||
const { baseUrl, user, onPressMention } = context;
|
const { onPressMention } = context;
|
||||||
|
|
||||||
const defineTestID = (type) => {
|
const defineTestID = (type) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -43,9 +43,6 @@ const MentionItem = ({
|
||||||
text={item.username || item.name}
|
text={item.username || item.name}
|
||||||
size={30}
|
size={30}
|
||||||
type={item.t}
|
type={item.t}
|
||||||
baseUrl={baseUrl}
|
|
||||||
userId={user.id}
|
|
||||||
token={user.token}
|
|
||||||
/>
|
/>
|
||||||
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{ item.username || item.name || item }</Text>
|
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{ item.username || item.name || item }</Text>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { themes } from '../../constants/colors';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
const Emoji = React.memo(({
|
const Emoji = React.memo(({
|
||||||
literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis = true, style = [], theme
|
literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis = true, style = {}, theme
|
||||||
}) => {
|
}) => {
|
||||||
const emojiUnicode = shortnameToUnicode(literal);
|
const emojiUnicode = shortnameToUnicode(literal);
|
||||||
const emoji = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, ''));
|
const emoji = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, ''));
|
||||||
|
@ -17,7 +17,10 @@ const Emoji = React.memo(({
|
||||||
return (
|
return (
|
||||||
<CustomEmoji
|
<CustomEmoji
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
style={isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji}
|
style={[
|
||||||
|
isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji,
|
||||||
|
style
|
||||||
|
]}
|
||||||
emoji={emoji}
|
emoji={emoji}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -27,7 +30,7 @@ const Emoji = React.memo(({
|
||||||
style={[
|
style={[
|
||||||
{ color: themes[theme].bodyText },
|
{ color: themes[theme].bodyText },
|
||||||
isMessageContainsOnlyEmoji ? styles.textBig : styles.text,
|
isMessageContainsOnlyEmoji ? styles.textBig : styles.text,
|
||||||
...style
|
style
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{emojiUnicode}
|
{emojiUnicode}
|
||||||
|
@ -41,7 +44,7 @@ Emoji.propTypes = {
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
customEmojis: PropTypes.bool,
|
customEmojis: PropTypes.bool,
|
||||||
style: PropTypes.array,
|
style: PropTypes.object,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar/Avatar';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
|
|
||||||
|
@ -22,11 +22,11 @@ const MessageAvatar = React.memo(({
|
||||||
borderRadius={small ? 2 : 4}
|
borderRadius={small ? 2 : 4}
|
||||||
onPress={author._id === user.id ? undefined : () => navToRoomInfo(navParam)}
|
onPress={author._id === user.id ? undefined : () => navToRoomInfo(navParam)}
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
|
user={user}
|
||||||
|
server={baseUrl}
|
||||||
|
avatarETag={author.avatarETag}
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
emoji={emoji}
|
emoji={emoji}
|
||||||
baseUrl={baseUrl}
|
|
||||||
userId={user.id}
|
|
||||||
token={user.token}
|
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,9 +6,10 @@ import Message from './Message';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
|
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
|
||||||
|
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants';
|
||||||
import messagesStatus from '../../constants/messagesStatus';
|
import messagesStatus from '../../constants/messagesStatus';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants';
|
import database from '../../lib/database';
|
||||||
|
|
||||||
class MessageContainer extends React.Component {
|
class MessageContainer extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -72,7 +73,11 @@ class MessageContainer extends React.Component {
|
||||||
theme: 'light'
|
theme: 'light'
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
state = {
|
||||||
|
author: null
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
const { item } = this.props;
|
const { item } = this.props;
|
||||||
if (item && item.observe) {
|
if (item && item.observe) {
|
||||||
const observable = item.observe();
|
const observable = item.observe();
|
||||||
|
@ -80,6 +85,19 @@ class MessageContainer extends React.Component {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const db = database.active;
|
||||||
|
const usersCollection = db.collections.get('users');
|
||||||
|
try {
|
||||||
|
const user = await usersCollection.find(item.u?._id);
|
||||||
|
const observable = user.observe();
|
||||||
|
this.userSubscription = observable.subscribe((author) => {
|
||||||
|
this.setState({ author });
|
||||||
|
this.forceUpdate();
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
|
@ -94,6 +112,9 @@ class MessageContainer extends React.Component {
|
||||||
if (this.subscription && this.subscription.unsubscribe) {
|
if (this.subscription && this.subscription.unsubscribe) {
|
||||||
this.subscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
if (this.userSubscription && this.userSubscription.unsubscribe) {
|
||||||
|
this.userSubscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onPress = debounce(() => {
|
onPress = debounce(() => {
|
||||||
|
@ -242,6 +263,7 @@ class MessageContainer extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { author } = this.state;
|
||||||
const {
|
const {
|
||||||
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, showAttachment, timeFormat, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom, callJitsi, blockAction, rid, theme
|
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, showAttachment, timeFormat, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom, callJitsi, blockAction, rid, theme
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -276,7 +298,7 @@ class MessageContainer extends React.Component {
|
||||||
id={id}
|
id={id}
|
||||||
msg={message}
|
msg={message}
|
||||||
rid={rid}
|
rid={rid}
|
||||||
author={u}
|
author={author || u}
|
||||||
ts={ts}
|
ts={ts}
|
||||||
type={t}
|
type={t}
|
||||||
attachments={attachments}
|
attachments={attachments}
|
||||||
|
|
|
@ -15,15 +15,16 @@ import Role from './model/Role';
|
||||||
import Permission from './model/Permission';
|
import Permission from './model/Permission';
|
||||||
import SlashCommand from './model/SlashCommand';
|
import SlashCommand from './model/SlashCommand';
|
||||||
import User from './model/User';
|
import User from './model/User';
|
||||||
import Server from './model/Server';
|
|
||||||
|
import LoggedUser from './model/servers/User';
|
||||||
|
import Server from './model/servers/Server';
|
||||||
import ServersHistory from './model/ServersHistory';
|
import ServersHistory from './model/ServersHistory';
|
||||||
|
|
||||||
import serversSchema from './schema/servers';
|
import serversSchema from './schema/servers';
|
||||||
import appSchema from './schema/app';
|
import appSchema from './schema/app';
|
||||||
|
|
||||||
import migrations from './model/migrations';
|
import migrations from './model/migrations';
|
||||||
|
import serversMigrations from './model/servers/migrations';
|
||||||
import serversMigrations from './model/serversMigrations';
|
|
||||||
|
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
import { isIOS } from '../../utils/deviceInfo';
|
||||||
import appGroup from '../../utils/appGroup';
|
import appGroup from '../../utils/appGroup';
|
||||||
|
@ -58,7 +59,8 @@ export const getDatabase = (database = '') => {
|
||||||
Setting,
|
Setting,
|
||||||
Role,
|
Role,
|
||||||
Permission,
|
Permission,
|
||||||
SlashCommand
|
SlashCommand,
|
||||||
|
User
|
||||||
],
|
],
|
||||||
actionsEnabled: true
|
actionsEnabled: true
|
||||||
});
|
});
|
||||||
|
@ -72,7 +74,7 @@ class DB {
|
||||||
schema: serversSchema,
|
schema: serversSchema,
|
||||||
migrations: serversMigrations
|
migrations: serversMigrations
|
||||||
}),
|
}),
|
||||||
modelClasses: [Server, User, ServersHistory],
|
modelClasses: [Server, LoggedUser, ServersHistory],
|
||||||
actionsEnabled: true
|
actionsEnabled: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -113,7 +115,8 @@ class DB {
|
||||||
Upload,
|
Upload,
|
||||||
Permission,
|
Permission,
|
||||||
CustomEmoji,
|
CustomEmoji,
|
||||||
FrequentlyUsedEmoji
|
FrequentlyUsedEmoji,
|
||||||
|
User
|
||||||
],
|
],
|
||||||
actionsEnabled: true
|
actionsEnabled: true
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,17 +6,13 @@ import { sanitizer } from '../utils';
|
||||||
export default class User extends Model {
|
export default class User extends Model {
|
||||||
static table = 'users';
|
static table = 'users';
|
||||||
|
|
||||||
@field('token') token;
|
@field('_id') _id;
|
||||||
|
|
||||||
@field('username') username;
|
|
||||||
|
|
||||||
@field('name') name;
|
@field('name') name;
|
||||||
|
|
||||||
@field('language') language;
|
@field('username') username;
|
||||||
|
|
||||||
@field('status') status;
|
@field('avatar_etag') avatarETag;
|
||||||
|
|
||||||
@field('statusText') statusText;
|
|
||||||
|
|
||||||
@field('login_email_password') loginEmailPassword;
|
@field('login_email_password') loginEmailPassword;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { schemaMigrations, addColumns } from '@nozbe/watermelondb/Schema/migrations';
|
import { schemaMigrations, addColumns, createTable } from '@nozbe/watermelondb/Schema/migrations';
|
||||||
|
|
||||||
export default schemaMigrations({
|
export default schemaMigrations({
|
||||||
migrations: [
|
migrations: [
|
||||||
|
@ -166,6 +166,20 @@ export default schemaMigrations({
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toVersion: 11,
|
||||||
|
steps: [
|
||||||
|
createTable({
|
||||||
|
name: 'users',
|
||||||
|
columns: [
|
||||||
|
{ name: '_id', type: 'string', isIndexed: true },
|
||||||
|
{ name: 'name', type: 'string', isOptional: true },
|
||||||
|
{ name: 'username', type: 'string', isIndexed: true },
|
||||||
|
{ name: 'avatar_etag', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import { field, json } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
import { sanitizer } from '../../utils';
|
||||||
|
|
||||||
|
export default class User extends Model {
|
||||||
|
static table = 'users';
|
||||||
|
|
||||||
|
@field('token') token;
|
||||||
|
|
||||||
|
@field('username') username;
|
||||||
|
|
||||||
|
@field('name') name;
|
||||||
|
|
||||||
|
@field('language') language;
|
||||||
|
|
||||||
|
@field('status') status;
|
||||||
|
|
||||||
|
@field('statusText') statusText;
|
||||||
|
|
||||||
|
@json('roles', sanitizer) roles;
|
||||||
|
|
||||||
|
@field('avatar_etag') avatarETag;
|
||||||
|
}
|
|
@ -83,6 +83,17 @@ export default schemaMigrations({
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toVersion: 10,
|
||||||
|
steps: [
|
||||||
|
addColumns({
|
||||||
|
table: 'users',
|
||||||
|
columns: [
|
||||||
|
{ name: 'avatar_etag', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
|
@ -1,7 +1,7 @@
|
||||||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
export default appSchema({
|
export default appSchema({
|
||||||
version: 10,
|
version: 11,
|
||||||
tables: [
|
tables: [
|
||||||
tableSchema({
|
tableSchema({
|
||||||
name: 'subscriptions',
|
name: 'subscriptions',
|
||||||
|
@ -247,6 +247,15 @@ export default appSchema({
|
||||||
{ name: 'provides_preview', type: 'boolean', isOptional: true },
|
{ name: 'provides_preview', type: 'boolean', isOptional: true },
|
||||||
{ name: 'app_id', type: 'string', isOptional: true }
|
{ name: 'app_id', type: 'string', isOptional: true }
|
||||||
]
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'users',
|
||||||
|
columns: [
|
||||||
|
{ name: '_id', type: 'string', isIndexed: true },
|
||||||
|
{ name: 'name', type: 'string', isOptional: true },
|
||||||
|
{ name: 'username', type: 'string', isIndexed: true },
|
||||||
|
{ name: 'avatar_etag', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
export default appSchema({
|
export default appSchema({
|
||||||
version: 9,
|
version: 10,
|
||||||
tables: [
|
tables: [
|
||||||
tableSchema({
|
tableSchema({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
|
@ -13,7 +13,8 @@ export default appSchema({
|
||||||
{ name: 'status', type: 'string', isOptional: true },
|
{ name: 'status', type: 'string', isOptional: true },
|
||||||
{ name: 'statusText', type: 'string', isOptional: true },
|
{ name: 'statusText', type: 'string', isOptional: true },
|
||||||
{ name: 'roles', type: 'string', isOptional: true },
|
{ name: 'roles', type: 'string', isOptional: true },
|
||||||
{ name: 'login_email_password', type: 'boolean', isOptional: true }
|
{ name: 'login_email_password', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'avatar_etag', type: 'string', isOptional: true }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
tableSchema({
|
tableSchema({
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { InteractionManager } from 'react-native';
|
import { InteractionManager } from 'react-native';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
|
||||||
import reduxStore from '../createStore';
|
import reduxStore from '../createStore';
|
||||||
import { setActiveUsers } from '../../actions/activeUsers';
|
import { setActiveUsers } from '../../actions/activeUsers';
|
||||||
import { setUser } from '../../actions/login';
|
import { setUser } from '../../actions/login';
|
||||||
|
import database from '../database';
|
||||||
|
|
||||||
export function subscribeUsersPresence() {
|
export function subscribeUsersPresence() {
|
||||||
const serverVersion = reduxStore.getState().server.version;
|
const serverVersion = reduxStore.getState().server.version;
|
||||||
|
@ -20,6 +22,11 @@ export function subscribeUsersPresence() {
|
||||||
} else {
|
} else {
|
||||||
this.sdk.subscribe('stream-notify-logged', 'user-status');
|
this.sdk.subscribe('stream-notify-logged', 'user-status');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RC 0.49.1
|
||||||
|
this.sdk.subscribe('stream-notify-logged', 'updateAvatar');
|
||||||
|
// RC 0.58.0
|
||||||
|
this.sdk.subscribe('stream-notify-logged', 'Users:NameChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
let ids = [];
|
let ids = [];
|
||||||
|
@ -46,7 +53,9 @@ export default async function getUsersPresence() {
|
||||||
// RC 1.1.0
|
// RC 1.1.0
|
||||||
const result = await this.sdk.get('users.presence', params);
|
const result = await this.sdk.get('users.presence', params);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const activeUsers = result.users.reduce((ret, item) => {
|
const { users } = result;
|
||||||
|
|
||||||
|
const activeUsers = users.reduce((ret, item) => {
|
||||||
const { _id, status, statusText } = item;
|
const { _id, status, statusText } = item;
|
||||||
|
|
||||||
if (loggedUser && loggedUser.id === _id) {
|
if (loggedUser && loggedUser.id === _id) {
|
||||||
|
@ -60,6 +69,27 @@ export default async function getUsersPresence() {
|
||||||
reduxStore.dispatch(setActiveUsers(activeUsers));
|
reduxStore.dispatch(setActiveUsers(activeUsers));
|
||||||
});
|
});
|
||||||
ids = [];
|
ids = [];
|
||||||
|
|
||||||
|
const db = database.active;
|
||||||
|
const userCollection = db.collections.get('users');
|
||||||
|
users.forEach(async(user) => {
|
||||||
|
try {
|
||||||
|
const userRecord = await userCollection.find(user._id);
|
||||||
|
await db.action(async() => {
|
||||||
|
await userRecord.update((u) => {
|
||||||
|
Object.assign(u, user);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// User not found
|
||||||
|
await db.action(async() => {
|
||||||
|
await userCollection.create((u) => {
|
||||||
|
u._raw = sanitizedRaw({ id: user._id }, userCollection.schema);
|
||||||
|
Object.assign(u, user);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@ -80,5 +110,7 @@ export function getUserPresence(uid) {
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
ids.push(uid);
|
if (uid) {
|
||||||
|
ids.push(uid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
} from '@rocket.chat/sdk';
|
} from '@rocket.chat/sdk';
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
import AsyncStorage from '@react-native-community/async-storage';
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
import RNFetchBlob from 'rn-fetch-blob';
|
import RNFetchBlob from 'rn-fetch-blob';
|
||||||
|
|
||||||
import reduxStore from './createStore';
|
import reduxStore from './createStore';
|
||||||
|
@ -244,9 +245,9 @@ const RocketChat = {
|
||||||
|
|
||||||
this.usersListener = this.sdk.onStreamData('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage)));
|
this.usersListener = this.sdk.onStreamData('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage)));
|
||||||
|
|
||||||
this.notifyLoggedListener = this.sdk.onStreamData('stream-notify-logged', protectedFunction((ddpMessage) => {
|
this.notifyLoggedListener = this.sdk.onStreamData('stream-notify-logged', protectedFunction(async(ddpMessage) => {
|
||||||
const { eventName } = ddpMessage.fields;
|
const { eventName } = ddpMessage.fields;
|
||||||
if (eventName === 'user-status') {
|
if (/user-status/.test(eventName)) {
|
||||||
this.activeUsers = this.activeUsers || {};
|
this.activeUsers = this.activeUsers || {};
|
||||||
if (!this._setUserTimer) {
|
if (!this._setUserTimer) {
|
||||||
this._setUserTimer = setTimeout(() => {
|
this._setUserTimer = setTimeout(() => {
|
||||||
|
@ -266,6 +267,40 @@ const RocketChat = {
|
||||||
if (loggedUser && loggedUser.id === id) {
|
if (loggedUser && loggedUser.id === id) {
|
||||||
reduxStore.dispatch(setUser({ status: STATUSES[status], statusText }));
|
reduxStore.dispatch(setUser({ status: STATUSES[status], statusText }));
|
||||||
}
|
}
|
||||||
|
} else if (/updateAvatar/.test(eventName)) {
|
||||||
|
const { username, etag } = ddpMessage.fields.args[0];
|
||||||
|
const db = database.active;
|
||||||
|
const userCollection = db.collections.get('users');
|
||||||
|
try {
|
||||||
|
const [userRecord] = await userCollection.query(Q.where('username', Q.eq(username))).fetch();
|
||||||
|
await db.action(async() => {
|
||||||
|
await userRecord.update((u) => {
|
||||||
|
u.avatarETag = etag;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// We can't create a new record since we don't receive the user._id
|
||||||
|
}
|
||||||
|
} else if (/Users:NameChanged/.test(eventName)) {
|
||||||
|
const userNameChanged = ddpMessage.fields.args[0];
|
||||||
|
const db = database.active;
|
||||||
|
const userCollection = db.collections.get('users');
|
||||||
|
try {
|
||||||
|
const userRecord = await userCollection.find(userNameChanged._id);
|
||||||
|
await db.action(async() => {
|
||||||
|
await userRecord.update((u) => {
|
||||||
|
Object.assign(u, userNameChanged);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// User not found
|
||||||
|
await db.action(async() => {
|
||||||
|
await userCollection.create((u) => {
|
||||||
|
u._raw = sanitizedRaw({ id: userNameChanged._id }, userCollection.schema);
|
||||||
|
Object.assign(u, userNameChanged);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -441,6 +476,7 @@ const RocketChat = {
|
||||||
statusLivechat: result.me.statusLivechat,
|
statusLivechat: result.me.statusLivechat,
|
||||||
emails: result.me.emails,
|
emails: result.me.emails,
|
||||||
roles: result.me.roles,
|
roles: result.me.roles,
|
||||||
|
avatarETag: result.me.avatarETag,
|
||||||
loginEmailPassword
|
loginEmailPassword
|
||||||
};
|
};
|
||||||
return user;
|
return user;
|
||||||
|
|
|
@ -18,7 +18,7 @@ const DirectoryItemLabel = React.memo(({ text, theme }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const DirectoryItem = ({
|
const DirectoryItem = ({
|
||||||
title, description, avatar, onPress, testID, style, baseUrl, user, rightLabel, type, theme
|
title, description, avatar, onPress, testID, style, rightLabel, type, theme
|
||||||
}) => (
|
}) => (
|
||||||
<Touch
|
<Touch
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
|
@ -32,9 +32,6 @@ const DirectoryItem = ({
|
||||||
size={30}
|
size={30}
|
||||||
type={type}
|
type={type}
|
||||||
style={styles.directoryItemAvatar}
|
style={styles.directoryItemAvatar}
|
||||||
baseUrl={baseUrl}
|
|
||||||
userId={user.id}
|
|
||||||
token={user.token}
|
|
||||||
/>
|
/>
|
||||||
<View style={styles.directoryItemTextContainer}>
|
<View style={styles.directoryItemTextContainer}>
|
||||||
<View style={styles.directoryItemTextTitle}>
|
<View style={styles.directoryItemTextTitle}>
|
||||||
|
@ -53,11 +50,6 @@ DirectoryItem.propTypes = {
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
avatar: PropTypes.string,
|
avatar: PropTypes.string,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
user: PropTypes.shape({
|
|
||||||
id: PropTypes.string,
|
|
||||||
token: PropTypes.string
|
|
||||||
}),
|
|
||||||
baseUrl: PropTypes.string.isRequired,
|
|
||||||
onPress: PropTypes.func.isRequired,
|
onPress: PropTypes.func.isRequired,
|
||||||
testID: PropTypes.string.isRequired,
|
testID: PropTypes.string.isRequired,
|
||||||
style: PropTypes.any,
|
style: PropTypes.any,
|
||||||
|
|
|
@ -45,7 +45,8 @@ const RoomItem = ({
|
||||||
onPress,
|
onPress,
|
||||||
toggleFav,
|
toggleFav,
|
||||||
toggleRead,
|
toggleRead,
|
||||||
hideChannel
|
hideChannel,
|
||||||
|
avatarETag
|
||||||
}) => (
|
}) => (
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
|
@ -66,6 +67,7 @@ const RoomItem = ({
|
||||||
accessibilityLabel={accessibilityLabel}
|
accessibilityLabel={accessibilityLabel}
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
avatarSize={avatarSize}
|
avatarSize={avatarSize}
|
||||||
|
avatarETag={avatarETag}
|
||||||
type={type}
|
type={type}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
|
@ -178,7 +180,8 @@ RoomItem.propTypes = {
|
||||||
toggleFav: PropTypes.func,
|
toggleFav: PropTypes.func,
|
||||||
toggleRead: PropTypes.func,
|
toggleRead: PropTypes.func,
|
||||||
onPress: PropTypes.func,
|
onPress: PropTypes.func,
|
||||||
hideChannel: PropTypes.func
|
hideChannel: PropTypes.func,
|
||||||
|
avatarETag: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
RoomItem.defaultProps = {
|
RoomItem.defaultProps = {
|
||||||
|
|
|
@ -4,12 +4,13 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar/Avatar';
|
||||||
|
|
||||||
const RoomItemInner = ({
|
const Wrapper = ({
|
||||||
accessibilityLabel,
|
accessibilityLabel,
|
||||||
avatar,
|
avatar,
|
||||||
avatarSize,
|
avatarSize,
|
||||||
|
avatarETag,
|
||||||
type,
|
type,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
userId,
|
userId,
|
||||||
|
@ -25,10 +26,10 @@ const RoomItemInner = ({
|
||||||
text={avatar}
|
text={avatar}
|
||||||
size={avatarSize}
|
size={avatarSize}
|
||||||
type={type}
|
type={type}
|
||||||
baseUrl={baseUrl}
|
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
userId={userId}
|
server={baseUrl}
|
||||||
token={token}
|
user={{ id: userId, token }}
|
||||||
|
avatarETag={avatarETag}
|
||||||
/>
|
/>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
|
@ -43,10 +44,11 @@ const RoomItemInner = ({
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
RoomItemInner.propTypes = {
|
Wrapper.propTypes = {
|
||||||
accessibilityLabel: PropTypes.string,
|
accessibilityLabel: PropTypes.string,
|
||||||
avatar: PropTypes.string,
|
avatar: PropTypes.string,
|
||||||
avatarSize: PropTypes.number,
|
avatarSize: PropTypes.number,
|
||||||
|
avatarETag: PropTypes.string,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
userId: PropTypes.string,
|
userId: PropTypes.string,
|
||||||
|
@ -55,4 +57,4 @@ RoomItemInner.propTypes = {
|
||||||
children: PropTypes.element
|
children: PropTypes.element
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RoomItemInner;
|
export default Wrapper;
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { ROW_HEIGHT } from './styles';
|
import { ROW_HEIGHT } from './styles';
|
||||||
import { formatDate } from '../../utils/room';
|
import { formatDate } from '../../utils/room';
|
||||||
|
import database from '../../lib/database';
|
||||||
import RoomItem from './RoomItem';
|
import RoomItem from './RoomItem';
|
||||||
|
|
||||||
export { ROW_HEIGHT };
|
export { ROW_HEIGHT };
|
||||||
|
@ -19,156 +20,212 @@ const attrs = [
|
||||||
'showLastMessage'
|
'showLastMessage'
|
||||||
];
|
];
|
||||||
|
|
||||||
const arePropsEqual = (oldProps, newProps) => attrs.every(key => oldProps[key] === newProps[key]);
|
class RoomItemContainer extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
item: PropTypes.object.isRequired,
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
showLastMessage: PropTypes.bool,
|
||||||
|
id: PropTypes.string,
|
||||||
|
onPress: PropTypes.func,
|
||||||
|
userId: PropTypes.string,
|
||||||
|
username: PropTypes.string,
|
||||||
|
token: PropTypes.string,
|
||||||
|
avatarSize: PropTypes.number,
|
||||||
|
testID: PropTypes.string,
|
||||||
|
width: PropTypes.number,
|
||||||
|
status: PropTypes.string,
|
||||||
|
toggleFav: PropTypes.func,
|
||||||
|
toggleRead: PropTypes.func,
|
||||||
|
hideChannel: PropTypes.func,
|
||||||
|
useRealName: PropTypes.bool,
|
||||||
|
getUserPresence: PropTypes.func,
|
||||||
|
connected: PropTypes.bool,
|
||||||
|
theme: PropTypes.string,
|
||||||
|
isFocused: PropTypes.bool,
|
||||||
|
getRoomTitle: PropTypes.func,
|
||||||
|
getRoomAvatar: PropTypes.func,
|
||||||
|
getIsGroupChat: PropTypes.func,
|
||||||
|
getIsRead: PropTypes.func,
|
||||||
|
swipeEnabled: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
const RoomItemContainer = React.memo(({
|
static defaultProps = {
|
||||||
item,
|
avatarSize: 48,
|
||||||
onPress,
|
status: 'offline',
|
||||||
width,
|
getUserPresence: () => {},
|
||||||
toggleFav,
|
getRoomTitle: () => 'title',
|
||||||
toggleRead,
|
getRoomAvatar: () => '',
|
||||||
hideChannel,
|
getIsGroupChat: () => false,
|
||||||
testID,
|
getIsRead: () => false,
|
||||||
avatarSize,
|
swipeEnabled: true
|
||||||
baseUrl,
|
}
|
||||||
userId,
|
|
||||||
username,
|
|
||||||
token,
|
|
||||||
id,
|
|
||||||
showLastMessage,
|
|
||||||
status,
|
|
||||||
useRealName,
|
|
||||||
getUserPresence,
|
|
||||||
connected,
|
|
||||||
theme,
|
|
||||||
isFocused,
|
|
||||||
getRoomTitle,
|
|
||||||
getRoomAvatar,
|
|
||||||
getIsGroupChat,
|
|
||||||
getIsRead,
|
|
||||||
swipeEnabled
|
|
||||||
}) => {
|
|
||||||
const [, setForceUpdate] = useState(1);
|
|
||||||
|
|
||||||
useEffect(() => {
|
constructor(props) {
|
||||||
if (connected && item.t === 'd' && id) {
|
super(props);
|
||||||
|
this.mounted = false;
|
||||||
|
this.state = { avatarETag: '' };
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
const { avatarETag } = this.state;
|
||||||
|
if (nextState.avatarETag !== avatarETag) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const { props } = this;
|
||||||
|
return !attrs.every(key => props[key] === nextProps[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const { connected, getUserPresence, id } = this.props;
|
||||||
|
if (prevProps.connected !== connected && connected && this.isDirect) {
|
||||||
getUserPresence(id);
|
getUserPresence(id);
|
||||||
}
|
}
|
||||||
}, [connected]);
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
componentWillUnmount() {
|
||||||
|
if (this.userSubscription?.unsubscribe) {
|
||||||
|
this.userSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
if (this.roomSubscription?.unsubscribe) {
|
||||||
|
this.roomSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get isGroupChat() {
|
||||||
|
const { item, getIsGroupChat } = this.props;
|
||||||
|
return getIsGroupChat(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDirect() {
|
||||||
|
const { item: { t }, id } = this.props;
|
||||||
|
return t === 'd' && id && !this.isGroupChat;
|
||||||
|
}
|
||||||
|
|
||||||
|
init = async() => {
|
||||||
|
const { item } = this.props;
|
||||||
if (item?.observe) {
|
if (item?.observe) {
|
||||||
const observable = item.observe();
|
const observable = item.observe();
|
||||||
const subscription = observable?.subscribe?.(() => {
|
this.roomSubscription = observable?.subscribe?.(() => {
|
||||||
setForceUpdate(prevForceUpdate => prevForceUpdate + 1);
|
this.forceUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
|
||||||
subscription?.unsubscribe?.();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}, []);
|
|
||||||
|
|
||||||
const name = getRoomTitle(item);
|
if (this.isDirect) {
|
||||||
const avatar = getRoomAvatar(item);
|
const { id } = this.props;
|
||||||
const isGroupChat = getIsGroupChat(item);
|
const db = database.active;
|
||||||
const isRead = getIsRead(item);
|
const usersCollection = db.collections.get('users');
|
||||||
const _onPress = () => onPress(item);
|
try {
|
||||||
const date = item.lastMessage?.ts && formatDate(item.lastMessage.ts);
|
const user = await usersCollection.find(id);
|
||||||
|
const observable = user.observe();
|
||||||
let accessibilityLabel = name;
|
this.userSubscription = observable.subscribe((u) => {
|
||||||
if (item.unread === 1) {
|
const { avatarETag } = u;
|
||||||
accessibilityLabel += `, ${ item.unread } ${ I18n.t('alert') }`;
|
if (this.mounted) {
|
||||||
} else if (item.unread > 1) {
|
this.setState({ avatarETag });
|
||||||
accessibilityLabel += `, ${ item.unread } ${ I18n.t('alerts') }`;
|
} else {
|
||||||
|
this.state.avatarETag = avatarETag;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// User not found
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.userMentions > 0) {
|
onPress = () => {
|
||||||
accessibilityLabel += `, ${ I18n.t('you_were_mentioned') }`;
|
const { item, onPress } = this.props;
|
||||||
|
return onPress(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (date) {
|
render() {
|
||||||
accessibilityLabel += `, ${ I18n.t('last_message') } ${ date }`;
|
const { avatarETag } = this.state;
|
||||||
|
const {
|
||||||
|
item,
|
||||||
|
getRoomTitle,
|
||||||
|
getRoomAvatar,
|
||||||
|
getIsRead,
|
||||||
|
width,
|
||||||
|
toggleFav,
|
||||||
|
toggleRead,
|
||||||
|
hideChannel,
|
||||||
|
testID,
|
||||||
|
theme,
|
||||||
|
isFocused,
|
||||||
|
avatarSize,
|
||||||
|
baseUrl,
|
||||||
|
userId,
|
||||||
|
token,
|
||||||
|
status,
|
||||||
|
showLastMessage,
|
||||||
|
username,
|
||||||
|
useRealName,
|
||||||
|
swipeEnabled
|
||||||
|
} = this.props;
|
||||||
|
const name = getRoomTitle(item);
|
||||||
|
const avatar = getRoomAvatar(item);
|
||||||
|
const isRead = getIsRead(item);
|
||||||
|
const date = item.lastMessage?.ts && formatDate(item.lastMessage.ts);
|
||||||
|
|
||||||
|
let accessibilityLabel = name;
|
||||||
|
if (item.unread === 1) {
|
||||||
|
accessibilityLabel += `, ${ item.unread } ${ I18n.t('alert') }`;
|
||||||
|
} else if (item.unread > 1) {
|
||||||
|
accessibilityLabel += `, ${ item.unread } ${ I18n.t('alerts') }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.userMentions > 0) {
|
||||||
|
accessibilityLabel += `, ${ I18n.t('you_were_mentioned') }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (date) {
|
||||||
|
accessibilityLabel += `, ${ I18n.t('last_message') } ${ date }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RoomItem
|
||||||
|
name={name}
|
||||||
|
avatar={avatar}
|
||||||
|
isGroupChat={this.isGroupChat}
|
||||||
|
isRead={isRead}
|
||||||
|
onPress={this.onPress}
|
||||||
|
date={date}
|
||||||
|
accessibilityLabel={accessibilityLabel}
|
||||||
|
userMentions={item.userMentions}
|
||||||
|
width={width}
|
||||||
|
favorite={item.f}
|
||||||
|
toggleFav={toggleFav}
|
||||||
|
rid={item.rid}
|
||||||
|
toggleRead={toggleRead}
|
||||||
|
hideChannel={hideChannel}
|
||||||
|
testID={testID}
|
||||||
|
type={item.t}
|
||||||
|
theme={theme}
|
||||||
|
isFocused={isFocused}
|
||||||
|
size={avatarSize}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
userId={userId}
|
||||||
|
token={token}
|
||||||
|
prid={item.prid}
|
||||||
|
status={status}
|
||||||
|
hideUnreadStatus={item.hideUnreadStatus}
|
||||||
|
alert={item.alert}
|
||||||
|
roomUpdatedAt={item.roomUpdatedAt}
|
||||||
|
lastMessage={item.lastMessage}
|
||||||
|
showLastMessage={showLastMessage}
|
||||||
|
username={username}
|
||||||
|
useRealName={useRealName}
|
||||||
|
unread={item.unread}
|
||||||
|
groupMentions={item.groupMentions}
|
||||||
|
avatarETag={avatarETag}
|
||||||
|
swipeEnabled={swipeEnabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return (
|
|
||||||
<RoomItem
|
|
||||||
name={name}
|
|
||||||
avatar={avatar}
|
|
||||||
isGroupChat={isGroupChat}
|
|
||||||
isRead={isRead}
|
|
||||||
onPress={_onPress}
|
|
||||||
date={date}
|
|
||||||
accessibilityLabel={accessibilityLabel}
|
|
||||||
userMentions={item.userMentions}
|
|
||||||
width={width}
|
|
||||||
favorite={item.f}
|
|
||||||
toggleFav={toggleFav}
|
|
||||||
rid={item.rid}
|
|
||||||
toggleRead={toggleRead}
|
|
||||||
hideChannel={hideChannel}
|
|
||||||
testID={testID}
|
|
||||||
type={item.t}
|
|
||||||
theme={theme}
|
|
||||||
isFocused={isFocused}
|
|
||||||
size={avatarSize}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
userId={userId}
|
|
||||||
token={token}
|
|
||||||
prid={item.prid}
|
|
||||||
status={status}
|
|
||||||
hideUnreadStatus={item.hideUnreadStatus}
|
|
||||||
alert={item.alert}
|
|
||||||
roomUpdatedAt={item.roomUpdatedAt}
|
|
||||||
lastMessage={item.lastMessage}
|
|
||||||
showLastMessage={showLastMessage}
|
|
||||||
username={username}
|
|
||||||
useRealName={useRealName}
|
|
||||||
unread={item.unread}
|
|
||||||
groupMentions={item.groupMentions}
|
|
||||||
swipeEnabled={swipeEnabled}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}, arePropsEqual);
|
|
||||||
|
|
||||||
RoomItemContainer.propTypes = {
|
|
||||||
item: PropTypes.object.isRequired,
|
|
||||||
baseUrl: PropTypes.string.isRequired,
|
|
||||||
showLastMessage: PropTypes.bool,
|
|
||||||
id: PropTypes.string,
|
|
||||||
onPress: PropTypes.func,
|
|
||||||
userId: PropTypes.string,
|
|
||||||
username: PropTypes.string,
|
|
||||||
token: PropTypes.string,
|
|
||||||
avatarSize: PropTypes.number,
|
|
||||||
testID: PropTypes.string,
|
|
||||||
width: PropTypes.number,
|
|
||||||
status: PropTypes.string,
|
|
||||||
toggleFav: PropTypes.func,
|
|
||||||
toggleRead: PropTypes.func,
|
|
||||||
hideChannel: PropTypes.func,
|
|
||||||
useRealName: PropTypes.bool,
|
|
||||||
getUserPresence: PropTypes.func,
|
|
||||||
connected: PropTypes.bool,
|
|
||||||
theme: PropTypes.string,
|
|
||||||
isFocused: PropTypes.bool,
|
|
||||||
getRoomTitle: PropTypes.func,
|
|
||||||
getRoomAvatar: PropTypes.func,
|
|
||||||
getIsGroupChat: PropTypes.func,
|
|
||||||
getIsRead: PropTypes.func,
|
|
||||||
swipeEnabled: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
RoomItemContainer.defaultProps = {
|
|
||||||
avatarSize: 48,
|
|
||||||
status: 'offline',
|
|
||||||
getUserPresence: () => {},
|
|
||||||
getRoomTitle: () => 'title',
|
|
||||||
getRoomAvatar: () => '',
|
|
||||||
getIsGroupChat: () => false,
|
|
||||||
getIsRead: () => false,
|
|
||||||
swipeEnabled: true
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
let status = 'offline';
|
let status = 'offline';
|
||||||
|
|
|
@ -42,7 +42,7 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
const UserItem = ({
|
const UserItem = ({
|
||||||
name, username, onPress, testID, onLongPress, style, icon, baseUrl, user, theme
|
name, username, onPress, testID, onLongPress, style, icon, theme
|
||||||
}) => (
|
}) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
|
@ -58,7 +58,7 @@ const UserItem = ({
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<View style={[styles.container, styles.button, style]}>
|
<View style={[styles.container, styles.button, style]}>
|
||||||
<Avatar text={username} size={30} type='d' style={styles.avatar} baseUrl={baseUrl} userId={user.id} token={user.token} />
|
<Avatar text={username} size={30} style={styles.avatar} />
|
||||||
<View style={styles.textContainer}>
|
<View style={styles.textContainer}>
|
||||||
<Text style={[styles.name, { color: themes[theme].titleText }]} numberOfLines={1}>{name}</Text>
|
<Text style={[styles.name, { color: themes[theme].titleText }]} numberOfLines={1}>{name}</Text>
|
||||||
<Text style={[styles.username, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>@{username}</Text>
|
<Text style={[styles.username, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>@{username}</Text>
|
||||||
|
@ -71,11 +71,6 @@ const UserItem = ({
|
||||||
UserItem.propTypes = {
|
UserItem.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
username: PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
user: PropTypes.shape({
|
|
||||||
id: PropTypes.string,
|
|
||||||
token: PropTypes.string
|
|
||||||
}),
|
|
||||||
baseUrl: PropTypes.string.isRequired,
|
|
||||||
onPress: PropTypes.func.isRequired,
|
onPress: PropTypes.func.isRequired,
|
||||||
testID: PropTypes.string.isRequired,
|
testID: PropTypes.string.isRequired,
|
||||||
onLongPress: PropTypes.func,
|
onLongPress: PropTypes.func,
|
||||||
|
|
|
@ -178,7 +178,8 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
status: user.status,
|
status: user.status,
|
||||||
statusText: user.statusText,
|
statusText: user.statusText,
|
||||||
roles: user.roles,
|
roles: user.roles,
|
||||||
loginEmailPassword: user.loginEmailPassword
|
loginEmailPassword: user.loginEmailPassword,
|
||||||
|
avatarETag: user.avatarETag
|
||||||
};
|
};
|
||||||
yield serversDB.action(async() => {
|
yield serversDB.action(async() => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -87,7 +87,8 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
||||||
language: userRecord.language,
|
language: userRecord.language,
|
||||||
status: userRecord.status,
|
status: userRecord.status,
|
||||||
statusText: userRecord.statusText,
|
statusText: userRecord.statusText,
|
||||||
roles: userRecord.roles
|
roles: userRecord.roles,
|
||||||
|
avatarETag: userRecord.avatarETag
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
// search credentials on shared credentials (Experimental/Official)
|
// search credentials on shared credentials (Experimental/Official)
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => (
|
const formatUrl = (url, size, query) => `${ url }?format=png&size=${ size }${ query }`;
|
||||||
`${ baseUrl }${ url }?format=png&size=${ uriSize }&${ avatarAuthURLFragment }`
|
|
||||||
);
|
|
||||||
|
|
||||||
export const avatarURL = ({
|
export const avatarURL = ({
|
||||||
type, text, size, userId, token, avatar, baseUrl
|
type, text, size, user = {}, avatar, server, avatarETag
|
||||||
}) => {
|
}) => {
|
||||||
const room = type === 'd' ? text : `@${ text }`;
|
const room = type === 'd' ? text : `@${ text }`;
|
||||||
|
|
||||||
// Avoid requesting several sizes by having only two sizes on cache
|
// Avoid requesting several sizes by having only two sizes on cache
|
||||||
const uriSize = size === 100 ? 100 : 50;
|
const uriSize = size === 100 ? 100 : 50;
|
||||||
|
|
||||||
let avatarAuthURLFragment = '';
|
const { id, token } = user;
|
||||||
if (userId && token) {
|
let query = '';
|
||||||
avatarAuthURLFragment = `&rc_token=${ token }&rc_uid=${ userId }`;
|
if (id && token) {
|
||||||
|
query += `&rc_token=${ token }&rc_uid=${ id }`;
|
||||||
|
}
|
||||||
|
if (avatarETag) {
|
||||||
|
query += `&etag=${ avatarETag }`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let uri;
|
|
||||||
if (avatar) {
|
if (avatar) {
|
||||||
uri = avatar.includes('http') ? avatar : formatUrl(avatar, baseUrl, uriSize, avatarAuthURLFragment);
|
if (avatar.startsWith('http')) {
|
||||||
} else {
|
return avatar;
|
||||||
uri = formatUrl(`/avatar/${ room }`, baseUrl, uriSize, avatarAuthURLFragment);
|
}
|
||||||
|
|
||||||
|
return formatUrl(`${ server }${ avatar }`, uriSize, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
return uri;
|
return formatUrl(`${ server }/avatar/${ room }`, uriSize, query);
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,7 @@ const SelectChannel = ({
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
const getAvatar = (text, type) => avatarURL({
|
const getAvatar = (text, type) => avatarURL({
|
||||||
text, type, userId, token, baseUrl: server
|
text, type, user: { id: userId, token }, server
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -2,10 +2,12 @@ import React, { useState } from 'react';
|
||||||
import { Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import { avatarURL } from '../../utils/avatar';
|
import { avatarURL } from '../../utils/avatar';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
|
import database from '../../lib/database';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
||||||
|
|
||||||
|
@ -19,15 +21,34 @@ const SelectUsers = ({
|
||||||
|
|
||||||
const getUsers = debounce(async(keyword = '') => {
|
const getUsers = debounce(async(keyword = '') => {
|
||||||
try {
|
try {
|
||||||
|
const db = database.active;
|
||||||
|
const usersCollection = db.collections.get('users');
|
||||||
const res = await RocketChat.search({ text: keyword, filterRooms: false });
|
const res = await RocketChat.search({ text: keyword, filterRooms: false });
|
||||||
setUsers([...users.filter(u => selected.includes(u.name)), ...res.filter(r => !users.find(u => u.name === r.name))]);
|
let items = [...users.filter(u => selected.includes(u.name)), ...res.filter(r => !users.find(u => u.name === r.name))];
|
||||||
|
const records = await usersCollection.query(Q.where('username', Q.oneOf(items.map(u => u.name)))).fetch();
|
||||||
|
items = items.map((item) => {
|
||||||
|
const index = records.findIndex(r => r.username === item.name);
|
||||||
|
if (index > -1) {
|
||||||
|
const record = records[index];
|
||||||
|
return {
|
||||||
|
uids: item.uids,
|
||||||
|
usernames: item.usernames,
|
||||||
|
prid: item.prid,
|
||||||
|
fname: item.fname,
|
||||||
|
name: item.name,
|
||||||
|
avatarETag: record.avatarETag
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
setUsers(items);
|
||||||
} catch {
|
} catch {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
const getAvatar = text => avatarURL({
|
const getAvatar = item => avatarURL({
|
||||||
text, type: 'd', userId, token, baseUrl: server
|
text: RocketChat.getRoomAvatar(item), type: 'd', user: { id: userId, token }, server, avatarETag: item.avatarETag
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -41,7 +62,7 @@ const SelectUsers = ({
|
||||||
options={users.map(user => ({
|
options={users.map(user => ({
|
||||||
value: user.name,
|
value: user.name,
|
||||||
text: { text: RocketChat.getRoomTitle(user) },
|
text: { text: RocketChat.getRoomTitle(user) },
|
||||||
imageUrl: getAvatar(RocketChat.getRoomAvatar(user))
|
imageUrl: getAvatar(user)
|
||||||
}))}
|
}))}
|
||||||
onClose={() => setUsers(users.filter(u => selected.includes(u.name)))}
|
onClose={() => setUsers(users.filter(u => selected.includes(u.name)))}
|
||||||
placeholder={{ text: `${ I18n.t('Select_Users') }...` }}
|
placeholder={{ text: `${ I18n.t('Select_Users') }...` }}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import prompt from 'react-native-prompt-android';
|
||||||
import SHA256 from 'js-sha256';
|
import SHA256 from 'js-sha256';
|
||||||
import ImagePicker from 'react-native-image-crop-picker';
|
import ImagePicker from 'react-native-image-crop-picker';
|
||||||
import RNPickerSelect from 'react-native-picker-select';
|
import RNPickerSelect from 'react-native-picker-select';
|
||||||
import equal from 'deep-equal';
|
import { isEqual, omit } from 'lodash';
|
||||||
|
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
import KeyboardView from '../../presentation/KeyboardView';
|
import KeyboardView from '../../presentation/KeyboardView';
|
||||||
|
@ -84,16 +84,22 @@ class ProfileView extends React.Component {
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
const { user } = this.props;
|
const { user } = this.props;
|
||||||
if (!equal(user, nextProps.user)) {
|
/*
|
||||||
|
* We need to ignore status because on Android ImagePicker
|
||||||
|
* changes the activity, so, the user status changes and
|
||||||
|
* it's resetting the avatar right after
|
||||||
|
* select some image from gallery.
|
||||||
|
*/
|
||||||
|
if (!isEqual(omit(user, ['status']), omit(nextProps.user, ['status']))) {
|
||||||
this.init(nextProps.user);
|
this.init(nextProps.user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
if (!equal(nextState, this.state)) {
|
if (!isEqual(nextState, this.state)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!equal(nextProps, this.props)) {
|
if (!isEqual(nextProps, this.props)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -324,7 +330,6 @@ class ProfileView extends React.Component {
|
||||||
const { avatarUrl, avatarSuggestions } = this.state;
|
const { avatarUrl, avatarSuggestions } = this.state;
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
baseUrl,
|
|
||||||
theme,
|
theme,
|
||||||
Accounts_AllowUserAvatarChange
|
Accounts_AllowUserAvatarChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -332,7 +337,7 @@ class ProfileView extends React.Component {
|
||||||
return (
|
return (
|
||||||
<View style={styles.avatarButtons}>
|
<View style={styles.avatarButtons}>
|
||||||
{this.renderAvatarButton({
|
{this.renderAvatarButton({
|
||||||
child: <Avatar text={`@${ user.username }`} size={50} baseUrl={baseUrl} userId={user.id} token={user.token} />,
|
child: <Avatar text={`@${ user.username }`} size={50} />,
|
||||||
onPress: () => this.resetAvatar(),
|
onPress: () => this.resetAvatar(),
|
||||||
disabled: !Accounts_AllowUserAvatarChange,
|
disabled: !Accounts_AllowUserAvatarChange,
|
||||||
key: 'profile-view-reset-avatar'
|
key: 'profile-view-reset-avatar'
|
||||||
|
@ -354,7 +359,7 @@ class ProfileView extends React.Component {
|
||||||
return this.renderAvatarButton({
|
return this.renderAvatarButton({
|
||||||
disabled: !Accounts_AllowUserAvatarChange,
|
disabled: !Accounts_AllowUserAvatarChange,
|
||||||
key: `profile-view-avatar-${ service }`,
|
key: `profile-view-avatar-${ service }`,
|
||||||
child: <Avatar avatar={url} size={50} baseUrl={baseUrl} userId={user.id} token={user.token} />,
|
child: <Avatar avatar={url} size={50} />,
|
||||||
onPress: () => this.setAvatar({
|
onPress: () => this.setAvatar({
|
||||||
url, data: blob, service, contentType
|
url, data: blob, service, contentType
|
||||||
})
|
})
|
||||||
|
@ -448,7 +453,6 @@ class ProfileView extends React.Component {
|
||||||
name, username, email, newPassword, avatarUrl, customFields, avatar, saving
|
name, username, email, newPassword, avatarUrl, customFields, avatar, saving
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
baseUrl,
|
|
||||||
user,
|
user,
|
||||||
theme,
|
theme,
|
||||||
Accounts_AllowEmailChange,
|
Accounts_AllowEmailChange,
|
||||||
|
@ -474,12 +478,10 @@ class ProfileView extends React.Component {
|
||||||
>
|
>
|
||||||
<View style={styles.avatarContainer} testID='profile-view-avatar'>
|
<View style={styles.avatarContainer} testID='profile-view-avatar'>
|
||||||
<Avatar
|
<Avatar
|
||||||
text={username}
|
text={user.username}
|
||||||
avatar={avatar && avatar.url}
|
avatar={avatar?.url}
|
||||||
|
isStatic={avatar?.url}
|
||||||
size={100}
|
size={100}
|
||||||
baseUrl={baseUrl}
|
|
||||||
userId={user.id}
|
|
||||||
token={user.token}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<RCTextInput
|
<RCTextInput
|
||||||
|
|
|
@ -14,7 +14,6 @@ import RocketChat from '../../lib/rocketchat';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
|
|
||||||
class ReadReceiptView extends React.Component {
|
class ReadReceiptView extends React.Component {
|
||||||
|
@ -31,8 +30,6 @@ class ReadReceiptView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
route: PropTypes.object,
|
route: PropTypes.object,
|
||||||
Message_TimeFormat: PropTypes.string,
|
Message_TimeFormat: PropTypes.string,
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
user: PropTypes.object,
|
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,9 +93,7 @@ class ReadReceiptView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem = ({ item }) => {
|
renderItem = ({ item }) => {
|
||||||
const {
|
const { Message_TimeFormat, theme } = this.props;
|
||||||
Message_TimeFormat, user: { id: userId, token }, baseUrl, theme
|
|
||||||
} = this.props;
|
|
||||||
const time = moment(item.ts).format(Message_TimeFormat);
|
const time = moment(item.ts).format(Message_TimeFormat);
|
||||||
if (!item?.user?.username) {
|
if (!item?.user?.username) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -108,9 +103,6 @@ class ReadReceiptView extends React.Component {
|
||||||
<Avatar
|
<Avatar
|
||||||
text={item.user.username}
|
text={item.user.username}
|
||||||
size={40}
|
size={40}
|
||||||
baseUrl={baseUrl}
|
|
||||||
userId={userId}
|
|
||||||
token={token}
|
|
||||||
/>
|
/>
|
||||||
<View style={styles.infoContainer}>
|
<View style={styles.infoContainer}>
|
||||||
<View style={styles.item}>
|
<View style={styles.item}>
|
||||||
|
@ -168,9 +160,7 @@ class ReadReceiptView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
Message_TimeFormat: state.settings.Message_TimeFormat
|
||||||
baseUrl: state.server.server,
|
|
||||||
user: getUserSelector(state)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withTheme(ReadReceiptView));
|
export default connect(mapStateToProps)(withTheme(ReadReceiptView));
|
||||||
|
|
|
@ -670,7 +670,7 @@ class RoomActionsView extends React.Component {
|
||||||
renderRoomInfo = ({ item }) => {
|
renderRoomInfo = ({ item }) => {
|
||||||
const { room, member } = this.state;
|
const { room, member } = this.state;
|
||||||
const { name, t, topic } = room;
|
const { name, t, topic } = room;
|
||||||
const { baseUrl, user, theme } = this.props;
|
const { theme } = this.props;
|
||||||
|
|
||||||
const avatar = RocketChat.getRoomAvatar(room);
|
const avatar = RocketChat.getRoomAvatar(room);
|
||||||
|
|
||||||
|
@ -679,12 +679,9 @@ class RoomActionsView extends React.Component {
|
||||||
<>
|
<>
|
||||||
<Avatar
|
<Avatar
|
||||||
text={avatar}
|
text={avatar}
|
||||||
size={50}
|
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
|
size={50}
|
||||||
type={t}
|
type={t}
|
||||||
baseUrl={baseUrl}
|
|
||||||
userId={user.id}
|
|
||||||
token={user.token}
|
|
||||||
>
|
>
|
||||||
{t === 'd' && member._id ? <Status style={sharedStyles.status} id={member._id} /> : null }
|
{t === 'd' && member._id ? <Status style={sharedStyles.status} id={member._id} /> : null }
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
|
|
@ -20,7 +20,6 @@ import StatusBar from '../../containers/StatusBar';
|
||||||
import log, { logEvent, events } from '../../utils/log';
|
import log, { logEvent, events } from '../../utils/log';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
|
||||||
import Markdown from '../../containers/markdown';
|
import Markdown from '../../containers/markdown';
|
||||||
import { LISTENER } from '../../containers/Toast';
|
import { LISTENER } from '../../containers/Toast';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../utils/events';
|
||||||
|
@ -54,11 +53,6 @@ class RoomInfoView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigation: PropTypes.object,
|
navigation: PropTypes.object,
|
||||||
route: PropTypes.object,
|
route: PropTypes.object,
|
||||||
user: PropTypes.shape({
|
|
||||||
id: PropTypes.string,
|
|
||||||
token: PropTypes.string
|
|
||||||
}),
|
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
rooms: PropTypes.array,
|
rooms: PropTypes.array,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
isMasterDetail: PropTypes.bool,
|
isMasterDetail: PropTypes.bool,
|
||||||
|
@ -287,17 +281,14 @@ class RoomInfoView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAvatar = (room, roomUser) => {
|
renderAvatar = (room, roomUser) => {
|
||||||
const { baseUrl, user, theme } = this.props;
|
const { theme } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
text={room.name || roomUser.username}
|
text={room.name || roomUser.username}
|
||||||
size={100}
|
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
type={this.t}
|
type={this.t}
|
||||||
baseUrl={baseUrl}
|
size={100}
|
||||||
userId={user.id}
|
|
||||||
token={user.token}
|
|
||||||
>
|
>
|
||||||
{this.t === 'd' && roomUser._id ? <Status style={[sharedStyles.status, styles.status]} theme={theme} size={24} id={roomUser._id} /> : null}
|
{this.t === 'd' && roomUser._id ? <Status style={[sharedStyles.status, styles.status]} theme={theme} size={24} id={roomUser._id} /> : null}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
@ -377,8 +368,6 @@ class RoomInfoView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
baseUrl: state.server.server,
|
|
||||||
user: getUserSelector(state),
|
|
||||||
rooms: state.room.rooms,
|
rooms: state.room.rooms,
|
||||||
isMasterDetail: state.app.isMasterDetail,
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
jitsiEnabled: state.settings.Jitsi_Enabled || false
|
jitsiEnabled: state.settings.Jitsi_Enabled || false
|
||||||
|
|
|
@ -32,16 +32,14 @@ const LeftButtons = React.memo(({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const onPress = useCallback(() => goRoomActionsView(), []);
|
const onPress = useCallback(() => goRoomActionsView(), []);
|
||||||
|
|
||||||
if (baseUrl && userId && token) {
|
if (baseUrl && userId && token) {
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
text={title}
|
text={title}
|
||||||
size={30}
|
size={30}
|
||||||
type={t}
|
type={t}
|
||||||
baseUrl={baseUrl}
|
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
userId={userId}
|
|
||||||
token={token}
|
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
import isEqual from 'react-fast-compare';
|
||||||
|
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
import Status from '../../containers/Status/Status';
|
import Status from '../../containers/Status/Status';
|
||||||
|
@ -90,19 +91,8 @@ class Sidebar extends Component {
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (nextProps.user && user) {
|
if (!isEqual(nextProps.user, user)) {
|
||||||
if (nextProps.user.language !== user.language) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.user.status !== user.status) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.user.username !== user.username) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.user.statusText !== user.statusText) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (nextProps.isMasterDetail !== isMasterDetail) {
|
if (nextProps.isMasterDetail !== isMasterDetail) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -241,11 +231,8 @@ class Sidebar extends Component {
|
||||||
<View style={styles.header} theme={theme}>
|
<View style={styles.header} theme={theme}>
|
||||||
<Avatar
|
<Avatar
|
||||||
text={user.username}
|
text={user.username}
|
||||||
size={30}
|
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
baseUrl={baseUrl}
|
size={30}
|
||||||
userId={user.id}
|
|
||||||
token={user.token}
|
|
||||||
/>
|
/>
|
||||||
<View style={styles.headerTextContainer}>
|
<View style={styles.headerTextContainer}>
|
||||||
<View style={styles.headerUsername}>
|
<View style={styles.headerUsername}>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -95,7 +95,7 @@
|
||||||
"react-native-notifier": "1.3.1",
|
"react-native-notifier": "1.3.1",
|
||||||
"react-native-orientation-locker": "1.1.8",
|
"react-native-orientation-locker": "1.1.8",
|
||||||
"react-native-picker-select": "7.0.0",
|
"react-native-picker-select": "7.0.0",
|
||||||
"react-native-platform-touchable": "^1.1.1",
|
"react-native-platform-touchable": "1.1.1",
|
||||||
"react-native-popover-view": "3.0.3",
|
"react-native-popover-view": "3.0.3",
|
||||||
"react-native-progress": "4.1.2",
|
"react-native-progress": "4.1.2",
|
||||||
"react-native-prompt-android": "^1.1.0",
|
"react-native-prompt-android": "^1.1.0",
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { ScrollView, StyleSheet } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { themes } from '../../app/constants/colors';
|
||||||
|
import Avatar from '../../app/containers/Avatar/Avatar';
|
||||||
|
import Status from '../../app/containers/Status/Status';
|
||||||
|
import StoriesSeparator from './StoriesSeparator';
|
||||||
|
import sharedStyles from '../../app/views/Styles';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
status: {
|
||||||
|
borderWidth: 4,
|
||||||
|
bottom: -4,
|
||||||
|
right: -4
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
padding: 16
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const server = 'https://open.rocket.chat';
|
||||||
|
|
||||||
|
const Separator = ({ title, theme }) => <StoriesSeparator title={title} theme={theme} />;
|
||||||
|
Separator.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const AvatarStories = ({ theme }) => (
|
||||||
|
<ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||||
|
<Separator title='Avatar by text' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
text='Avatar'
|
||||||
|
server={server}
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
|
<Separator title='Avatar by url' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
avatar='https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg'
|
||||||
|
server={server}
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
|
<Separator title='Avatar by path' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
avatar='/avatar/diego.mello'
|
||||||
|
server={server}
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
|
<Separator title='With ETag' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
type='d'
|
||||||
|
text='djorkaeff.alexandre'
|
||||||
|
avatarETag='5ag8KffJcZj9m5rCv'
|
||||||
|
server={server}
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
|
<Separator title='Without ETag' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
type='d'
|
||||||
|
text='djorkaeff.alexandre'
|
||||||
|
server={server}
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
|
<Separator title='Emoji' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
emoji='troll'
|
||||||
|
getCustomEmoji={() => ({ name: 'troll', extension: 'jpg' })}
|
||||||
|
server={server}
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
|
<Separator title='Direct' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
text='diego.mello'
|
||||||
|
server={server}
|
||||||
|
type='d'
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
|
<Separator title='Channel' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
text='general'
|
||||||
|
server={server}
|
||||||
|
type='c'
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
|
<Separator title='Touchable' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
text='Avatar'
|
||||||
|
server={server}
|
||||||
|
onPress={() => console.log('Pressed!')}
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
|
<Separator title='Static' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
avatar='https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg'
|
||||||
|
server={server}
|
||||||
|
isStatic
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
|
<Separator title='Custom borderRadius' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
text='Avatar'
|
||||||
|
server={server}
|
||||||
|
borderRadius={28}
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
|
<Separator title='Children' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
text='Avatar'
|
||||||
|
server={server}
|
||||||
|
size={56}
|
||||||
|
>
|
||||||
|
<Status
|
||||||
|
size={24}
|
||||||
|
style={[sharedStyles.status, styles.status]}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
</Avatar>
|
||||||
|
<Separator title='Wrong server' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
text='Avatar'
|
||||||
|
server='https://google.com'
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
|
<Separator title='Custom style' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
text='Avatar'
|
||||||
|
server={server}
|
||||||
|
size={56}
|
||||||
|
style={styles.custom}
|
||||||
|
/>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
AvatarStories.propTypes = {
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
export default AvatarStories;
|
|
@ -9,6 +9,7 @@ import Message from './Message';
|
||||||
import UiKitMessage from './UiKitMessage';
|
import UiKitMessage from './UiKitMessage';
|
||||||
import UiKitModal from './UiKitModal';
|
import UiKitModal from './UiKitModal';
|
||||||
import Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
|
import Avatar from './Avatar';
|
||||||
// import RoomViewHeader from './RoomViewHeader';
|
// import RoomViewHeader from './RoomViewHeader';
|
||||||
|
|
||||||
import MessageContext from '../../app/containers/message/Context';
|
import MessageContext from '../../app/containers/message/Context';
|
||||||
|
@ -70,6 +71,8 @@ storiesOf('UiKitModal', module)
|
||||||
.add('list UiKitModal', () => <UiKitModal theme={theme} />);
|
.add('list UiKitModal', () => <UiKitModal theme={theme} />);
|
||||||
storiesOf('Markdown', module)
|
storiesOf('Markdown', module)
|
||||||
.add('list Markdown', () => <Markdown theme={theme} />);
|
.add('list Markdown', () => <Markdown theme={theme} />);
|
||||||
|
storiesOf('Avatar', module)
|
||||||
|
.add('list Avatar', () => <Avatar theme={theme} />);
|
||||||
|
|
||||||
// FIXME: I couldn't make these pass on jest :(
|
// FIXME: I couldn't make these pass on jest :(
|
||||||
// storiesOf('RoomViewHeader', module)
|
// storiesOf('RoomViewHeader', module)
|
||||||
|
|
|
@ -12891,7 +12891,7 @@ react-native-picker-select@7.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash.isequal "^4.5.0"
|
lodash.isequal "^4.5.0"
|
||||||
|
|
||||||
react-native-platform-touchable@^1.1.1:
|
react-native-platform-touchable@1.1.1, react-native-platform-touchable@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-platform-touchable/-/react-native-platform-touchable-1.1.1.tgz#fde4acc65eea585d28b164d0c3716a42129a68e4"
|
resolved "https://registry.yarnpkg.com/react-native-platform-touchable/-/react-native-platform-touchable-1.1.1.tgz#fde4acc65eea585d28b164d0c3716a42129a68e4"
|
||||||
integrity sha1-/eSsxl7qWF0osWTQw3FqQhKaaOQ=
|
integrity sha1-/eSsxl7qWF0osWTQw3FqQhKaaOQ=
|
||||||
|
|
Loading…
Reference in New Issue