[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 {
|
||||
"headers": undefined,
|
||||
"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={
|
||||
|
@ -189,7 +254,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
|||
Object {
|
||||
"headers": undefined,
|
||||
"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={
|
||||
|
@ -254,7 +319,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
|||
Object {
|
||||
"headers": undefined,
|
||||
"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={
|
||||
|
@ -319,7 +384,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
|||
Object {
|
||||
"headers": undefined,
|
||||
"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={
|
||||
|
@ -454,7 +519,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
|||
Object {
|
||||
"headers": undefined,
|
||||
"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={
|
||||
|
@ -519,7 +584,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
|||
Object {
|
||||
"headers": undefined,
|
||||
"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={
|
||||
|
@ -679,7 +744,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
|||
Object {
|
||||
"headers": undefined,
|
||||
"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={
|
||||
|
@ -744,7 +809,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
|||
Object {
|
||||
"headers": undefined,
|
||||
"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={
|
||||
|
@ -835,7 +900,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
|||
Object {
|
||||
"headers": undefined,
|
||||
"priority": "high",
|
||||
"uri": "https://google.com/avatar/Avatar?format=png&size=50",
|
||||
"uri": "https://google.com/avatar/Avatar?format=png&size=100",
|
||||
}
|
||||
}
|
||||
style={
|
||||
|
@ -902,7 +967,7 @@ exports[`Storyshots Avatar list Avatar 1`] = `
|
|||
Object {
|
||||
"headers": undefined,
|
||||
"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={
|
||||
|
|
|
@ -23,9 +23,10 @@ const Avatar = React.memo(({
|
|||
theme,
|
||||
getCustomEmoji,
|
||||
avatarETag,
|
||||
isStatic
|
||||
isStatic,
|
||||
rid
|
||||
}) => {
|
||||
if ((!text && !avatar && !emoji) || !server) {
|
||||
if ((!text && !avatar && !emoji && !rid) || !server) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -57,7 +58,8 @@ const Avatar = React.memo(({
|
|||
user,
|
||||
avatar,
|
||||
server,
|
||||
avatarETag
|
||||
avatarETag,
|
||||
rid
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -108,7 +110,8 @@ Avatar.propTypes = {
|
|||
onPress: PropTypes.func,
|
||||
getCustomEmoji: PropTypes.func,
|
||||
avatarETag: PropTypes.string,
|
||||
isStatic: PropTypes.bool
|
||||
isStatic: PropTypes.bool,
|
||||
rid: PropTypes.string
|
||||
};
|
||||
|
||||
Avatar.defaultProps = {
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import isEqual from 'react-fast-compare';
|
||||
|
||||
import database from '../../lib/database';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
|
@ -9,6 +10,7 @@ import Avatar from './Avatar';
|
|||
|
||||
class AvatarContainer extends React.Component {
|
||||
static propTypes = {
|
||||
rid: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
type: PropTypes.string
|
||||
};
|
||||
|
@ -29,9 +31,15 @@ class AvatarContainer extends React.Component {
|
|||
this.mounted = true;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!isEqual(prevProps, this.props)) {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.userSubscription?.unsubscribe) {
|
||||
this.userSubscription.unsubscribe();
|
||||
if (this.subscription?.unsubscribe) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,16 +49,28 @@ class AvatarContainer extends React.Component {
|
|||
}
|
||||
|
||||
init = async() => {
|
||||
if (this.isDirect) {
|
||||
const { text } = this.props;
|
||||
const db = database.active;
|
||||
const usersCollection = db.collections.get('users');
|
||||
const subsCollection = db.collections.get('subscriptions');
|
||||
|
||||
let record;
|
||||
try {
|
||||
if (this.isDirect) {
|
||||
const { text } = this.props;
|
||||
const [user] = await usersCollection.query(Q.where('username', text)).fetch();
|
||||
if (user) {
|
||||
const observable = user.observe();
|
||||
this.userSubscription = observable.subscribe((u) => {
|
||||
const { avatarETag } = u;
|
||||
record = user;
|
||||
} else {
|
||||
const { rid } = this.props;
|
||||
record = await subsCollection.find(rid);
|
||||
}
|
||||
} 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 {
|
||||
|
@ -58,10 +78,6 @@ class AvatarContainer extends React.Component {
|
|||
}
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// User was not found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -70,13 +70,13 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }) => {
|
|||
const { isLandscape } = useOrientation();
|
||||
|
||||
const { text, payload } = notification;
|
||||
const { type } = payload;
|
||||
const { type, rid } = payload;
|
||||
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
|
||||
const { title = name, avatar = name } = notification;
|
||||
|
||||
const onPress = () => {
|
||||
const { rid, prid } = payload;
|
||||
const { prid } = payload;
|
||||
if (!rid) {
|
||||
return;
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }) => {
|
|||
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}>
|
||||
<Text style={[styles.roomName, { color: themes[theme].titleText }]} numberOfLines={1}>{title}</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_topic',
|
||||
'room_changed_privacy',
|
||||
'room_changed_avatar',
|
||||
'message_snippeted',
|
||||
'thread-created'
|
||||
];
|
||||
|
@ -91,6 +92,8 @@ export const getInfoMessage = ({
|
|||
return I18n.t('Room_changed_topic', { topic: msg, userBy: username });
|
||||
} else if (type === 'room_changed_privacy') {
|
||||
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') {
|
||||
return I18n.t('Created_snippet');
|
||||
}
|
||||
|
|
|
@ -437,6 +437,7 @@ export default {
|
|||
Roles: 'Roles',
|
||||
Room_actions: 'Room actions',
|
||||
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_privacy: 'Room type changed to: {{type}} 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('tags', sanitizer) tags;
|
||||
|
||||
@field('avatar_etag') avatarETag;
|
||||
}
|
||||
|
|
|
@ -115,4 +115,6 @@ export default class Subscription extends Model {
|
|||
@field('encrypted') encrypted;
|
||||
|
||||
@field('e2e_key_id') e2eKeyId;
|
||||
|
||||
@field('avatar_etag') avatarETag;
|
||||
}
|
||||
|
|
|
@ -178,6 +178,18 @@ export default schemaMigrations({
|
|||
{ name: 'username', type: 'string', isIndexed: 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: 'e2e_key', type: 'string', 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({
|
||||
|
@ -67,7 +68,8 @@ export default appSchema({
|
|||
{ name: 'served_by', type: 'string', isOptional: true },
|
||||
{ name: 'livechat_data', 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({
|
||||
|
|
|
@ -52,7 +52,8 @@ export default async(subscriptions = [], rooms = []) => {
|
|||
tags: s.tags,
|
||||
encrypted: s.encrypted,
|
||||
e2eKeyId: s.e2eKeyId,
|
||||
E2EKey: s.E2EKey
|
||||
E2EKey: s.E2EKey,
|
||||
avatarETag: s.avatarETag
|
||||
}));
|
||||
subscriptions = subscriptions.concat(existingSubs);
|
||||
|
||||
|
@ -80,7 +81,8 @@ export default async(subscriptions = [], rooms = []) => {
|
|||
livechatData: r.livechatData,
|
||||
tags: r.tags,
|
||||
encrypted: r.encrypted,
|
||||
e2eKeyId: r.e2eKeyId
|
||||
e2eKeyId: r.e2eKeyId,
|
||||
avatarETag: r.avatarETag
|
||||
}));
|
||||
rooms = rooms.concat(existingRooms);
|
||||
} catch {
|
||||
|
|
|
@ -30,6 +30,7 @@ export const merge = (subscription, room) => {
|
|||
subscription.broadcast = room.broadcast;
|
||||
subscription.encrypted = room.encrypted;
|
||||
subscription.e2eKeyId = room.e2eKeyId;
|
||||
subscription.avatarETag = room.avatarETag;
|
||||
if (!subscription.roles || !subscription.roles.length) {
|
||||
subscription.roles = [];
|
||||
}
|
||||
|
|
|
@ -84,7 +84,8 @@ const createOrUpdateSubscription = async(subscription, room) => {
|
|||
tags: s.tags,
|
||||
encrypted: s.encrypted,
|
||||
e2eKeyId: s.e2eKeyId,
|
||||
E2EKey: s.E2EKey
|
||||
E2EKey: s.E2EKey,
|
||||
avatarETag: s.avatarETag
|
||||
};
|
||||
} catch (error) {
|
||||
try {
|
||||
|
@ -116,7 +117,8 @@ const createOrUpdateSubscription = async(subscription, room) => {
|
|||
broadcast: r.broadcast,
|
||||
customFields: r.customFields,
|
||||
departmentId: r.departmentId,
|
||||
livechatData: r.livechatData
|
||||
livechatData: r.livechatData,
|
||||
avatarETag: r.avatarETag
|
||||
};
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
|
|
|
@ -579,6 +579,7 @@ const RocketChat = {
|
|||
rid: sub.rid,
|
||||
name: sub.name,
|
||||
fname: sub.fname,
|
||||
avatarETag: sub.avatarETag,
|
||||
t: sub.t,
|
||||
search: true
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ const DirectoryItemLabel = React.memo(({ text, theme }) => {
|
|||
});
|
||||
|
||||
const DirectoryItem = ({
|
||||
title, description, avatar, onPress, testID, style, rightLabel, type, theme
|
||||
title, description, avatar, onPress, testID, style, rightLabel, type, rid, theme
|
||||
}) => (
|
||||
<Touch
|
||||
onPress={onPress}
|
||||
|
@ -31,6 +31,7 @@ const DirectoryItem = ({
|
|||
text={avatar}
|
||||
size={30}
|
||||
type={type}
|
||||
rid={rid}
|
||||
style={styles.directoryItemAvatar}
|
||||
/>
|
||||
<View style={styles.directoryItemTextContainer}>
|
||||
|
@ -54,6 +55,7 @@ DirectoryItem.propTypes = {
|
|||
testID: PropTypes.string.isRequired,
|
||||
style: PropTypes.any,
|
||||
rightLabel: PropTypes.string,
|
||||
rid: PropTypes.string,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ const RoomItem = ({
|
|||
userId={userId}
|
||||
token={token}
|
||||
theme={theme}
|
||||
rid={rid}
|
||||
>
|
||||
{showLastMessage
|
||||
? (
|
||||
|
|
|
@ -16,6 +16,7 @@ const Wrapper = ({
|
|||
userId,
|
||||
token,
|
||||
theme,
|
||||
rid,
|
||||
children
|
||||
}) => (
|
||||
<View
|
||||
|
@ -30,6 +31,7 @@ const Wrapper = ({
|
|||
server={baseUrl}
|
||||
user={{ id: userId, token }}
|
||||
avatarETag={avatarETag}
|
||||
rid={rid}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
|
@ -54,6 +56,7 @@ Wrapper.propTypes = {
|
|||
userId: PropTypes.string,
|
||||
token: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
rid: PropTypes.string,
|
||||
children: PropTypes.element
|
||||
};
|
||||
|
||||
|
|
|
@ -88,8 +88,8 @@ class RoomItemContainer extends React.Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.userSubscription?.unsubscribe) {
|
||||
this.userSubscription.unsubscribe();
|
||||
if (this.avatarSubscription?.unsubscribe) {
|
||||
this.avatarSubscription.unsubscribe();
|
||||
}
|
||||
if (this.roomSubscription?.unsubscribe) {
|
||||
this.roomSubscription.unsubscribe();
|
||||
|
@ -115,14 +115,23 @@ class RoomItemContainer extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
if (this.isDirect) {
|
||||
const { id } = this.props;
|
||||
const db = database.active;
|
||||
const usersCollection = db.collections.get('users');
|
||||
const subsCollection = db.collections.get('subscriptions');
|
||||
try {
|
||||
const user = await usersCollection.find(id);
|
||||
const observable = user.observe();
|
||||
this.userSubscription = observable.subscribe((u) => {
|
||||
const { id } = this.props;
|
||||
const { rid } = item;
|
||||
|
||||
let record;
|
||||
if (this.isDirect) {
|
||||
record = await usersCollection.find(id);
|
||||
} else {
|
||||
record = await subsCollection.find(rid);
|
||||
}
|
||||
|
||||
if (record) {
|
||||
const observable = record.observe();
|
||||
this.avatarSubscription = observable.subscribe((u) => {
|
||||
const { avatarETag } = u;
|
||||
if (this.mounted) {
|
||||
this.setState({ avatarETag });
|
||||
|
@ -130,9 +139,9 @@ class RoomItemContainer extends React.Component {
|
|||
this.state.avatarETag = avatarETag;
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
// User not found
|
||||
}
|
||||
} catch {
|
||||
// Record not found
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,7 +229,7 @@ class RoomItemContainer extends React.Component {
|
|||
useRealName={useRealName}
|
||||
unread={item.unread}
|
||||
groupMentions={item.groupMentions}
|
||||
avatarETag={avatarETag}
|
||||
avatarETag={avatarETag || item.avatarETag}
|
||||
swipeEnabled={swipeEnabled}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
const formatUrl = (url, size, query) => `${ url }?format=png&size=${ size }${ query }`;
|
||||
|
||||
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 ? 100 : 50;
|
||||
const uriSize = size > 100 ? size : 100;
|
||||
|
||||
const { id, token } = user;
|
||||
let query = '';
|
||||
|
|
|
@ -25,8 +25,13 @@ const SelectChannel = ({
|
|||
}
|
||||
}, 300);
|
||||
|
||||
const getAvatar = (text, type) => avatarURL({
|
||||
text, type, user: { id: userId, token }, server
|
||||
const getAvatar = item => avatarURL({
|
||||
text: RocketChat.getRoomAvatar(item),
|
||||
type: item.t,
|
||||
user: { id: userId, token },
|
||||
server,
|
||||
avatarETag: item.avatarETag,
|
||||
rid: item.rid
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -42,7 +47,7 @@ const SelectChannel = ({
|
|||
options={channels.map(channel => ({
|
||||
value: channel.rid,
|
||||
text: { text: RocketChat.getRoomTitle(channel) },
|
||||
imageUrl: getAvatar(RocketChat.getRoomAvatar(channel), channel.t)
|
||||
imageUrl: getAvatar(channel)
|
||||
}))}
|
||||
onClose={() => setChannels([])}
|
||||
placeholder={{ text: `${ I18n.t('Select_a_Channel') }...` }}
|
||||
|
|
|
@ -205,7 +205,8 @@ class DirectoryView extends React.Component {
|
|||
testID: `federation-view-item-${ item.name }`,
|
||||
style,
|
||||
user,
|
||||
theme
|
||||
theme,
|
||||
rid: item._id
|
||||
};
|
||||
|
||||
if (type === 'users') {
|
||||
|
|
|
@ -669,7 +669,12 @@ class RoomActionsView extends React.Component {
|
|||
|
||||
renderRoomInfo = ({ item }) => {
|
||||
const { room, member } = this.state;
|
||||
const { name, t, topic } = room;
|
||||
const {
|
||||
name,
|
||||
t,
|
||||
rid,
|
||||
topic
|
||||
} = room;
|
||||
const { theme } = this.props;
|
||||
|
||||
const avatar = RocketChat.getRoomAvatar(room);
|
||||
|
@ -682,6 +687,7 @@ class RoomActionsView extends React.Component {
|
|||
style={styles.avatar}
|
||||
size={50}
|
||||
type={t}
|
||||
rid={rid}
|
||||
>
|
||||
{t === 'd' && member._id ? <Status style={sharedStyles.status} id={member._id} /> : null }
|
||||
</Avatar>
|
||||
|
|
|
@ -6,7 +6,9 @@ import {
|
|||
import { connect } from 'react-redux';
|
||||
import equal from 'deep-equal';
|
||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||
import ImagePicker from 'react-native-image-crop-picker';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import semver from 'semver';
|
||||
|
||||
import database from '../../lib/database';
|
||||
|
@ -31,6 +33,8 @@ import { withTheme } from '../../theme';
|
|||
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
||||
import { MessageTypeValues } from '../../utils/messageTypes';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import Avatar from '../../containers/Avatar';
|
||||
import { CustomIcon } from '../../lib/Icons';
|
||||
|
||||
const PERMISSION_SET_READONLY = 'set-readonly';
|
||||
const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly';
|
||||
|
@ -64,6 +68,7 @@ class RoomInfoEditView extends React.Component {
|
|||
super(props);
|
||||
this.state = {
|
||||
room: {},
|
||||
avatar: {},
|
||||
permissions: {},
|
||||
name: '',
|
||||
description: '',
|
||||
|
@ -136,6 +141,7 @@ class RoomInfoEditView extends React.Component {
|
|||
topic,
|
||||
announcement,
|
||||
t: t === 'p',
|
||||
avatar: {},
|
||||
ro,
|
||||
reactWhenReadOnly,
|
||||
joinCode: joinCodeRequired ? this.randomValue : '',
|
||||
|
@ -160,7 +166,7 @@ class RoomInfoEditView extends React.Component {
|
|||
|
||||
formIsChanged = () => {
|
||||
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;
|
||||
const { joinCodeRequired } = room;
|
||||
return !(room.name === name
|
||||
|
@ -174,6 +180,7 @@ class RoomInfoEditView extends React.Component {
|
|||
&& isEqual(room.sysMes, systemMessages)
|
||||
&& enableSysMes === (room.sysMes && room.sysMes.length > 0)
|
||||
&& room.encrypted === encrypted
|
||||
&& isEmpty(avatar)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -181,7 +188,7 @@ class RoomInfoEditView extends React.Component {
|
|||
logEvent(events.RI_EDIT_SAVE);
|
||||
Keyboard.dismiss();
|
||||
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.setState({ saving: true });
|
||||
|
@ -201,6 +208,10 @@ class RoomInfoEditView extends React.Component {
|
|||
if (room.name !== name) {
|
||||
params.roomName = name;
|
||||
}
|
||||
// Avatar
|
||||
if (!isEmpty(avatar)) {
|
||||
params.roomAvatar = avatar.data;
|
||||
}
|
||||
// Description
|
||||
if (room.description !== 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) => {
|
||||
logEvent(events.RI_EDIT_TOGGLE_ROOM_TYPE);
|
||||
this.setState(({ encrypted }) => ({ t: value, encrypted: value && encrypted }));
|
||||
|
@ -374,7 +407,7 @@ class RoomInfoEditView extends React.Component {
|
|||
|
||||
render() {
|
||||
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;
|
||||
const { serverVersion, e2eEnabled, theme } = this.props;
|
||||
const { dangerColor } = themes[theme];
|
||||
|
@ -396,6 +429,20 @@ class RoomInfoEditView extends React.Component {
|
|||
testID='room-info-edit-view-list'
|
||||
{...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
|
||||
inputRef={(e) => { this.name = e; }}
|
||||
label={I18n.t('Name')}
|
||||
|
|
|
@ -72,5 +72,17 @@ export default StyleSheet.create({
|
|||
},
|
||||
switchMargin: {
|
||||
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}
|
||||
type={this.t}
|
||||
size={100}
|
||||
rid={room?.rid}
|
||||
>
|
||||
{this.t === 'd' && roomUser._id ? <Status style={[sharedStyles.status, styles.status]} theme={theme} size={24} id={roomUser._id} /> : null}
|
||||
</Avatar>
|
||||
|
|
|
@ -116,7 +116,7 @@
|
|||
"redux-immutable-state-invariant": "2.1.0",
|
||||
"redux-saga": "1.1.3",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"reselect": "4.0.0",
|
||||
"rn-extensions-share": "^2.4.0",
|
||||
"rn-fetch-blob": "0.12.0",
|
||||
"rn-root-view": "^1.0.3",
|
||||
|
|
|
@ -35,6 +35,13 @@ const AvatarStories = ({ theme }) => (
|
|||
server={server}
|
||||
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} />
|
||||
<Avatar
|
||||
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"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
reselect@^4.0.0:
|
||||
reselect@4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
|
||||
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
|
||||
|
|
Loading…
Reference in New Issue