[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 { themes } from '../../constants/colors';
|
||||
import { useTheme } from '../../theme';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import { ROW_HEIGHT } from '../../presentation/RoomItem';
|
||||
import { goRoom } from '../../utils/goRoom';
|
||||
import Navigation from '../../lib/Navigation';
|
||||
|
@ -65,14 +64,11 @@ const styles = StyleSheet.create({
|
|||
|
||||
const hideNotification = () => Notifier.hideNotification();
|
||||
|
||||
const NotifierComponent = React.memo(({
|
||||
baseUrl, user, notification, isMasterDetail
|
||||
}) => {
|
||||
const NotifierComponent = React.memo(({ notification, isMasterDetail }) => {
|
||||
const { theme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { isLandscape } = useOrientation();
|
||||
|
||||
const { id: userId, token } = user;
|
||||
const { text, payload } = notification;
|
||||
const { type } = payload;
|
||||
const name = type === 'd' ? payload.sender.username : payload.name;
|
||||
|
@ -115,7 +111,7 @@ const NotifierComponent = React.memo(({
|
|||
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}>
|
||||
<Text style={[styles.roomName, { color: themes[theme].titleText }]} numberOfLines={1}>{title}</Text>
|
||||
<Text style={[styles.message, { color: themes[theme].titleText }]} numberOfLines={1}>{text}</Text>
|
||||
|
@ -134,15 +130,11 @@ const NotifierComponent = React.memo(({
|
|||
});
|
||||
|
||||
NotifierComponent.propTypes = {
|
||||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
notification: PropTypes.object,
|
||||
isMasterDetail: PropTypes.bool
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
user: getUserSelector(state),
|
||||
baseUrl: state.server.server,
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ const MentionItem = ({
|
|||
item, trackingType, theme
|
||||
}) => {
|
||||
const context = useContext(MessageboxContext);
|
||||
const { baseUrl, user, onPressMention } = context;
|
||||
const { onPressMention } = context;
|
||||
|
||||
const defineTestID = (type) => {
|
||||
switch (type) {
|
||||
|
@ -43,9 +43,6 @@ const MentionItem = ({
|
|||
text={item.username || item.name}
|
||||
size={30}
|
||||
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>
|
||||
</>
|
||||
|
|
|
@ -9,7 +9,7 @@ import { themes } from '../../constants/colors';
|
|||
import styles from './styles';
|
||||
|
||||
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 emoji = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, ''));
|
||||
|
@ -17,7 +17,10 @@ const Emoji = React.memo(({
|
|||
return (
|
||||
<CustomEmoji
|
||||
baseUrl={baseUrl}
|
||||
style={isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji}
|
||||
style={[
|
||||
isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji,
|
||||
style
|
||||
]}
|
||||
emoji={emoji}
|
||||
/>
|
||||
);
|
||||
|
@ -27,7 +30,7 @@ const Emoji = React.memo(({
|
|||
style={[
|
||||
{ color: themes[theme].bodyText },
|
||||
isMessageContainsOnlyEmoji ? styles.textBig : styles.text,
|
||||
...style
|
||||
style
|
||||
]}
|
||||
>
|
||||
{emojiUnicode}
|
||||
|
@ -41,7 +44,7 @@ Emoji.propTypes = {
|
|||
getCustomEmoji: PropTypes.func,
|
||||
baseUrl: PropTypes.string,
|
||||
customEmojis: PropTypes.bool,
|
||||
style: PropTypes.array,
|
||||
style: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Avatar from '../Avatar';
|
||||
import Avatar from '../Avatar/Avatar';
|
||||
import styles from './styles';
|
||||
import MessageContext from './Context';
|
||||
|
||||
|
@ -22,11 +22,11 @@ const MessageAvatar = React.memo(({
|
|||
borderRadius={small ? 2 : 4}
|
||||
onPress={author._id === user.id ? undefined : () => navToRoomInfo(navParam)}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
user={user}
|
||||
server={baseUrl}
|
||||
avatarETag={author.avatarETag}
|
||||
avatar={avatar}
|
||||
emoji={emoji}
|
||||
baseUrl={baseUrl}
|
||||
userId={user.id}
|
||||
token={user.token}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -6,9 +6,10 @@ import Message from './Message';
|
|||
import MessageContext from './Context';
|
||||
import debounce from '../../utils/debounce';
|
||||
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
|
||||
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants';
|
||||
import messagesStatus from '../../constants/messagesStatus';
|
||||
import { withTheme } from '../../theme';
|
||||
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants';
|
||||
import database from '../../lib/database';
|
||||
|
||||
class MessageContainer extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -72,7 +73,11 @@ class MessageContainer extends React.Component {
|
|||
theme: 'light'
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
state = {
|
||||
author: null
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const { item } = this.props;
|
||||
if (item && item.observe) {
|
||||
const observable = item.observe();
|
||||
|
@ -80,6 +85,19 @@ class MessageContainer extends React.Component {
|
|||
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) {
|
||||
|
@ -94,6 +112,9 @@ class MessageContainer extends React.Component {
|
|||
if (this.subscription && this.subscription.unsubscribe) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
if (this.userSubscription && this.userSubscription.unsubscribe) {
|
||||
this.userSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
onPress = debounce(() => {
|
||||
|
@ -242,6 +263,7 @@ class MessageContainer extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { author } = this.state;
|
||||
const {
|
||||
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, showAttachment, timeFormat, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom, callJitsi, blockAction, rid, theme
|
||||
} = this.props;
|
||||
|
@ -276,7 +298,7 @@ class MessageContainer extends React.Component {
|
|||
id={id}
|
||||
msg={message}
|
||||
rid={rid}
|
||||
author={u}
|
||||
author={author || u}
|
||||
ts={ts}
|
||||
type={t}
|
||||
attachments={attachments}
|
||||
|
|
|
@ -15,15 +15,16 @@ import Role from './model/Role';
|
|||
import Permission from './model/Permission';
|
||||
import SlashCommand from './model/SlashCommand';
|
||||
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 serversSchema from './schema/servers';
|
||||
import appSchema from './schema/app';
|
||||
|
||||
import migrations from './model/migrations';
|
||||
|
||||
import serversMigrations from './model/serversMigrations';
|
||||
import serversMigrations from './model/servers/migrations';
|
||||
|
||||
import { isIOS } from '../../utils/deviceInfo';
|
||||
import appGroup from '../../utils/appGroup';
|
||||
|
@ -58,7 +59,8 @@ export const getDatabase = (database = '') => {
|
|||
Setting,
|
||||
Role,
|
||||
Permission,
|
||||
SlashCommand
|
||||
SlashCommand,
|
||||
User
|
||||
],
|
||||
actionsEnabled: true
|
||||
});
|
||||
|
@ -72,7 +74,7 @@ class DB {
|
|||
schema: serversSchema,
|
||||
migrations: serversMigrations
|
||||
}),
|
||||
modelClasses: [Server, User, ServersHistory],
|
||||
modelClasses: [Server, LoggedUser, ServersHistory],
|
||||
actionsEnabled: true
|
||||
})
|
||||
}
|
||||
|
@ -113,7 +115,8 @@ class DB {
|
|||
Upload,
|
||||
Permission,
|
||||
CustomEmoji,
|
||||
FrequentlyUsedEmoji
|
||||
FrequentlyUsedEmoji,
|
||||
User
|
||||
],
|
||||
actionsEnabled: true
|
||||
});
|
||||
|
|
|
@ -6,17 +6,13 @@ import { sanitizer } from '../utils';
|
|||
export default class User extends Model {
|
||||
static table = 'users';
|
||||
|
||||
@field('token') token;
|
||||
|
||||
@field('username') username;
|
||||
@field('_id') _id;
|
||||
|
||||
@field('name') name;
|
||||
|
||||
@field('language') language;
|
||||
@field('username') username;
|
||||
|
||||
@field('status') status;
|
||||
|
||||
@field('statusText') statusText;
|
||||
@field('avatar_etag') avatarETag;
|
||||
|
||||
@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({
|
||||
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';
|
||||
|
||||
export default appSchema({
|
||||
version: 10,
|
||||
version: 11,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'subscriptions',
|
||||
|
@ -247,6 +247,15 @@ export default appSchema({
|
|||
{ name: 'provides_preview', type: 'boolean', 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';
|
||||
|
||||
export default appSchema({
|
||||
version: 9,
|
||||
version: 10,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'users',
|
||||
|
@ -13,7 +13,8 @@ export default appSchema({
|
|||
{ name: 'status', type: 'string', isOptional: true },
|
||||
{ name: 'statusText', 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({
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { InteractionManager } from 'react-native';
|
||||
import semver from 'semver';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
|
||||
import reduxStore from '../createStore';
|
||||
import { setActiveUsers } from '../../actions/activeUsers';
|
||||
import { setUser } from '../../actions/login';
|
||||
import database from '../database';
|
||||
|
||||
export function subscribeUsersPresence() {
|
||||
const serverVersion = reduxStore.getState().server.version;
|
||||
|
@ -20,6 +22,11 @@ export function subscribeUsersPresence() {
|
|||
} else {
|
||||
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 = [];
|
||||
|
@ -46,7 +53,9 @@ export default async function getUsersPresence() {
|
|||
// RC 1.1.0
|
||||
const result = await this.sdk.get('users.presence', params);
|
||||
if (result.success) {
|
||||
const activeUsers = result.users.reduce((ret, item) => {
|
||||
const { users } = result;
|
||||
|
||||
const activeUsers = users.reduce((ret, item) => {
|
||||
const { _id, status, statusText } = item;
|
||||
|
||||
if (loggedUser && loggedUser.id === _id) {
|
||||
|
@ -60,6 +69,27 @@ export default async function getUsersPresence() {
|
|||
reduxStore.dispatch(setActiveUsers(activeUsers));
|
||||
});
|
||||
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 {
|
||||
// do nothing
|
||||
|
@ -80,5 +110,7 @@ export function getUserPresence(uid) {
|
|||
}, 2000);
|
||||
}
|
||||
|
||||
ids.push(uid);
|
||||
if (uid) {
|
||||
ids.push(uid);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
} from '@rocket.chat/sdk';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
|
||||
import reduxStore from './createStore';
|
||||
|
@ -244,9 +245,9 @@ const RocketChat = {
|
|||
|
||||
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;
|
||||
if (eventName === 'user-status') {
|
||||
if (/user-status/.test(eventName)) {
|
||||
this.activeUsers = this.activeUsers || {};
|
||||
if (!this._setUserTimer) {
|
||||
this._setUserTimer = setTimeout(() => {
|
||||
|
@ -266,6 +267,40 @@ const RocketChat = {
|
|||
if (loggedUser && loggedUser.id === id) {
|
||||
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,
|
||||
emails: result.me.emails,
|
||||
roles: result.me.roles,
|
||||
avatarETag: result.me.avatarETag,
|
||||
loginEmailPassword
|
||||
};
|
||||
return user;
|
||||
|
|
|
@ -18,7 +18,7 @@ const DirectoryItemLabel = React.memo(({ text, theme }) => {
|
|||
});
|
||||
|
||||
const DirectoryItem = ({
|
||||
title, description, avatar, onPress, testID, style, baseUrl, user, rightLabel, type, theme
|
||||
title, description, avatar, onPress, testID, style, rightLabel, type, theme
|
||||
}) => (
|
||||
<Touch
|
||||
onPress={onPress}
|
||||
|
@ -32,9 +32,6 @@ const DirectoryItem = ({
|
|||
size={30}
|
||||
type={type}
|
||||
style={styles.directoryItemAvatar}
|
||||
baseUrl={baseUrl}
|
||||
userId={user.id}
|
||||
token={user.token}
|
||||
/>
|
||||
<View style={styles.directoryItemTextContainer}>
|
||||
<View style={styles.directoryItemTextTitle}>
|
||||
|
@ -53,11 +50,6 @@ DirectoryItem.propTypes = {
|
|||
description: PropTypes.string,
|
||||
avatar: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
token: PropTypes.string
|
||||
}),
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
testID: PropTypes.string.isRequired,
|
||||
style: PropTypes.any,
|
||||
|
|
|
@ -45,7 +45,8 @@ const RoomItem = ({
|
|||
onPress,
|
||||
toggleFav,
|
||||
toggleRead,
|
||||
hideChannel
|
||||
hideChannel,
|
||||
avatarETag
|
||||
}) => (
|
||||
<Touchable
|
||||
onPress={onPress}
|
||||
|
@ -66,6 +67,7 @@ const RoomItem = ({
|
|||
accessibilityLabel={accessibilityLabel}
|
||||
avatar={avatar}
|
||||
avatarSize={avatarSize}
|
||||
avatarETag={avatarETag}
|
||||
type={type}
|
||||
baseUrl={baseUrl}
|
||||
userId={userId}
|
||||
|
@ -178,7 +180,8 @@ RoomItem.propTypes = {
|
|||
toggleFav: PropTypes.func,
|
||||
toggleRead: PropTypes.func,
|
||||
onPress: PropTypes.func,
|
||||
hideChannel: PropTypes.func
|
||||
hideChannel: PropTypes.func,
|
||||
avatarETag: PropTypes.string
|
||||
};
|
||||
|
||||
RoomItem.defaultProps = {
|
||||
|
|
|
@ -4,12 +4,13 @@ import PropTypes from 'prop-types';
|
|||
|
||||
import styles from './styles';
|
||||
import { themes } from '../../constants/colors';
|
||||
import Avatar from '../../containers/Avatar';
|
||||
import Avatar from '../../containers/Avatar/Avatar';
|
||||
|
||||
const RoomItemInner = ({
|
||||
const Wrapper = ({
|
||||
accessibilityLabel,
|
||||
avatar,
|
||||
avatarSize,
|
||||
avatarETag,
|
||||
type,
|
||||
baseUrl,
|
||||
userId,
|
||||
|
@ -25,10 +26,10 @@ const RoomItemInner = ({
|
|||
text={avatar}
|
||||
size={avatarSize}
|
||||
type={type}
|
||||
baseUrl={baseUrl}
|
||||
style={styles.avatar}
|
||||
userId={userId}
|
||||
token={token}
|
||||
server={baseUrl}
|
||||
user={{ id: userId, token }}
|
||||
avatarETag={avatarETag}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
|
@ -43,10 +44,11 @@ const RoomItemInner = ({
|
|||
</View>
|
||||
);
|
||||
|
||||
RoomItemInner.propTypes = {
|
||||
Wrapper.propTypes = {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
avatar: PropTypes.string,
|
||||
avatarSize: PropTypes.number,
|
||||
avatarETag: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
baseUrl: PropTypes.string,
|
||||
userId: PropTypes.string,
|
||||
|
@ -55,4 +57,4 @@ RoomItemInner.propTypes = {
|
|||
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 { connect } from 'react-redux';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
import { ROW_HEIGHT } from './styles';
|
||||
import { formatDate } from '../../utils/room';
|
||||
import database from '../../lib/database';
|
||||
import RoomItem from './RoomItem';
|
||||
|
||||
export { ROW_HEIGHT };
|
||||
|
@ -19,156 +20,212 @@ const attrs = [
|
|||
'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(({
|
||||
item,
|
||||
onPress,
|
||||
width,
|
||||
toggleFav,
|
||||
toggleRead,
|
||||
hideChannel,
|
||||
testID,
|
||||
avatarSize,
|
||||
baseUrl,
|
||||
userId,
|
||||
username,
|
||||
token,
|
||||
id,
|
||||
showLastMessage,
|
||||
status,
|
||||
useRealName,
|
||||
getUserPresence,
|
||||
connected,
|
||||
theme,
|
||||
isFocused,
|
||||
getRoomTitle,
|
||||
getRoomAvatar,
|
||||
getIsGroupChat,
|
||||
getIsRead,
|
||||
swipeEnabled
|
||||
}) => {
|
||||
const [, setForceUpdate] = useState(1);
|
||||
static defaultProps = {
|
||||
avatarSize: 48,
|
||||
status: 'offline',
|
||||
getUserPresence: () => {},
|
||||
getRoomTitle: () => 'title',
|
||||
getRoomAvatar: () => '',
|
||||
getIsGroupChat: () => false,
|
||||
getIsRead: () => false,
|
||||
swipeEnabled: true
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (connected && item.t === 'd' && id) {
|
||||
constructor(props) {
|
||||
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);
|
||||
}
|
||||
}, [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) {
|
||||
const observable = item.observe();
|
||||
const subscription = observable?.subscribe?.(() => {
|
||||
setForceUpdate(prevForceUpdate => prevForceUpdate + 1);
|
||||
this.roomSubscription = observable?.subscribe?.(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription?.unsubscribe?.();
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
const name = getRoomTitle(item);
|
||||
const avatar = getRoomAvatar(item);
|
||||
const isGroupChat = getIsGroupChat(item);
|
||||
const isRead = getIsRead(item);
|
||||
const _onPress = () => onPress(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 (this.isDirect) {
|
||||
const { id } = this.props;
|
||||
const db = database.active;
|
||||
const usersCollection = db.collections.get('users');
|
||||
try {
|
||||
const user = await usersCollection.find(id);
|
||||
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 not found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.userMentions > 0) {
|
||||
accessibilityLabel += `, ${ I18n.t('you_were_mentioned') }`;
|
||||
onPress = () => {
|
||||
const { item, onPress } = this.props;
|
||||
return onPress(item);
|
||||
}
|
||||
|
||||
if (date) {
|
||||
accessibilityLabel += `, ${ I18n.t('last_message') } ${ date }`;
|
||||
render() {
|
||||
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) => {
|
||||
let status = 'offline';
|
||||
|
|
|
@ -42,7 +42,7 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
const UserItem = ({
|
||||
name, username, onPress, testID, onLongPress, style, icon, baseUrl, user, theme
|
||||
name, username, onPress, testID, onLongPress, style, icon, theme
|
||||
}) => (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
|
@ -58,7 +58,7 @@ const UserItem = ({
|
|||
})}
|
||||
>
|
||||
<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}>
|
||||
<Text style={[styles.name, { color: themes[theme].titleText }]} numberOfLines={1}>{name}</Text>
|
||||
<Text style={[styles.username, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>@{username}</Text>
|
||||
|
@ -71,11 +71,6 @@ const UserItem = ({
|
|||
UserItem.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
token: PropTypes.string
|
||||
}),
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
testID: PropTypes.string.isRequired,
|
||||
onLongPress: PropTypes.func,
|
||||
|
|
|
@ -178,7 +178,8 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
|||
status: user.status,
|
||||
statusText: user.statusText,
|
||||
roles: user.roles,
|
||||
loginEmailPassword: user.loginEmailPassword
|
||||
loginEmailPassword: user.loginEmailPassword,
|
||||
avatarETag: user.avatarETag
|
||||
};
|
||||
yield serversDB.action(async() => {
|
||||
try {
|
||||
|
|
|
@ -87,7 +87,8 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
|||
language: userRecord.language,
|
||||
status: userRecord.status,
|
||||
statusText: userRecord.statusText,
|
||||
roles: userRecord.roles
|
||||
roles: userRecord.roles,
|
||||
avatarETag: userRecord.avatarETag
|
||||
};
|
||||
} catch {
|
||||
// search credentials on shared credentials (Experimental/Official)
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => (
|
||||
`${ baseUrl }${ url }?format=png&size=${ uriSize }&${ avatarAuthURLFragment }`
|
||||
);
|
||||
const formatUrl = (url, size, query) => `${ url }?format=png&size=${ size }${ query }`;
|
||||
|
||||
export const avatarURL = ({
|
||||
type, text, size, userId, token, avatar, baseUrl
|
||||
type, text, size, user = {}, avatar, server, avatarETag
|
||||
}) => {
|
||||
const room = type === 'd' ? text : `@${ text }`;
|
||||
|
||||
// Avoid requesting several sizes by having only two sizes on cache
|
||||
const uriSize = size === 100 ? 100 : 50;
|
||||
|
||||
let avatarAuthURLFragment = '';
|
||||
if (userId && token) {
|
||||
avatarAuthURLFragment = `&rc_token=${ token }&rc_uid=${ userId }`;
|
||||
const { id, token } = user;
|
||||
let query = '';
|
||||
if (id && token) {
|
||||
query += `&rc_token=${ token }&rc_uid=${ id }`;
|
||||
}
|
||||
if (avatarETag) {
|
||||
query += `&etag=${ avatarETag }`;
|
||||
}
|
||||
|
||||
|
||||
let uri;
|
||||
if (avatar) {
|
||||
uri = avatar.includes('http') ? avatar : formatUrl(avatar, baseUrl, uriSize, avatarAuthURLFragment);
|
||||
} else {
|
||||
uri = formatUrl(`/avatar/${ room }`, baseUrl, uriSize, avatarAuthURLFragment);
|
||||
if (avatar.startsWith('http')) {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
return formatUrl(`${ server }${ avatar }`, uriSize, query);
|
||||
}
|
||||
|
||||
return uri;
|
||||
return formatUrl(`${ server }/avatar/${ room }`, uriSize, query);
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@ const SelectChannel = ({
|
|||
}, 300);
|
||||
|
||||
const getAvatar = (text, type) => avatarURL({
|
||||
text, type, userId, token, baseUrl: server
|
||||
text, type, user: { id: userId, token }, server
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -2,10 +2,12 @@ import React, { useState } from 'react';
|
|||
import { Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
||||
import debounce from '../../utils/debounce';
|
||||
import { avatarURL } from '../../utils/avatar';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import database from '../../lib/database';
|
||||
import I18n from '../../i18n';
|
||||
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
||||
|
||||
|
@ -19,15 +21,34 @@ const SelectUsers = ({
|
|||
|
||||
const getUsers = debounce(async(keyword = '') => {
|
||||
try {
|
||||
const db = database.active;
|
||||
const usersCollection = db.collections.get('users');
|
||||
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 {
|
||||
// do nothing
|
||||
}
|
||||
}, 300);
|
||||
|
||||
const getAvatar = text => avatarURL({
|
||||
text, type: 'd', userId, token, baseUrl: server
|
||||
const getAvatar = item => avatarURL({
|
||||
text: RocketChat.getRoomAvatar(item), type: 'd', user: { id: userId, token }, server, avatarETag: item.avatarETag
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -41,7 +62,7 @@ const SelectUsers = ({
|
|||
options={users.map(user => ({
|
||||
value: user.name,
|
||||
text: { text: RocketChat.getRoomTitle(user) },
|
||||
imageUrl: getAvatar(RocketChat.getRoomAvatar(user))
|
||||
imageUrl: getAvatar(user)
|
||||
}))}
|
||||
onClose={() => setUsers(users.filter(u => selected.includes(u.name)))}
|
||||
placeholder={{ text: `${ I18n.t('Select_Users') }...` }}
|
||||
|
|
|
@ -6,7 +6,7 @@ import prompt from 'react-native-prompt-android';
|
|||
import SHA256 from 'js-sha256';
|
||||
import ImagePicker from 'react-native-image-crop-picker';
|
||||
import RNPickerSelect from 'react-native-picker-select';
|
||||
import equal from 'deep-equal';
|
||||
import { isEqual, omit } from 'lodash';
|
||||
|
||||
import Touch from '../../utils/touch';
|
||||
import KeyboardView from '../../presentation/KeyboardView';
|
||||
|
@ -84,16 +84,22 @@ class ProfileView extends React.Component {
|
|||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (!equal(nextState, this.state)) {
|
||||
if (!isEqual(nextState, this.state)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps, this.props)) {
|
||||
if (!isEqual(nextProps, this.props)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -324,7 +330,6 @@ class ProfileView extends React.Component {
|
|||
const { avatarUrl, avatarSuggestions } = this.state;
|
||||
const {
|
||||
user,
|
||||
baseUrl,
|
||||
theme,
|
||||
Accounts_AllowUserAvatarChange
|
||||
} = this.props;
|
||||
|
@ -332,7 +337,7 @@ class ProfileView extends React.Component {
|
|||
return (
|
||||
<View style={styles.avatarButtons}>
|
||||
{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(),
|
||||
disabled: !Accounts_AllowUserAvatarChange,
|
||||
key: 'profile-view-reset-avatar'
|
||||
|
@ -354,7 +359,7 @@ class ProfileView extends React.Component {
|
|||
return this.renderAvatarButton({
|
||||
disabled: !Accounts_AllowUserAvatarChange,
|
||||
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({
|
||||
url, data: blob, service, contentType
|
||||
})
|
||||
|
@ -448,7 +453,6 @@ class ProfileView extends React.Component {
|
|||
name, username, email, newPassword, avatarUrl, customFields, avatar, saving
|
||||
} = this.state;
|
||||
const {
|
||||
baseUrl,
|
||||
user,
|
||||
theme,
|
||||
Accounts_AllowEmailChange,
|
||||
|
@ -474,12 +478,10 @@ class ProfileView extends React.Component {
|
|||
>
|
||||
<View style={styles.avatarContainer} testID='profile-view-avatar'>
|
||||
<Avatar
|
||||
text={username}
|
||||
avatar={avatar && avatar.url}
|
||||
text={user.username}
|
||||
avatar={avatar?.url}
|
||||
isStatic={avatar?.url}
|
||||
size={100}
|
||||
baseUrl={baseUrl}
|
||||
userId={user.id}
|
||||
token={user.token}
|
||||
/>
|
||||
</View>
|
||||
<RCTextInput
|
||||
|
|
|
@ -14,7 +14,6 @@ import RocketChat from '../../lib/rocketchat';
|
|||
import StatusBar from '../../containers/StatusBar';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
class ReadReceiptView extends React.Component {
|
||||
|
@ -31,8 +30,6 @@ class ReadReceiptView extends React.Component {
|
|||
static propTypes = {
|
||||
route: PropTypes.object,
|
||||
Message_TimeFormat: PropTypes.string,
|
||||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
}
|
||||
|
||||
|
@ -96,9 +93,7 @@ class ReadReceiptView extends React.Component {
|
|||
}
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
const {
|
||||
Message_TimeFormat, user: { id: userId, token }, baseUrl, theme
|
||||
} = this.props;
|
||||
const { Message_TimeFormat, theme } = this.props;
|
||||
const time = moment(item.ts).format(Message_TimeFormat);
|
||||
if (!item?.user?.username) {
|
||||
return null;
|
||||
|
@ -108,9 +103,6 @@ class ReadReceiptView extends React.Component {
|
|||
<Avatar
|
||||
text={item.user.username}
|
||||
size={40}
|
||||
baseUrl={baseUrl}
|
||||
userId={userId}
|
||||
token={token}
|
||||
/>
|
||||
<View style={styles.infoContainer}>
|
||||
<View style={styles.item}>
|
||||
|
@ -168,9 +160,7 @@ class ReadReceiptView extends React.Component {
|
|||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||
baseUrl: state.server.server,
|
||||
user: getUserSelector(state)
|
||||
Message_TimeFormat: state.settings.Message_TimeFormat
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(ReadReceiptView));
|
||||
|
|
|
@ -670,7 +670,7 @@ class RoomActionsView extends React.Component {
|
|||
renderRoomInfo = ({ item }) => {
|
||||
const { room, member } = this.state;
|
||||
const { name, t, topic } = room;
|
||||
const { baseUrl, user, theme } = this.props;
|
||||
const { theme } = this.props;
|
||||
|
||||
const avatar = RocketChat.getRoomAvatar(room);
|
||||
|
||||
|
@ -679,12 +679,9 @@ class RoomActionsView extends React.Component {
|
|||
<>
|
||||
<Avatar
|
||||
text={avatar}
|
||||
size={50}
|
||||
style={styles.avatar}
|
||||
size={50}
|
||||
type={t}
|
||||
baseUrl={baseUrl}
|
||||
userId={user.id}
|
||||
token={user.token}
|
||||
>
|
||||
{t === 'd' && member._id ? <Status style={sharedStyles.status} id={member._id} /> : null }
|
||||
</Avatar>
|
||||
|
|
|
@ -20,7 +20,6 @@ import StatusBar from '../../containers/StatusBar';
|
|||
import log, { logEvent, events } from '../../utils/log';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import Markdown from '../../containers/markdown';
|
||||
import { LISTENER } from '../../containers/Toast';
|
||||
import EventEmitter from '../../utils/events';
|
||||
|
@ -54,11 +53,6 @@ class RoomInfoView extends React.Component {
|
|||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
token: PropTypes.string
|
||||
}),
|
||||
baseUrl: PropTypes.string,
|
||||
rooms: PropTypes.array,
|
||||
theme: PropTypes.string,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
|
@ -287,17 +281,14 @@ class RoomInfoView extends React.Component {
|
|||
}
|
||||
|
||||
renderAvatar = (room, roomUser) => {
|
||||
const { baseUrl, user, theme } = this.props;
|
||||
const { theme } = this.props;
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
text={room.name || roomUser.username}
|
||||
size={100}
|
||||
style={styles.avatar}
|
||||
type={this.t}
|
||||
baseUrl={baseUrl}
|
||||
userId={user.id}
|
||||
token={user.token}
|
||||
size={100}
|
||||
>
|
||||
{this.t === 'd' && roomUser._id ? <Status style={[sharedStyles.status, styles.status]} theme={theme} size={24} id={roomUser._id} /> : null}
|
||||
</Avatar>
|
||||
|
@ -377,8 +368,6 @@ class RoomInfoView extends React.Component {
|
|||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
baseUrl: state.server.server,
|
||||
user: getUserSelector(state),
|
||||
rooms: state.room.rooms,
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
jitsiEnabled: state.settings.Jitsi_Enabled || false
|
||||
|
|
|
@ -32,16 +32,14 @@ const LeftButtons = React.memo(({
|
|||
);
|
||||
}
|
||||
const onPress = useCallback(() => goRoomActionsView(), []);
|
||||
|
||||
if (baseUrl && userId && token) {
|
||||
return (
|
||||
<Avatar
|
||||
text={title}
|
||||
size={30}
|
||||
type={t}
|
||||
baseUrl={baseUrl}
|
||||
style={styles.avatar}
|
||||
userId={userId}
|
||||
token={token}
|
||||
onPress={onPress}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import isEqual from 'react-fast-compare';
|
||||
|
||||
import Avatar from '../../containers/Avatar';
|
||||
import Status from '../../containers/Status/Status';
|
||||
|
@ -90,19 +91,8 @@ class Sidebar extends Component {
|
|||
if (nextProps.theme !== theme) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.user && user) {
|
||||
if (nextProps.user.language !== user.language) {
|
||||
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 (!isEqual(nextProps.user, user)) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.isMasterDetail !== isMasterDetail) {
|
||||
return true;
|
||||
|
@ -241,11 +231,8 @@ class Sidebar extends Component {
|
|||
<View style={styles.header} theme={theme}>
|
||||
<Avatar
|
||||
text={user.username}
|
||||
size={30}
|
||||
style={styles.avatar}
|
||||
baseUrl={baseUrl}
|
||||
userId={user.id}
|
||||
token={user.token}
|
||||
size={30}
|
||||
/>
|
||||
<View style={styles.headerTextContainer}>
|
||||
<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-orientation-locker": "1.1.8",
|
||||
"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-progress": "4.1.2",
|
||||
"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 UiKitModal from './UiKitModal';
|
||||
import Markdown from './Markdown';
|
||||
import Avatar from './Avatar';
|
||||
// import RoomViewHeader from './RoomViewHeader';
|
||||
|
||||
import MessageContext from '../../app/containers/message/Context';
|
||||
|
@ -70,6 +71,8 @@ storiesOf('UiKitModal', module)
|
|||
.add('list UiKitModal', () => <UiKitModal theme={theme} />);
|
||||
storiesOf('Markdown', module)
|
||||
.add('list Markdown', () => <Markdown theme={theme} />);
|
||||
storiesOf('Avatar', module)
|
||||
.add('list Avatar', () => <Avatar theme={theme} />);
|
||||
|
||||
// FIXME: I couldn't make these pass on jest :(
|
||||
// storiesOf('RoomViewHeader', module)
|
||||
|
|
|
@ -12891,7 +12891,7 @@ react-native-picker-select@7.0.0:
|
|||
dependencies:
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/react-native-platform-touchable/-/react-native-platform-touchable-1.1.1.tgz#fde4acc65eea585d28b164d0c3716a42129a68e4"
|
||||
integrity sha1-/eSsxl7qWF0osWTQw3FqQhKaaOQ=
|
||||
|
|
Loading…
Reference in New Issue