[NEW] Channel avatars (#2504)
* [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 * Format Avatar URL to use RoomId. Co-authored-by: Ezequiel De Oliveira <ezequiel1de1oliveira@gmail.com> * edit room avatar * invalidate cache of room images * reinit avatar if something change * read avatar cache on search * room avatar changed system message * add avatar by rid test * update snapshot * etag cache on select channel * reset room avatar * increase caching to have a better image quality * fix lgtm warn * invalidate ci cache * get avatar etag on select users of create discussion * invalidate ci cache * Fix migration * Fix sidebar avatar not updating * Remove outdated comment * Tests Co-authored-by: Prateek93a <prateek93a@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> Co-authored-by: Ezequiel De Oliveira <ezequiel1de1oliveira@gmail.com>
This commit is contained in:
parent
734039191f
commit
46e3db97e8
|
@ -59,7 +59,72 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"headers": undefined,
|
"headers": undefined,
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=50",
|
"uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=100",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"bottom": 0,
|
||||||
|
"left": 0,
|
||||||
|
"position": "absolute",
|
||||||
|
"right": 0,
|
||||||
|
"top": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Text
|
||||||
|
style={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"fontSize": 20,
|
||||||
|
"fontWeight": "300",
|
||||||
|
"marginLeft": 10,
|
||||||
|
"marginVertical": 30,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"color": "#0d0e12",
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Avatar by roomId
|
||||||
|
</Text>
|
||||||
|
<View
|
||||||
|
style={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"borderRadius": 4,
|
||||||
|
"height": 56,
|
||||||
|
"width": 56,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"overflow": "hidden",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"borderRadius": 4,
|
||||||
|
"height": 56,
|
||||||
|
"width": 56,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FastImageView
|
||||||
|
resizeMode="cover"
|
||||||
|
source={
|
||||||
|
Object {
|
||||||
|
"headers": undefined,
|
||||||
|
"priority": "high",
|
||||||
|
"uri": "https://open.rocket.chat/avatar/room/devWBbYr7inwupPqK?format=png&size=100",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style={
|
style={
|
||||||
|
@ -189,7 +254,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"headers": undefined,
|
"headers": undefined,
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=50",
|
"uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=100",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style={
|
style={
|
||||||
|
@ -254,7 +319,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"headers": undefined,
|
"headers": undefined,
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"uri": "https://open.rocket.chat/avatar/djorkaeff.alexandre?format=png&size=50&etag=5ag8KffJcZj9m5rCv",
|
"uri": "https://open.rocket.chat/avatar/djorkaeff.alexandre?format=png&size=100&etag=5ag8KffJcZj9m5rCv",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style={
|
style={
|
||||||
|
@ -319,7 +384,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"headers": undefined,
|
"headers": undefined,
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"uri": "https://open.rocket.chat/avatar/djorkaeff.alexandre?format=png&size=50",
|
"uri": "https://open.rocket.chat/avatar/djorkaeff.alexandre?format=png&size=100",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style={
|
style={
|
||||||
|
@ -454,7 +519,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"headers": undefined,
|
"headers": undefined,
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=50",
|
"uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=100",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style={
|
style={
|
||||||
|
@ -519,7 +584,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"headers": undefined,
|
"headers": undefined,
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"uri": "https://open.rocket.chat/avatar/@general?format=png&size=50",
|
"uri": "https://open.rocket.chat/avatar/@general?format=png&size=100",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style={
|
style={
|
||||||
|
@ -679,7 +744,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"headers": undefined,
|
"headers": undefined,
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=50",
|
"uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=100",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style={
|
style={
|
||||||
|
@ -744,7 +809,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"headers": undefined,
|
"headers": undefined,
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=50",
|
"uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=100",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style={
|
style={
|
||||||
|
@ -835,7 +900,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"headers": undefined,
|
"headers": undefined,
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"uri": "https://google.com/avatar/Avatar?format=png&size=50",
|
"uri": "https://google.com/avatar/Avatar?format=png&size=100",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style={
|
style={
|
||||||
|
@ -902,7 +967,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"headers": undefined,
|
"headers": undefined,
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=50",
|
"uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=100",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style={
|
style={
|
||||||
|
|
|
@ -23,9 +23,10 @@ const Avatar = React.memo(({
|
||||||
theme,
|
theme,
|
||||||
getCustomEmoji,
|
getCustomEmoji,
|
||||||
avatarETag,
|
avatarETag,
|
||||||
isStatic
|
isStatic,
|
||||||
|
rid
|
||||||
}) => {
|
}) => {
|
||||||
if ((!text && !avatar && !emoji) || !server) {
|
if ((!text && !avatar && !emoji && !rid) || !server) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +58,8 @@ const Avatar = React.memo(({
|
||||||
user,
|
user,
|
||||||
avatar,
|
avatar,
|
||||||
server,
|
server,
|
||||||
avatarETag
|
avatarETag,
|
||||||
|
rid
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +110,8 @@ Avatar.propTypes = {
|
||||||
onPress: PropTypes.func,
|
onPress: PropTypes.func,
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
avatarETag: PropTypes.string,
|
avatarETag: PropTypes.string,
|
||||||
isStatic: PropTypes.bool
|
isStatic: PropTypes.bool,
|
||||||
|
rid: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
Avatar.defaultProps = {
|
Avatar.defaultProps = {
|
||||||
|
|
|
@ -2,6 +2,7 @@ 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 { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
import isEqual from 'react-fast-compare';
|
||||||
|
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
|
@ -9,6 +10,7 @@ import Avatar from './Avatar';
|
||||||
|
|
||||||
class AvatarContainer extends React.Component {
|
class AvatarContainer extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
rid: PropTypes.string,
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
type: PropTypes.string
|
type: PropTypes.string
|
||||||
};
|
};
|
||||||
|
@ -29,9 +31,15 @@ class AvatarContainer extends React.Component {
|
||||||
this.mounted = true;
|
this.mounted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (!isEqual(prevProps, this.props)) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.userSubscription?.unsubscribe) {
|
if (this.subscription?.unsubscribe) {
|
||||||
this.userSubscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,26 +49,34 @@ class AvatarContainer extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
init = async() => {
|
init = async() => {
|
||||||
if (this.isDirect) {
|
const db = database.active;
|
||||||
const { text } = this.props;
|
const usersCollection = db.collections.get('users');
|
||||||
const db = database.active;
|
const subsCollection = db.collections.get('subscriptions');
|
||||||
const usersCollection = db.collections.get('users');
|
|
||||||
try {
|
let record;
|
||||||
|
try {
|
||||||
|
if (this.isDirect) {
|
||||||
|
const { text } = this.props;
|
||||||
const [user] = await usersCollection.query(Q.where('username', text)).fetch();
|
const [user] = await usersCollection.query(Q.where('username', text)).fetch();
|
||||||
if (user) {
|
record = user;
|
||||||
const observable = user.observe();
|
} else {
|
||||||
this.userSubscription = observable.subscribe((u) => {
|
const { rid } = this.props;
|
||||||
const { avatarETag } = u;
|
record = await subsCollection.find(rid);
|
||||||
if (this.mounted) {
|
|
||||||
this.setState({ avatarETag });
|
|
||||||
} else {
|
|
||||||
this.state.avatarETag = avatarETag;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// User was not found
|
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
// Record not found
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record) {
|
||||||
|
const observable = record.observe();
|
||||||
|
this.subscription = observable.subscribe((r) => {
|
||||||
|
const { avatarETag } = r;
|
||||||
|
if (this.mounted) {
|
||||||
|
this.setState({ avatarETag });
|
||||||
|
} else {
|
||||||
|
this.state.avatarETag = avatarETag;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,13 +70,13 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }) => {
|
||||||
const { isLandscape } = useOrientation();
|
const { isLandscape } = useOrientation();
|
||||||
|
|
||||||
const { text, payload } = notification;
|
const { text, payload } = notification;
|
||||||
const { type } = payload;
|
const { type, rid } = payload;
|
||||||
const name = type === 'd' ? payload.sender.username : payload.name;
|
const name = type === 'd' ? payload.sender.username : payload.name;
|
||||||
// if sub is not on local database, title and avatar will be null, so we use payload from notification
|
// if sub is not on local database, title and avatar will be null, so we use payload from notification
|
||||||
const { title = name, avatar = name } = notification;
|
const { title = name, avatar = name } = notification;
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
const { rid, prid } = payload;
|
const { prid } = payload;
|
||||||
if (!rid) {
|
if (!rid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }) => {
|
||||||
background={Touchable.SelectableBackgroundBorderless()}
|
background={Touchable.SelectableBackgroundBorderless()}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<Avatar text={avatar} size={AVATAR_SIZE} type={type} style={styles.avatar} />
|
<Avatar text={avatar} size={AVATAR_SIZE} type={type} rid={rid} 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>
|
||||||
|
|
|
@ -49,6 +49,7 @@ export const SYSTEM_MESSAGES = [
|
||||||
'room_changed_announcement',
|
'room_changed_announcement',
|
||||||
'room_changed_topic',
|
'room_changed_topic',
|
||||||
'room_changed_privacy',
|
'room_changed_privacy',
|
||||||
|
'room_changed_avatar',
|
||||||
'message_snippeted',
|
'message_snippeted',
|
||||||
'thread-created'
|
'thread-created'
|
||||||
];
|
];
|
||||||
|
@ -91,6 +92,8 @@ export const getInfoMessage = ({
|
||||||
return I18n.t('Room_changed_topic', { topic: msg, userBy: username });
|
return I18n.t('Room_changed_topic', { topic: msg, userBy: username });
|
||||||
} else if (type === 'room_changed_privacy') {
|
} else if (type === 'room_changed_privacy') {
|
||||||
return I18n.t('Room_changed_privacy', { type: msg, userBy: username });
|
return I18n.t('Room_changed_privacy', { type: msg, userBy: username });
|
||||||
|
} else if (type === 'room_changed_avatar') {
|
||||||
|
return I18n.t('Room_changed_avatar', { userBy: username });
|
||||||
} else if (type === 'message_snippeted') {
|
} else if (type === 'message_snippeted') {
|
||||||
return I18n.t('Created_snippet');
|
return I18n.t('Created_snippet');
|
||||||
}
|
}
|
||||||
|
|
|
@ -437,6 +437,7 @@ export default {
|
||||||
Roles: 'Roles',
|
Roles: 'Roles',
|
||||||
Room_actions: 'Room actions',
|
Room_actions: 'Room actions',
|
||||||
Room_changed_announcement: 'Room announcement changed to: {{announcement}} by {{userBy}}',
|
Room_changed_announcement: 'Room announcement changed to: {{announcement}} by {{userBy}}',
|
||||||
|
Room_changed_avatar: 'Room avatar changed by {{userBy}}',
|
||||||
Room_changed_description: 'Room description changed to: {{description}} by {{userBy}}',
|
Room_changed_description: 'Room description changed to: {{description}} by {{userBy}}',
|
||||||
Room_changed_privacy: 'Room type changed to: {{type}} by {{userBy}}',
|
Room_changed_privacy: 'Room type changed to: {{type}} by {{userBy}}',
|
||||||
Room_changed_topic: 'Room topic changed to: {{topic}} by {{userBy}}',
|
Room_changed_topic: 'Room topic changed to: {{topic}} by {{userBy}}',
|
||||||
|
|
|
@ -25,4 +25,6 @@ export default class Room extends Model {
|
||||||
@json('livechat_data', sanitizer) livechatData;
|
@json('livechat_data', sanitizer) livechatData;
|
||||||
|
|
||||||
@json('tags', sanitizer) tags;
|
@json('tags', sanitizer) tags;
|
||||||
|
|
||||||
|
@field('avatar_etag') avatarETag;
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,4 +115,6 @@ export default class Subscription extends Model {
|
||||||
@field('encrypted') encrypted;
|
@field('encrypted') encrypted;
|
||||||
|
|
||||||
@field('e2e_key_id') e2eKeyId;
|
@field('e2e_key_id') e2eKeyId;
|
||||||
|
|
||||||
|
@field('avatar_etag') avatarETag;
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,6 +178,18 @@ export default schemaMigrations({
|
||||||
{ name: 'username', type: 'string', isIndexed: true },
|
{ name: 'username', type: 'string', isIndexed: true },
|
||||||
{ name: 'avatar_etag', type: 'string', isOptional: true }
|
{ name: 'avatar_etag', type: 'string', isOptional: true }
|
||||||
]
|
]
|
||||||
|
}),
|
||||||
|
addColumns({
|
||||||
|
table: 'subscriptions',
|
||||||
|
columns: [
|
||||||
|
{ name: 'avatar_etag', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
addColumns({
|
||||||
|
table: 'rooms',
|
||||||
|
columns: [
|
||||||
|
{ name: 'avatar_etag', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,8 @@ export default appSchema({
|
||||||
{ name: 'tags', type: 'string', isOptional: true },
|
{ name: 'tags', type: 'string', isOptional: true },
|
||||||
{ name: 'e2e_key', type: 'string', isOptional: true },
|
{ name: 'e2e_key', type: 'string', isOptional: true },
|
||||||
{ name: 'encrypted', type: 'boolean', isOptional: true },
|
{ name: 'encrypted', type: 'boolean', isOptional: true },
|
||||||
{ name: 'e2e_key_id', type: 'string', isOptional: true }
|
{ name: 'e2e_key_id', type: 'string', isOptional: true },
|
||||||
|
{ name: 'avatar_etag', type: 'string', isOptional: true }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
tableSchema({
|
tableSchema({
|
||||||
|
@ -67,7 +68,8 @@ export default appSchema({
|
||||||
{ name: 'served_by', type: 'string', isOptional: true },
|
{ name: 'served_by', type: 'string', isOptional: true },
|
||||||
{ name: 'livechat_data', type: 'string', isOptional: true },
|
{ name: 'livechat_data', type: 'string', isOptional: true },
|
||||||
{ name: 'tags', type: 'string', isOptional: true },
|
{ name: 'tags', type: 'string', isOptional: true },
|
||||||
{ name: 'e2e_key_id', type: 'string', isOptional: true }
|
{ name: 'e2e_key_id', type: 'string', isOptional: true },
|
||||||
|
{ name: 'avatar_etag', type: 'string', isOptional: true }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
tableSchema({
|
tableSchema({
|
||||||
|
|
|
@ -52,7 +52,8 @@ export default async(subscriptions = [], rooms = []) => {
|
||||||
tags: s.tags,
|
tags: s.tags,
|
||||||
encrypted: s.encrypted,
|
encrypted: s.encrypted,
|
||||||
e2eKeyId: s.e2eKeyId,
|
e2eKeyId: s.e2eKeyId,
|
||||||
E2EKey: s.E2EKey
|
E2EKey: s.E2EKey,
|
||||||
|
avatarETag: s.avatarETag
|
||||||
}));
|
}));
|
||||||
subscriptions = subscriptions.concat(existingSubs);
|
subscriptions = subscriptions.concat(existingSubs);
|
||||||
|
|
||||||
|
@ -80,7 +81,8 @@ export default async(subscriptions = [], rooms = []) => {
|
||||||
livechatData: r.livechatData,
|
livechatData: r.livechatData,
|
||||||
tags: r.tags,
|
tags: r.tags,
|
||||||
encrypted: r.encrypted,
|
encrypted: r.encrypted,
|
||||||
e2eKeyId: r.e2eKeyId
|
e2eKeyId: r.e2eKeyId,
|
||||||
|
avatarETag: r.avatarETag
|
||||||
}));
|
}));
|
||||||
rooms = rooms.concat(existingRooms);
|
rooms = rooms.concat(existingRooms);
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -30,6 +30,7 @@ export const merge = (subscription, room) => {
|
||||||
subscription.broadcast = room.broadcast;
|
subscription.broadcast = room.broadcast;
|
||||||
subscription.encrypted = room.encrypted;
|
subscription.encrypted = room.encrypted;
|
||||||
subscription.e2eKeyId = room.e2eKeyId;
|
subscription.e2eKeyId = room.e2eKeyId;
|
||||||
|
subscription.avatarETag = room.avatarETag;
|
||||||
if (!subscription.roles || !subscription.roles.length) {
|
if (!subscription.roles || !subscription.roles.length) {
|
||||||
subscription.roles = [];
|
subscription.roles = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,8 @@ const createOrUpdateSubscription = async(subscription, room) => {
|
||||||
tags: s.tags,
|
tags: s.tags,
|
||||||
encrypted: s.encrypted,
|
encrypted: s.encrypted,
|
||||||
e2eKeyId: s.e2eKeyId,
|
e2eKeyId: s.e2eKeyId,
|
||||||
E2EKey: s.E2EKey
|
E2EKey: s.E2EKey,
|
||||||
|
avatarETag: s.avatarETag
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
try {
|
try {
|
||||||
|
@ -116,7 +117,8 @@ const createOrUpdateSubscription = async(subscription, room) => {
|
||||||
broadcast: r.broadcast,
|
broadcast: r.broadcast,
|
||||||
customFields: r.customFields,
|
customFields: r.customFields,
|
||||||
departmentId: r.departmentId,
|
departmentId: r.departmentId,
|
||||||
livechatData: r.livechatData
|
livechatData: r.livechatData,
|
||||||
|
avatarETag: r.avatarETag
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
|
|
@ -579,6 +579,7 @@ const RocketChat = {
|
||||||
rid: sub.rid,
|
rid: sub.rid,
|
||||||
name: sub.name,
|
name: sub.name,
|
||||||
fname: sub.fname,
|
fname: sub.fname,
|
||||||
|
avatarETag: sub.avatarETag,
|
||||||
t: sub.t,
|
t: sub.t,
|
||||||
search: true
|
search: true
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,7 @@ const DirectoryItemLabel = React.memo(({ text, theme }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const DirectoryItem = ({
|
const DirectoryItem = ({
|
||||||
title, description, avatar, onPress, testID, style, rightLabel, type, theme
|
title, description, avatar, onPress, testID, style, rightLabel, type, rid, theme
|
||||||
}) => (
|
}) => (
|
||||||
<Touch
|
<Touch
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
|
@ -31,6 +31,7 @@ const DirectoryItem = ({
|
||||||
text={avatar}
|
text={avatar}
|
||||||
size={30}
|
size={30}
|
||||||
type={type}
|
type={type}
|
||||||
|
rid={rid}
|
||||||
style={styles.directoryItemAvatar}
|
style={styles.directoryItemAvatar}
|
||||||
/>
|
/>
|
||||||
<View style={styles.directoryItemTextContainer}>
|
<View style={styles.directoryItemTextContainer}>
|
||||||
|
@ -54,6 +55,7 @@ DirectoryItem.propTypes = {
|
||||||
testID: PropTypes.string.isRequired,
|
testID: PropTypes.string.isRequired,
|
||||||
style: PropTypes.any,
|
style: PropTypes.any,
|
||||||
rightLabel: PropTypes.string,
|
rightLabel: PropTypes.string,
|
||||||
|
rid: PropTypes.string,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,7 @@ const RoomItem = ({
|
||||||
userId={userId}
|
userId={userId}
|
||||||
token={token}
|
token={token}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
rid={rid}
|
||||||
>
|
>
|
||||||
{showLastMessage
|
{showLastMessage
|
||||||
? (
|
? (
|
||||||
|
|
|
@ -16,6 +16,7 @@ const Wrapper = ({
|
||||||
userId,
|
userId,
|
||||||
token,
|
token,
|
||||||
theme,
|
theme,
|
||||||
|
rid,
|
||||||
children
|
children
|
||||||
}) => (
|
}) => (
|
||||||
<View
|
<View
|
||||||
|
@ -30,6 +31,7 @@ const Wrapper = ({
|
||||||
server={baseUrl}
|
server={baseUrl}
|
||||||
user={{ id: userId, token }}
|
user={{ id: userId, token }}
|
||||||
avatarETag={avatarETag}
|
avatarETag={avatarETag}
|
||||||
|
rid={rid}
|
||||||
/>
|
/>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
|
@ -54,6 +56,7 @@ Wrapper.propTypes = {
|
||||||
userId: PropTypes.string,
|
userId: PropTypes.string,
|
||||||
token: PropTypes.string,
|
token: PropTypes.string,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
|
rid: PropTypes.string,
|
||||||
children: PropTypes.element
|
children: PropTypes.element
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -88,8 +88,8 @@ class RoomItemContainer extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.userSubscription?.unsubscribe) {
|
if (this.avatarSubscription?.unsubscribe) {
|
||||||
this.userSubscription.unsubscribe();
|
this.avatarSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
if (this.roomSubscription?.unsubscribe) {
|
if (this.roomSubscription?.unsubscribe) {
|
||||||
this.roomSubscription.unsubscribe();
|
this.roomSubscription.unsubscribe();
|
||||||
|
@ -115,14 +115,23 @@ class RoomItemContainer extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isDirect) {
|
const db = database.active;
|
||||||
|
const usersCollection = db.collections.get('users');
|
||||||
|
const subsCollection = db.collections.get('subscriptions');
|
||||||
|
try {
|
||||||
const { id } = this.props;
|
const { id } = this.props;
|
||||||
const db = database.active;
|
const { rid } = item;
|
||||||
const usersCollection = db.collections.get('users');
|
|
||||||
try {
|
let record;
|
||||||
const user = await usersCollection.find(id);
|
if (this.isDirect) {
|
||||||
const observable = user.observe();
|
record = await usersCollection.find(id);
|
||||||
this.userSubscription = observable.subscribe((u) => {
|
} else {
|
||||||
|
record = await subsCollection.find(rid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record) {
|
||||||
|
const observable = record.observe();
|
||||||
|
this.avatarSubscription = observable.subscribe((u) => {
|
||||||
const { avatarETag } = u;
|
const { avatarETag } = u;
|
||||||
if (this.mounted) {
|
if (this.mounted) {
|
||||||
this.setState({ avatarETag });
|
this.setState({ avatarETag });
|
||||||
|
@ -130,9 +139,9 @@ class RoomItemContainer extends React.Component {
|
||||||
this.state.avatarETag = avatarETag;
|
this.state.avatarETag = avatarETag;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch {
|
|
||||||
// User not found
|
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
// Record not found
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +229,7 @@ class RoomItemContainer extends React.Component {
|
||||||
useRealName={useRealName}
|
useRealName={useRealName}
|
||||||
unread={item.unread}
|
unread={item.unread}
|
||||||
groupMentions={item.groupMentions}
|
groupMentions={item.groupMentions}
|
||||||
avatarETag={avatarETag}
|
avatarETag={avatarETag || item.avatarETag}
|
||||||
swipeEnabled={swipeEnabled}
|
swipeEnabled={swipeEnabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
const formatUrl = (url, size, query) => `${ url }?format=png&size=${ size }${ query }`;
|
const formatUrl = (url, size, query) => `${ url }?format=png&size=${ size }${ query }`;
|
||||||
|
|
||||||
export const avatarURL = ({
|
export const avatarURL = ({
|
||||||
type, text, size, user = {}, avatar, server, avatarETag
|
type, text, size, user = {}, avatar, server, avatarETag, rid
|
||||||
}) => {
|
}) => {
|
||||||
const room = type === 'd' ? text : `@${ text }`;
|
let room;
|
||||||
|
if (type === 'd') {
|
||||||
|
room = text;
|
||||||
|
} else if (rid) {
|
||||||
|
room = `room/${ rid }`;
|
||||||
|
} else {
|
||||||
|
room = `@${ text }`;
|
||||||
|
}
|
||||||
|
|
||||||
// Avoid requesting several sizes by having only two sizes on cache
|
const uriSize = size > 100 ? size : 100;
|
||||||
const uriSize = size === 100 ? 100 : 50;
|
|
||||||
|
|
||||||
const { id, token } = user;
|
const { id, token } = user;
|
||||||
let query = '';
|
let query = '';
|
||||||
|
|
|
@ -25,8 +25,13 @@ const SelectChannel = ({
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
const getAvatar = (text, type) => avatarURL({
|
const getAvatar = item => avatarURL({
|
||||||
text, type, user: { id: userId, token }, server
|
text: RocketChat.getRoomAvatar(item),
|
||||||
|
type: item.t,
|
||||||
|
user: { id: userId, token },
|
||||||
|
server,
|
||||||
|
avatarETag: item.avatarETag,
|
||||||
|
rid: item.rid
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -42,7 +47,7 @@ const SelectChannel = ({
|
||||||
options={channels.map(channel => ({
|
options={channels.map(channel => ({
|
||||||
value: channel.rid,
|
value: channel.rid,
|
||||||
text: { text: RocketChat.getRoomTitle(channel) },
|
text: { text: RocketChat.getRoomTitle(channel) },
|
||||||
imageUrl: getAvatar(RocketChat.getRoomAvatar(channel), channel.t)
|
imageUrl: getAvatar(channel)
|
||||||
}))}
|
}))}
|
||||||
onClose={() => setChannels([])}
|
onClose={() => setChannels([])}
|
||||||
placeholder={{ text: `${ I18n.t('Select_a_Channel') }...` }}
|
placeholder={{ text: `${ I18n.t('Select_a_Channel') }...` }}
|
||||||
|
|
|
@ -205,7 +205,8 @@ class DirectoryView extends React.Component {
|
||||||
testID: `federation-view-item-${ item.name }`,
|
testID: `federation-view-item-${ item.name }`,
|
||||||
style,
|
style,
|
||||||
user,
|
user,
|
||||||
theme
|
theme,
|
||||||
|
rid: item._id
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type === 'users') {
|
if (type === 'users') {
|
||||||
|
|
|
@ -669,7 +669,12 @@ 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,
|
||||||
|
rid,
|
||||||
|
topic
|
||||||
|
} = room;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
|
|
||||||
const avatar = RocketChat.getRoomAvatar(room);
|
const avatar = RocketChat.getRoomAvatar(room);
|
||||||
|
@ -682,6 +687,7 @@ class RoomActionsView extends React.Component {
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
size={50}
|
size={50}
|
||||||
type={t}
|
type={t}
|
||||||
|
rid={rid}
|
||||||
>
|
>
|
||||||
{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>
|
||||||
|
|
|
@ -6,7 +6,9 @@ import {
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
import ImagePicker from 'react-native-image-crop-picker';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
|
@ -31,6 +33,8 @@ import { withTheme } from '../../theme';
|
||||||
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
||||||
import { MessageTypeValues } from '../../utils/messageTypes';
|
import { MessageTypeValues } from '../../utils/messageTypes';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
|
import Avatar from '../../containers/Avatar';
|
||||||
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
|
|
||||||
const PERMISSION_SET_READONLY = 'set-readonly';
|
const PERMISSION_SET_READONLY = 'set-readonly';
|
||||||
const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly';
|
const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly';
|
||||||
|
@ -64,6 +68,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
room: {},
|
room: {},
|
||||||
|
avatar: {},
|
||||||
permissions: {},
|
permissions: {},
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
@ -136,6 +141,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
topic,
|
topic,
|
||||||
announcement,
|
announcement,
|
||||||
t: t === 'p',
|
t: t === 'p',
|
||||||
|
avatar: {},
|
||||||
ro,
|
ro,
|
||||||
reactWhenReadOnly,
|
reactWhenReadOnly,
|
||||||
joinCode: joinCodeRequired ? this.randomValue : '',
|
joinCode: joinCodeRequired ? this.randomValue : '',
|
||||||
|
@ -160,7 +166,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
|
|
||||||
formIsChanged = () => {
|
formIsChanged = () => {
|
||||||
const {
|
const {
|
||||||
room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode, systemMessages, enableSysMes, encrypted
|
room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode, systemMessages, enableSysMes, encrypted, avatar
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { joinCodeRequired } = room;
|
const { joinCodeRequired } = room;
|
||||||
return !(room.name === name
|
return !(room.name === name
|
||||||
|
@ -174,6 +180,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
&& isEqual(room.sysMes, systemMessages)
|
&& isEqual(room.sysMes, systemMessages)
|
||||||
&& enableSysMes === (room.sysMes && room.sysMes.length > 0)
|
&& enableSysMes === (room.sysMes && room.sysMes.length > 0)
|
||||||
&& room.encrypted === encrypted
|
&& room.encrypted === encrypted
|
||||||
|
&& isEmpty(avatar)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +188,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
logEvent(events.RI_EDIT_SAVE);
|
logEvent(events.RI_EDIT_SAVE);
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
const {
|
const {
|
||||||
room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode, systemMessages, encrypted
|
room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode, systemMessages, encrypted, avatar
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
this.setState({ saving: true });
|
this.setState({ saving: true });
|
||||||
|
@ -201,6 +208,10 @@ class RoomInfoEditView extends React.Component {
|
||||||
if (room.name !== name) {
|
if (room.name !== name) {
|
||||||
params.roomName = name;
|
params.roomName = name;
|
||||||
}
|
}
|
||||||
|
// Avatar
|
||||||
|
if (!isEmpty(avatar)) {
|
||||||
|
params.roomAvatar = avatar.data;
|
||||||
|
}
|
||||||
// Description
|
// Description
|
||||||
if (room.description !== description) {
|
if (room.description !== description) {
|
||||||
params.roomDescription = description;
|
params.roomDescription = description;
|
||||||
|
@ -347,6 +358,28 @@ class RoomInfoEditView extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeAvatar = async() => {
|
||||||
|
const options = {
|
||||||
|
cropping: true,
|
||||||
|
compressImageQuality: 0.8,
|
||||||
|
cropperAvoidEmptySpaceAroundImage: false,
|
||||||
|
cropperChooseText: I18n.t('Choose'),
|
||||||
|
cropperCancelText: I18n.t('Cancel'),
|
||||||
|
includeBase64: true
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await ImagePicker.openPicker(options);
|
||||||
|
this.setState({ avatar: { url: response.path, data: `data:image/jpeg;base64,${ response.data }`, service: 'upload' } });
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetAvatar = () => {
|
||||||
|
this.setState({ avatar: { data: null } });
|
||||||
|
}
|
||||||
|
|
||||||
toggleRoomType = (value) => {
|
toggleRoomType = (value) => {
|
||||||
logEvent(events.RI_EDIT_TOGGLE_ROOM_TYPE);
|
logEvent(events.RI_EDIT_TOGGLE_ROOM_TYPE);
|
||||||
this.setState(({ encrypted }) => ({ t: value, encrypted: value && encrypted }));
|
this.setState(({ encrypted }) => ({ t: value, encrypted: value && encrypted }));
|
||||||
|
@ -374,7 +407,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived, enableSysMes, encrypted
|
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived, enableSysMes, encrypted, avatar
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { serverVersion, e2eEnabled, theme } = this.props;
|
const { serverVersion, e2eEnabled, theme } = this.props;
|
||||||
const { dangerColor } = themes[theme];
|
const { dangerColor } = themes[theme];
|
||||||
|
@ -396,6 +429,20 @@ class RoomInfoEditView extends React.Component {
|
||||||
testID='room-info-edit-view-list'
|
testID='room-info-edit-view-list'
|
||||||
{...scrollPersistTaps}
|
{...scrollPersistTaps}
|
||||||
>
|
>
|
||||||
|
<TouchableOpacity style={styles.avatarContainer} onPress={this.changeAvatar}>
|
||||||
|
<Avatar
|
||||||
|
type={room.t}
|
||||||
|
text={room.name}
|
||||||
|
avatar={avatar?.url}
|
||||||
|
isStatic={avatar?.url}
|
||||||
|
rid={isEmpty(avatar) && room.rid}
|
||||||
|
size={100}
|
||||||
|
>
|
||||||
|
<TouchableOpacity style={[styles.resetButton, { backgroundColor: themes[theme].dangerColor }]} onPress={this.resetAvatar}>
|
||||||
|
<CustomIcon name='delete' color={themes[theme].backgroundColor} size={24} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Avatar>
|
||||||
|
</TouchableOpacity>
|
||||||
<RCTextInput
|
<RCTextInput
|
||||||
inputRef={(e) => { this.name = e; }}
|
inputRef={(e) => { this.name = e; }}
|
||||||
label={I18n.t('Name')}
|
label={I18n.t('Name')}
|
||||||
|
|
|
@ -72,5 +72,17 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
switchMargin: {
|
switchMargin: {
|
||||||
marginBottom: 16
|
marginBottom: 16
|
||||||
|
},
|
||||||
|
avatarContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: 10
|
||||||
|
},
|
||||||
|
resetButton: {
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 4,
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: -8,
|
||||||
|
right: -8
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -289,6 +289,7 @@ class RoomInfoView extends React.Component {
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
type={this.t}
|
type={this.t}
|
||||||
size={100}
|
size={100}
|
||||||
|
rid={room?.rid}
|
||||||
>
|
>
|
||||||
{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>
|
||||||
|
|
|
@ -116,7 +116,7 @@
|
||||||
"redux-immutable-state-invariant": "2.1.0",
|
"redux-immutable-state-invariant": "2.1.0",
|
||||||
"redux-saga": "1.1.3",
|
"redux-saga": "1.1.3",
|
||||||
"remove-markdown": "^0.3.0",
|
"remove-markdown": "^0.3.0",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "4.0.0",
|
||||||
"rn-extensions-share": "^2.4.0",
|
"rn-extensions-share": "^2.4.0",
|
||||||
"rn-fetch-blob": "0.12.0",
|
"rn-fetch-blob": "0.12.0",
|
||||||
"rn-root-view": "^1.0.3",
|
"rn-root-view": "^1.0.3",
|
||||||
|
|
|
@ -35,6 +35,13 @@ const AvatarStories = ({ theme }) => (
|
||||||
server={server}
|
server={server}
|
||||||
size={56}
|
size={56}
|
||||||
/>
|
/>
|
||||||
|
<Separator title='Avatar by roomId' theme={theme} />
|
||||||
|
<Avatar
|
||||||
|
type='p'
|
||||||
|
rid='devWBbYr7inwupPqK'
|
||||||
|
server={server}
|
||||||
|
size={56}
|
||||||
|
/>
|
||||||
<Separator title='Avatar by url' theme={theme} />
|
<Separator title='Avatar by url' theme={theme} />
|
||||||
<Avatar
|
<Avatar
|
||||||
avatar='https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg'
|
avatar='https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg'
|
||||||
|
|
|
@ -13560,7 +13560,7 @@ requires-port@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||||
|
|
||||||
reselect@^4.0.0:
|
reselect@4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
|
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
|
||||||
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
|
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
|
||||||
|
|
Loading…
Reference in New Issue