From 8d8a1d73b0078aa5eb66e70b19913d5ea6778de5 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Fri, 17 Jul 2020 15:38:10 -0300 Subject: [PATCH 01/34] [WIP] Avatar cache invalidation --- app/containers/Avatar.js | 14 +++++-- app/containers/message/MessageAvatar.js | 1 + app/containers/message/index.js | 22 +++++++++- app/lib/database/index.js | 4 +- app/lib/database/model/User.js | 2 + app/lib/database/model/Users.js | 14 +++++++ app/lib/database/model/migrations.js | 16 ++++++- app/lib/database/model/serversMigrations.js | 11 +++++ app/lib/database/schema/app.js | 11 ++++- app/lib/database/schema/servers.js | 5 ++- app/lib/methods/getUsersPresence.js | 23 +++++++++++ app/lib/rocketchat.js | 46 +++++++++++++++++++-- app/presentation/RoomItem/index.js | 26 +++++++++++- app/sagas/login.js | 3 +- app/sagas/selectServer.js | 3 +- app/utils/avatar.js | 15 ++++--- 16 files changed, 195 insertions(+), 21 deletions(-) create mode 100644 app/lib/database/model/Users.js diff --git a/app/containers/Avatar.js b/app/containers/Avatar.js index c4ed0ba7c4e..476466e4967 100644 --- a/app/containers/Avatar.js +++ b/app/containers/Avatar.js @@ -9,7 +9,7 @@ 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 + text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token, onPress, theme, emoji, getCustomEmoji, avatarETag }) => { const avatarStyle = { width: size, @@ -22,7 +22,14 @@ const Avatar = React.memo(({ } const uri = avatarURL({ - type, text, size, userId, token, avatar, baseUrl + type, + text, + size, + userId, + token, + avatar, + baseUrl, + avatarETag }); let image = emoji ? ( @@ -74,7 +81,8 @@ Avatar.propTypes = { token: PropTypes.string, theme: PropTypes.string, onPress: PropTypes.func, - getCustomEmoji: PropTypes.func + getCustomEmoji: PropTypes.func, + avatarETag: PropTypes.string }; Avatar.defaultProps = { diff --git a/app/containers/message/MessageAvatar.js b/app/containers/message/MessageAvatar.js index 9f1eb6a2311..0d4d9e674f7 100644 --- a/app/containers/message/MessageAvatar.js +++ b/app/containers/message/MessageAvatar.js @@ -27,6 +27,7 @@ const MessageAvatar = React.memo(({ baseUrl={baseUrl} userId={user.id} token={user.token} + avatarETag={author.avatarETag} theme={theme} /> ); diff --git a/app/containers/message/index.js b/app/containers/message/index.js index 207e5e70cf4..be0b81da5ef 100644 --- a/app/containers/message/index.js +++ b/app/containers/message/index.js @@ -8,6 +8,7 @@ import debounce from '../../utils/debounce'; import { SYSTEM_MESSAGES, getMessageTranslation } from './utils'; import messagesStatus from '../../constants/messagesStatus'; import { withTheme } from '../../theme'; +import database from '../../lib/database'; class MessageContainer extends React.Component { static propTypes = { @@ -69,7 +70,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(); @@ -77,6 +82,18 @@ 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); + user.observe().subscribe((changes) => { + this.setState({ author: changes }); + this.forceUpdate(); + }); + } catch { + // Do nothing + } } shouldComponentUpdate(nextProps) { @@ -226,6 +243,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; @@ -259,7 +277,7 @@ class MessageContainer extends React.Component { id={id} msg={message} rid={rid} - author={u} + author={author || u} ts={ts} type={t} attachments={attachments} diff --git a/app/lib/database/index.js b/app/lib/database/index.js index 37a17a3cd2f..fc70d9e8013 100644 --- a/app/lib/database/index.js +++ b/app/lib/database/index.js @@ -17,6 +17,7 @@ import Permission from './model/Permission'; import SlashCommand from './model/SlashCommand'; import User from './model/User'; import Server from './model/Server'; +import Users from './model/Users'; import serversSchema from './schema/servers'; import appSchema from './schema/app'; @@ -57,7 +58,8 @@ export const getDatabase = (database = '') => { Setting, Role, Permission, - SlashCommand + SlashCommand, + Users ], actionsEnabled: true }); diff --git a/app/lib/database/model/User.js b/app/lib/database/model/User.js index 5535ef440cf..a220a9f4bd3 100644 --- a/app/lib/database/model/User.js +++ b/app/lib/database/model/User.js @@ -19,4 +19,6 @@ export default class User extends Model { @field('statusText') statusText; @json('roles', sanitizer) roles; + + @field('avatar_etag') avatarETag; } diff --git a/app/lib/database/model/Users.js b/app/lib/database/model/Users.js new file mode 100644 index 00000000000..11073a90639 --- /dev/null +++ b/app/lib/database/model/Users.js @@ -0,0 +1,14 @@ +import { Model } from '@nozbe/watermelondb'; +import { field } from '@nozbe/watermelondb/decorators'; + +export default class User extends Model { + static table = 'users'; + + @field('_id') _id; + + @field('name') name; + + @field('username') username; + + @field('avatar_etag') avatarETag; +} diff --git a/app/lib/database/model/migrations.js b/app/lib/database/model/migrations.js index 2602094270b..ecc292657ec 100644 --- a/app/lib/database/model/migrations.js +++ b/app/lib/database/model/migrations.js @@ -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: [ @@ -118,6 +118,20 @@ export default schemaMigrations({ ] }) ] + }, + { + toVersion: 9, + steps: [ + createTable({ + name: 'users', + columns: [ + { name: '_id', type: 'string', isOptional: true }, + { name: 'name', type: 'string', isOptional: true }, + { name: 'username', type: 'string', isOptional: true }, + { name: 'avatar_etag', type: 'string', isOptional: true } + ] + }) + ] } ] }); diff --git a/app/lib/database/model/serversMigrations.js b/app/lib/database/model/serversMigrations.js index 0163d3bde99..d7645f98dbf 100644 --- a/app/lib/database/model/serversMigrations.js +++ b/app/lib/database/model/serversMigrations.js @@ -26,6 +26,17 @@ export default schemaMigrations({ ] }) ] + }, + { + toVersion: 5, + steps: [ + addColumns({ + table: 'users', + columns: [ + { name: 'avatar_etag', type: 'string', isOptional: true } + ] + }) + ] } ] }); diff --git a/app/lib/database/schema/app.js b/app/lib/database/schema/app.js index 97784b3f9f1..b90715aa95a 100644 --- a/app/lib/database/schema/app.js +++ b/app/lib/database/schema/app.js @@ -1,7 +1,7 @@ import { appSchema, tableSchema } from '@nozbe/watermelondb'; export default appSchema({ - version: 8, + version: 9, tables: [ tableSchema({ name: 'subscriptions', @@ -239,6 +239,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', isOptional: true }, + { name: 'name', type: 'string', isOptional: true }, + { name: 'username', type: 'string', isOptional: true }, + { name: 'avatar_etag', type: 'string', isOptional: true } + ] }) ] }); diff --git a/app/lib/database/schema/servers.js b/app/lib/database/schema/servers.js index 7557f8b1c8a..b8c855a21be 100644 --- a/app/lib/database/schema/servers.js +++ b/app/lib/database/schema/servers.js @@ -1,7 +1,7 @@ import { appSchema, tableSchema } from '@nozbe/watermelondb'; export default appSchema({ - version: 4, + version: 5, tables: [ tableSchema({ name: 'users', @@ -12,7 +12,8 @@ export default appSchema({ { name: 'language', type: 'string', isOptional: true }, { name: 'status', type: 'string', isOptional: true }, { name: 'statusText', type: 'string', isOptional: true }, - { name: 'roles', type: 'string', isOptional: true } + { name: 'roles', type: 'string', isOptional: true }, + { name: 'avatar_etag', type: 'string', isOptional: true } ] }), tableSchema({ diff --git a/app/lib/methods/getUsersPresence.js b/app/lib/methods/getUsersPresence.js index 91c82ff47f5..7e6460e3b07 100644 --- a/app/lib/methods/getUsersPresence.js +++ b/app/lib/methods/getUsersPresence.js @@ -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; @@ -61,6 +63,27 @@ export default async function getUsersPresence() { }); ids = []; } + + const db = database.active; + const userCollection = db.collections.get('users'); + result.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) { + // Not was found + await db.action(async() => { + await userCollection.create((u) => { + u._raw = sanitizedRaw({ id: user._id }, userCollection.schema); + Object.assign(u, user); + }); + }); + } + }); } catch { // do nothing } diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index cfa9599198f..d9fde8fc89e 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -4,6 +4,7 @@ import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk'; import RNUserDefaults from 'rn-user-defaults'; import { Q } from '@nozbe/watermelondb'; import AsyncStorage from '@react-native-community/async-storage'; +import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import reduxStore from './createStore'; import defaultSettings from '../constants/settings'; @@ -245,9 +246,13 @@ const RocketChat = { this.usersListener = this.sdk.onStreamData('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage))); - this.notifyLoggedListener = this.sdk.onStreamData('stream-notify-logged', protectedFunction((ddpMessage) => { + this.sdk.subscribe('stream-notify-logged', 'updateAvatar'); + + this.sdk.subscribe('stream-notify-logged', 'Users:NameChanged'); + + 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(() => { @@ -267,6 +272,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 { + // Not was found + await db.action(async() => { + await userCollection.create((u) => { + u._raw = sanitizedRaw({ id: userNameChanged._id }, userCollection.schema); + Object.assign(u, userNameChanged); + }); + }); + } } })); @@ -401,7 +440,8 @@ const RocketChat = { customFields: result.me.customFields, statusLivechat: result.me.statusLivechat, emails: result.me.emails, - roles: result.me.roles + roles: result.me.roles, + avatarETag: result.me.avatarETag }; return user; }, diff --git a/app/presentation/RoomItem/index.js b/app/presentation/RoomItem/index.js index 1b3ee93f2be..947fc022862 100644 --- a/app/presentation/RoomItem/index.js +++ b/app/presentation/RoomItem/index.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { View, Text } from 'react-native'; import { connect } from 'react-redux'; @@ -12,6 +12,7 @@ import LastMessage from './LastMessage'; import { capitalize, formatDate } from '../../utils/room'; import Touchable from './Touchable'; import { themes } from '../../constants/colors'; +import database from '../../lib/database'; export { ROW_HEIGHT }; @@ -76,12 +77,33 @@ const RoomItem = React.memo(({ theme, isFocused }) => { + const [avatarETag, setAvatarETag] = useState(); + useEffect(() => { if (connected && type === 'd' && id) { getUserPresence(id); } }, [connected]); + const handleAvatarChange = async() => { + if (type === 'd' && id) { + const db = database.active; + const usersCollection = db.collections.get('users'); + try { + const user = await usersCollection.find(id); + user.observe().subscribe((changes) => { + setAvatarETag(changes.avatarETag); + }); + } catch { + // Do nothing + } + } + }; + + useEffect(() => { + handleAvatarChange(); + }, []); + const date = lastMessage && formatDate(lastMessage.ts); let accessibilityLabel = name; @@ -119,6 +141,7 @@ const RoomItem = React.memo(({ accessibilityLabel={accessibilityLabel} > { try { diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index 429b2642cc4..9deb15763d9 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -81,7 +81,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 (e) { // We only run it if not has user on DB diff --git a/app/utils/avatar.js b/app/utils/avatar.js index cf61daee1a8..fae17c136fd 100644 --- a/app/utils/avatar.js +++ b/app/utils/avatar.js @@ -1,9 +1,9 @@ -const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => ( - `${ baseUrl }${ url }?format=png&size=${ uriSize }&${ avatarAuthURLFragment }` +const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment, avatarETagFragment) => ( + `${ baseUrl }${ url }?format=png&size=${ uriSize }${ avatarAuthURLFragment }${ avatarETagFragment }` ); export const avatarURL = ({ - type, text, size, userId, token, avatar, baseUrl + type, text, size, userId, token, avatar, baseUrl, avatarETag }) => { const room = type === 'd' ? text : `@${ text }`; @@ -15,12 +15,17 @@ export const avatarURL = ({ avatarAuthURLFragment = `&rc_token=${ token }&rc_uid=${ userId }`; } + let avatarETagFragment; + if (avatarETag) { + avatarETagFragment = `&${ avatarETag }`; + } + let uri; if (avatar) { - uri = avatar.includes('http') ? avatar : formatUrl(avatar, baseUrl, uriSize, avatarAuthURLFragment); + uri = avatar.includes('http') ? avatar : formatUrl(avatar, baseUrl, uriSize, avatarAuthURLFragment, avatarETagFragment); } else { - uri = formatUrl(`/avatar/${ room }`, baseUrl, uriSize, avatarAuthURLFragment); + uri = formatUrl(`/avatar/${ room }`, baseUrl, uriSize, avatarAuthURLFragment, avatarETagFragment); } return uri; From ba0e3dd5980add8b0a08a0b2eee45e630fa12523 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Mon, 20 Jul 2020 13:04:58 -0300 Subject: [PATCH 02/34] [WIP] Avatar container --- app/containers/{ => Avatar}/Avatar.js | 82 ++++++------ app/containers/Avatar/index.js | 122 ++++++++++++++++++ .../InAppNotification/NotifierComponent.js | 12 +- .../MessageBox/Mentions/MentionItem.js | 5 +- app/containers/message/MessageAvatar.js | 9 +- app/containers/message/index.js | 6 +- app/lib/database/index.js | 3 +- app/presentation/DirectoryItem/index.js | 10 +- app/presentation/RoomItem/index.js | 35 +---- app/presentation/UserItem.js | 9 +- app/utils/avatar.js | 29 ++--- .../CreateDiscussionView/SelectChannel.js | 2 +- app/views/CreateDiscussionView/SelectUsers.js | 2 +- app/views/ProfileView/index.js | 10 +- app/views/ReadReceiptView/index.js | 14 +- app/views/RoomActionsView/index.js | 7 +- app/views/RoomInfoView/index.js | 15 +-- app/views/RoomView/Header/LeftButtons.js | 30 ++--- app/views/SidebarView/index.js | 5 +- 19 files changed, 214 insertions(+), 193 deletions(-) rename app/containers/{ => Avatar}/Avatar.js (50%) create mode 100644 app/containers/Avatar/index.js diff --git a/app/containers/Avatar.js b/app/containers/Avatar/Avatar.js similarity index 50% rename from app/containers/Avatar.js rename to app/containers/Avatar/Avatar.js index 476466e4967..3630de906d9 100644 --- a/app/containers/Avatar.js +++ b/app/containers/Avatar/Avatar.js @@ -1,74 +1,68 @@ 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'; +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, avatarETag + text, size, server, borderRadius, style, avatar, type, children, user, onPress, emoji, theme, getCustomEmoji, avatarETag }) => { + if ((!text && !avatar) || !server) { + return null; + } + const avatarStyle = { width: size, height: size, borderRadius }; - if (!text && !avatar) { - return null; + if (emoji) { + return ( + + + + ); } const uri = avatarURL({ type, text, size, - userId, - token, + user, avatar, - baseUrl, + server, avatarETag }); - let image = emoji ? ( - - ) : ( - - ); - - if (onPress) { - image = ( - - {image} - - ); - } - return ( - - {image} - {children} - + + <> + + {children} + + ); }); Avatar.propTypes = { - baseUrl: PropTypes.string.isRequired, + server: PropTypes.string, style: PropTypes.any, text: PropTypes.string, avatar: PropTypes.string, @@ -77,8 +71,10 @@ Avatar.propTypes = { borderRadius: PropTypes.number, type: PropTypes.string, children: PropTypes.object, - userId: PropTypes.string, - token: PropTypes.string, + user: PropTypes.shape({ + id: PropTypes.string, + token: PropTypes.string + }), theme: PropTypes.string, onPress: PropTypes.func, getCustomEmoji: PropTypes.func, diff --git a/app/containers/Avatar/index.js b/app/containers/Avatar/index.js new file mode 100644 index 00000000000..9d9b84cd795 --- /dev/null +++ b/app/containers/Avatar/index.js @@ -0,0 +1,122 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +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 { Q } from '@nozbe/watermelondb'; + +import database from '../../lib/database'; +import { avatarURL } from '../../utils/avatar'; +import { getUserSelector } from '../../selectors/login'; +import Emoji from '../markdown/Emoji'; + +const Avatar = React.memo(({ + text, size, server, borderRadius, style, avatar, type, children, user, onPress, emoji, theme, getCustomEmoji +}) => { + if ((!text && !avatar) || !server) { + return null; + } + + const [avatarETag, setAvatarETag] = useState(); + + const subscribeAvatar = async() => { + if (type === 'd') { + const db = database.active; + const usersCollection = db.collections.get('users'); + try { + const [userRecord] = await usersCollection.query(Q.where('username', text)).fetch(); + if (userRecord) { + const observable = userRecord.observe(); + observable.subscribe((u) => { + setAvatarETag(u.avatarETag); + }); + } + } catch { + // Do nothing + } + } + }; + + useEffect(() => { + subscribeAvatar(); + }, []); + + const avatarStyle = { + width: size, + height: size, + borderRadius + }; + + if (emoji) { + return ( + + + + ); + } + + const uri = avatarURL({ + type, + text, + size, + user, + avatar, + server, + avatarETag + }); + + return ( + + <> + + {children} + + + ); +}); + +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 +}; + +Avatar.defaultProps = { + text: '', + size: 25, + type: 'd', + borderRadius: 4 +}; + +const mapStateToProps = state => ({ + user: getUserSelector(state), + server: state.share.server || state.server.server +}); +export default connect(mapStateToProps)(Avatar); diff --git a/app/containers/InAppNotification/NotifierComponent.js b/app/containers/InAppNotification/NotifierComponent.js index 9cab30a59b1..98d0fd46d24 100644 --- a/app/containers/InAppNotification/NotifierComponent.js +++ b/app/containers/InAppNotification/NotifierComponent.js @@ -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()} > <> - + {title} {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 }); diff --git a/app/containers/MessageBox/Mentions/MentionItem.js b/app/containers/MessageBox/Mentions/MentionItem.js index 6a3505da2d1..36144de6f56 100644 --- a/app/containers/MessageBox/Mentions/MentionItem.js +++ b/app/containers/MessageBox/Mentions/MentionItem.js @@ -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} /> { item.username || item.name || item } diff --git a/app/containers/message/MessageAvatar.js b/app/containers/message/MessageAvatar.js index 0d4d9e674f7..4c0f546873f 100644 --- a/app/containers/message/MessageAvatar.js +++ b/app/containers/message/MessageAvatar.js @@ -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,12 +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} - avatarETag={author.avatarETag} theme={theme} /> ); diff --git a/app/containers/message/index.js b/app/containers/message/index.js index be0b81da5ef..14610ec3674 100644 --- a/app/containers/message/index.js +++ b/app/containers/message/index.js @@ -87,7 +87,8 @@ class MessageContainer extends React.Component { const usersCollection = db.collections.get('users'); try { const user = await usersCollection.find(item.u._id); - user.observe().subscribe((changes) => { + const observable = user.observe(); + this.userSubscription = observable.subscribe((changes) => { this.setState({ author: changes }); this.forceUpdate(); }); @@ -108,6 +109,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(() => { diff --git a/app/lib/database/index.js b/app/lib/database/index.js index fc70d9e8013..2438a66984d 100644 --- a/app/lib/database/index.js +++ b/app/lib/database/index.js @@ -114,7 +114,8 @@ class DB { Upload, Permission, CustomEmoji, - FrequentlyUsedEmoji + FrequentlyUsedEmoji, + Users ], actionsEnabled: true }); diff --git a/app/presentation/DirectoryItem/index.js b/app/presentation/DirectoryItem/index.js index c77bc4c26c1..c8f41202f50 100644 --- a/app/presentation/DirectoryItem/index.js +++ b/app/presentation/DirectoryItem/index.js @@ -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 }) => ( @@ -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, diff --git a/app/presentation/RoomItem/index.js b/app/presentation/RoomItem/index.js index 947fc022862..be89f968293 100644 --- a/app/presentation/RoomItem/index.js +++ b/app/presentation/RoomItem/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import { View, Text } from 'react-native'; import { connect } from 'react-redux'; @@ -12,7 +12,6 @@ import LastMessage from './LastMessage'; import { capitalize, formatDate } from '../../utils/room'; import Touchable from './Touchable'; import { themes } from '../../constants/colors'; -import database from '../../lib/database'; export { ROW_HEIGHT }; @@ -59,10 +58,7 @@ const RoomItem = React.memo(({ alert, type, avatarSize, - baseUrl, - userId, username, - token, id, prid, showLastMessage, @@ -77,33 +73,12 @@ const RoomItem = React.memo(({ theme, isFocused }) => { - const [avatarETag, setAvatarETag] = useState(); - useEffect(() => { if (connected && type === 'd' && id) { getUserPresence(id); } }, [connected]); - const handleAvatarChange = async() => { - if (type === 'd' && id) { - const db = database.active; - const usersCollection = db.collections.get('users'); - try { - const user = await usersCollection.find(id); - user.observe().subscribe((changes) => { - setAvatarETag(changes.avatarETag); - }); - } catch { - // Do nothing - } - } - }; - - useEffect(() => { - handleAvatarChange(); - }, []); - const date = lastMessage && formatDate(lastMessage.ts); let accessibilityLabel = name; @@ -141,15 +116,10 @@ const RoomItem = React.memo(({ accessibilityLabel={accessibilityLabel} > ( - + {name} @{username} @@ -65,11 +65,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, diff --git a/app/utils/avatar.js b/app/utils/avatar.js index fae17c136fd..369e1914fd6 100644 --- a/app/utils/avatar.js +++ b/app/utils/avatar.js @@ -1,32 +1,29 @@ -const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment, avatarETagFragment) => ( - `${ baseUrl }${ url }?format=png&size=${ uriSize }${ avatarAuthURLFragment }${ avatarETagFragment }` -); +const formatUrl = (url, size, query) => `${ url }?format=png&size=${ size }${ query }`; export const avatarURL = ({ - type, text, size, userId, token, avatar, baseUrl, avatarETag + 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 }`; } - - let avatarETagFragment; if (avatarETag) { - avatarETagFragment = `&${ avatarETag }`; + query += `&etag=${ avatarETag }`; } - - let uri; if (avatar) { - uri = avatar.includes('http') ? avatar : formatUrl(avatar, baseUrl, uriSize, avatarAuthURLFragment, avatarETagFragment); - } else { - uri = formatUrl(`/avatar/${ room }`, baseUrl, uriSize, avatarAuthURLFragment, avatarETagFragment); + if (avatar.startsWith('http')) { + return avatar; + } + + return formatUrl(`${ server }${ avatar }`, uriSize, query); } - return uri; + return formatUrl(`${ server }/avatar/${ room }`, uriSize, query); }; diff --git a/app/views/CreateDiscussionView/SelectChannel.js b/app/views/CreateDiscussionView/SelectChannel.js index f45bbfe1808..0a47a086b24 100644 --- a/app/views/CreateDiscussionView/SelectChannel.js +++ b/app/views/CreateDiscussionView/SelectChannel.js @@ -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 ( diff --git a/app/views/CreateDiscussionView/SelectUsers.js b/app/views/CreateDiscussionView/SelectUsers.js index 21b15946090..041894fee81 100644 --- a/app/views/CreateDiscussionView/SelectUsers.js +++ b/app/views/CreateDiscussionView/SelectUsers.js @@ -27,7 +27,7 @@ const SelectUsers = ({ }, 300); const getAvatar = text => avatarURL({ - text, type: 'd', userId, token, baseUrl: server + text, type: 'd', user: { id: userId, token }, server }); return ( diff --git a/app/views/ProfileView/index.js b/app/views/ProfileView/index.js index f6155c2a2c1..3cebec563a0 100644 --- a/app/views/ProfileView/index.js +++ b/app/views/ProfileView/index.js @@ -310,7 +310,6 @@ class ProfileView extends React.Component { const { avatarUrl, avatarSuggestions } = this.state; const { user, - baseUrl, theme, Accounts_AllowUserAvatarChange } = this.props; @@ -318,7 +317,7 @@ class ProfileView extends React.Component { return ( {this.renderAvatarButton({ - child: , + child: , onPress: () => this.resetAvatar(), disabled: !Accounts_AllowUserAvatarChange, key: 'profile-view-reset-avatar' @@ -340,7 +339,7 @@ class ProfileView extends React.Component { return this.renderAvatarButton({ disabled: !Accounts_AllowUserAvatarChange, key: `profile-view-avatar-${ service }`, - child: , + child: , onPress: () => this.setAvatar({ url, data: blob, service, contentType }) @@ -417,8 +416,6 @@ class ProfileView extends React.Component { name, username, email, newPassword, avatarUrl, customFields, avatar, saving } = this.state; const { - baseUrl, - user, theme, Accounts_AllowEmailChange, Accounts_AllowPasswordChange, @@ -446,9 +443,6 @@ class ProfileView extends React.Component { text={username} avatar={avatar && avatar.url} size={100} - baseUrl={baseUrl} - userId={user.id} - token={user.token} /> { - 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); return ( @@ -167,9 +159,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)); diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 67aff37e42f..9fd5c8105ea 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -551,7 +551,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); @@ -560,12 +560,9 @@ class RoomActionsView extends React.Component { <> {t === 'd' && member._id ? : null } diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js index 8794995ac80..c9d1bcaf717 100644 --- a/app/views/RoomInfoView/index.js +++ b/app/views/RoomInfoView/index.js @@ -20,7 +20,6 @@ import StatusBar from '../../containers/StatusBar'; import log 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, @@ -281,17 +275,14 @@ class RoomInfoView extends React.Component { } renderAvatar = (room, roomUser) => { - const { baseUrl, user, theme } = this.props; + const { theme } = this.props; return ( {this.t === 'd' && roomUser._id ? : null} @@ -371,8 +362,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 diff --git a/app/views/RoomView/Header/LeftButtons.js b/app/views/RoomView/Header/LeftButtons.js index 76cbc6c7591..84f3a786df3 100644 --- a/app/views/RoomView/Header/LeftButtons.js +++ b/app/views/RoomView/Header/LeftButtons.js @@ -14,7 +14,7 @@ const styles = StyleSheet.create({ }); const LeftButtons = React.memo(({ - tmid, unreadsCount, navigation, baseUrl, userId, token, title, t, theme, goRoomActionsView, isMasterDetail + tmid, unreadsCount, navigation, title, t, theme, goRoomActionsView, isMasterDetail }) => { if (!isMasterDetail || tmid) { const onPress = useCallback(() => navigation.goBack()); @@ -32,30 +32,22 @@ const LeftButtons = React.memo(({ ); } const onPress = useCallback(() => goRoomActionsView(), []); - if (baseUrl && userId && token) { - return ( - - ); - } - return null; + + return ( + + ); }); LeftButtons.propTypes = { tmid: PropTypes.string, unreadsCount: PropTypes.number, navigation: PropTypes.object, - baseUrl: PropTypes.string, - userId: PropTypes.string, - token: PropTypes.string, title: PropTypes.string, t: PropTypes.string, theme: PropTypes.string, diff --git a/app/views/SidebarView/index.js b/app/views/SidebarView/index.js index 2da42c460ce..a4a7d02689e 100644 --- a/app/views/SidebarView/index.js +++ b/app/views/SidebarView/index.js @@ -241,11 +241,8 @@ class Sidebar extends Component { From 78f819873b0e233b26c1482a1baa4c77827e130b Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Mon, 20 Jul 2020 15:09:04 -0300 Subject: [PATCH 03/34] [IMPROVEMENT] Avatar container --- app/containers/Avatar/index.js | 87 ++++------------------------------ 1 file changed, 9 insertions(+), 78 deletions(-) diff --git a/app/containers/Avatar/index.js b/app/containers/Avatar/index.js index 9d9b84cd795..f783e541fad 100644 --- a/app/containers/Avatar/index.js +++ b/app/containers/Avatar/index.js @@ -1,23 +1,13 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -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 { Q } from '@nozbe/watermelondb'; import database from '../../lib/database'; -import { avatarURL } from '../../utils/avatar'; import { getUserSelector } from '../../selectors/login'; -import Emoji from '../markdown/Emoji'; - -const Avatar = React.memo(({ - text, size, server, borderRadius, style, avatar, type, children, user, onPress, emoji, theme, getCustomEmoji -}) => { - if ((!text && !avatar) || !server) { - return null; - } +import Component from './Avatar'; +const Avatar = React.memo(({ type, text, ...props }) => { const [avatarETag, setAvatarETag] = useState(); const subscribeAvatar = async() => { @@ -42,77 +32,18 @@ const Avatar = React.memo(({ subscribeAvatar(); }, []); - const avatarStyle = { - width: size, - height: size, - borderRadius - }; - - if (emoji) { - return ( - - - - ); - } - - const uri = avatarURL({ - type, - text, - size, - user, - avatar, - server, - avatarETag - }); - return ( - - <> - - {children} - - + ); }); - 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 -}; - -Avatar.defaultProps = { - text: '', - size: 25, - type: 'd', - borderRadius: 4 + type: PropTypes.string }; const mapStateToProps = state => ({ From b64eb53aee1b3d211f9c82a08bad6d99ab9a1949 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Mon, 20 Jul 2020 16:27:33 -0300 Subject: [PATCH 04/34] [CHORE] Improve code --- app/containers/Avatar/Avatar.js | 58 ++++++++++++++++++--------------- app/containers/message/index.js | 4 +-- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/app/containers/Avatar/Avatar.js b/app/containers/Avatar/Avatar.js index 3630de906d9..d58b32408e9 100644 --- a/app/containers/Avatar/Avatar.js +++ b/app/containers/Avatar/Avatar.js @@ -20,41 +20,45 @@ const Avatar = React.memo(({ borderRadius }; + let image; if (emoji) { - return ( - - - + image = ( + + ); + } else { + const uri = avatarURL({ + type, + text, + size, + user, + avatar, + server, + avatarETag + }); + + image = ( + ); } - const uri = avatarURL({ - type, - text, - size, - user, - avatar, - server, - avatarETag - }); return ( <> - + {image} {children} diff --git a/app/containers/message/index.js b/app/containers/message/index.js index 14610ec3674..bcc12f3d677 100644 --- a/app/containers/message/index.js +++ b/app/containers/message/index.js @@ -70,9 +70,7 @@ class MessageContainer extends React.Component { theme: 'light' } - state = { - author: null - } + state = {} async componentDidMount() { const { item } = this.props; From 71912404812131e6a719c57d79a1f8ec01d8deef Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Fri, 31 Jul 2020 10:28:36 -0300 Subject: [PATCH 05/34] Allow static image on Avatar --- app/containers/Avatar/Avatar.js | 21 ++++++++++++++++++--- app/views/ProfileView/index.js | 3 ++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/containers/Avatar/Avatar.js b/app/containers/Avatar/Avatar.js index d58b32408e9..ab1d4889765 100644 --- a/app/containers/Avatar/Avatar.js +++ b/app/containers/Avatar/Avatar.js @@ -8,7 +8,21 @@ 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 + text, + size, + server, + borderRadius, + style, + avatar, + type, + children, + user, + onPress, + emoji, + theme, + getCustomEmoji, + avatarETag, + isStatic }) => { if ((!text && !avatar) || !server) { return null; @@ -46,7 +60,7 @@ const Avatar = React.memo(({ From ff73068bdd515bb92bfe23411c72b80fb9eb1b25 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Fri, 31 Jul 2020 11:10:45 -0300 Subject: [PATCH 06/34] Fix avatar changing while change username (#1583) Co-authored-by: Prateek93a --- app/views/ProfileView/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/ProfileView/index.js b/app/views/ProfileView/index.js index a08ae8e2db0..dd5b0d573aa 100644 --- a/app/views/ProfileView/index.js +++ b/app/views/ProfileView/index.js @@ -427,6 +427,7 @@ class ProfileView extends React.Component { name, username, email, newPassword, avatarUrl, customFields, avatar, saving } = this.state; const { + user, theme, Accounts_AllowEmailChange, Accounts_AllowPasswordChange, @@ -451,7 +452,7 @@ class ProfileView extends React.Component { > Date: Fri, 31 Jul 2020 11:39:08 -0300 Subject: [PATCH 07/34] Add default props to properly update on Sidebar and ProfileView --- app/containers/Avatar/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/containers/Avatar/index.js b/app/containers/Avatar/index.js index f783e541fad..0d02b99d3c9 100644 --- a/app/containers/Avatar/index.js +++ b/app/containers/Avatar/index.js @@ -45,6 +45,10 @@ Avatar.propTypes = { text: PropTypes.string, type: PropTypes.string }; +Avatar.defaultProps = { + text: '', + type: 'd' +}; const mapStateToProps = state => ({ user: getUserSelector(state), From b1c46e6a87614ac484f033b0c8f06248d5ad3f50 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Fri, 31 Jul 2020 12:34:28 -0300 Subject: [PATCH 08/34] Fix subscribing on the wrong moment --- app/lib/methods/getUsersPresence.js | 3 +++ app/lib/rocketchat.js | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/lib/methods/getUsersPresence.js b/app/lib/methods/getUsersPresence.js index 7e6460e3b07..42c669d5540 100644 --- a/app/lib/methods/getUsersPresence.js +++ b/app/lib/methods/getUsersPresence.js @@ -22,6 +22,9 @@ export function subscribeUsersPresence() { } else { this.sdk.subscribe('stream-notify-logged', 'user-status'); } + + this.sdk.subscribe('stream-notify-logged', 'updateAvatar'); + this.sdk.subscribe('stream-notify-logged', 'Users:NameChanged'); } let ids = []; diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 2c24261d162..a42abeb8bc7 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -246,10 +246,6 @@ const RocketChat = { this.usersListener = this.sdk.onStreamData('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage))); - this.sdk.subscribe('stream-notify-logged', 'updateAvatar'); - - this.sdk.subscribe('stream-notify-logged', 'Users:NameChanged'); - this.notifyLoggedListener = this.sdk.onStreamData('stream-notify-logged', protectedFunction(async(ddpMessage) => { const { eventName } = ddpMessage.fields; if (/user-status/.test(eventName)) { From 9eaf2aefb8e1260f64d8cd965c93fb9c091959c4 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Fri, 31 Jul 2020 13:56:12 -0300 Subject: [PATCH 09/34] Storyshots update --- __tests__/__snapshots__/Storyshots.test.js.snap | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index f70e4a6a9ff..54172e7ddc0 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -4257,20 +4257,6 @@ exports[`Storyshots Message list message 1`] = ` } } > - Date: Fri, 31 Jul 2020 15:03:03 -0300 Subject: [PATCH 10/34] RoomItem using Avatar Component --- app/presentation/RoomItem/RoomItem.js | 7 +++++-- app/presentation/RoomItem/Wrapper.js | 3 +++ app/presentation/RoomItem/index.js | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/app/presentation/RoomItem/RoomItem.js b/app/presentation/RoomItem/RoomItem.js index 189840bae84..05447c931d3 100644 --- a/app/presentation/RoomItem/RoomItem.js +++ b/app/presentation/RoomItem/RoomItem.js @@ -44,7 +44,8 @@ const RoomItem = ({ onPress, toggleFav, toggleRead, - hideChannel + hideChannel, + avatarETag }) => ( { const [, setForceUpdate] = useState(1); + const [avatarETag, setAvatarETag] = useState(''); useEffect(() => { if (connected && item.t === 'd' && id) { @@ -55,7 +58,27 @@ const RoomItemContainer = React.memo(({ } }, [connected]); + const subscribeUser = async() => { + const db = database.active; + const usersCollection = db.collections.get('users'); + try { + const [user] = await usersCollection.query(Q.where('username', Q.eq(username))).fetch(); + if (user) { + const observable = user.observe(); + observable.subscribe((u) => { + setAvatarETag(u.avatarETag); + }); + } + } catch { + // Do nothing + } + }; + useEffect(() => { + if (item.t === 'd') { + subscribeUser(); + } + if (item?.observe) { const observable = item.observe(); const subscription = observable?.subscribe?.(() => { @@ -125,6 +148,7 @@ const RoomItemContainer = React.memo(({ useRealName={useRealName} unread={item.unread} groupMentions={item.groupMentions} + avatarETag={avatarETag} /> ); }, arePropsEqual); From 0bf55516d72dc0141e59feeb6a641ff95e5f7d5d Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Fri, 31 Jul 2020 16:16:40 -0300 Subject: [PATCH 11/34] use iife to unsubscribe from user --- app/containers/Avatar/index.js | 38 ++++++++++++++++-------------- app/presentation/RoomItem/index.js | 34 +++++++++++++------------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/app/containers/Avatar/index.js b/app/containers/Avatar/index.js index 0d02b99d3c9..6cfec18ce9e 100644 --- a/app/containers/Avatar/index.js +++ b/app/containers/Avatar/index.js @@ -10,26 +10,28 @@ import Component from './Avatar'; const Avatar = React.memo(({ type, text, ...props }) => { const [avatarETag, setAvatarETag] = useState(); - const subscribeAvatar = async() => { - if (type === 'd') { - const db = database.active; - const usersCollection = db.collections.get('users'); - try { - const [userRecord] = await usersCollection.query(Q.where('username', text)).fetch(); - if (userRecord) { - const observable = userRecord.observe(); - observable.subscribe((u) => { - setAvatarETag(u.avatarETag); - }); + useEffect(() => { + let subscription; + (async() => { + if (type === 'd') { + const db = database.active; + const usersCollection = db.collections.get('users'); + try { + const [userRecord] = await usersCollection.query(Q.where('username', text)).fetch(); + if (userRecord) { + const observable = userRecord.observe(); + subscription = observable.subscribe((u) => { + setAvatarETag(u.avatarETag); + }); + } + } catch { + // Do nothing } - } catch { - // Do nothing } - } - }; - - useEffect(() => { - subscribeAvatar(); + })(); + return () => { + subscription?.unsubscribe?.(); + }; }, []); return ( diff --git a/app/presentation/RoomItem/index.js b/app/presentation/RoomItem/index.js index f6c510c0292..3e1c3e676d4 100644 --- a/app/presentation/RoomItem/index.js +++ b/app/presentation/RoomItem/index.js @@ -59,25 +59,24 @@ const RoomItemContainer = React.memo(({ } }, [connected]); - const subscribeUser = async() => { - const db = database.active; - const usersCollection = db.collections.get('users'); - try { - const [user] = await usersCollection.query(Q.where('username', Q.eq(username))).fetch(); - if (user) { - const observable = user.observe(); - observable.subscribe((u) => { - setAvatarETag(u.avatarETag); - }); - } - } catch { - // Do nothing - } - }; - useEffect(() => { + let userSub; if (item.t === 'd') { - subscribeUser(); + (async() => { + const db = database.active; + const usersCollection = db.collections.get('users'); + try { + const [user] = await usersCollection.query(Q.where('username', Q.eq(username))).fetch(); + if (user) { + const observable = user.observe(); + userSub = observable.subscribe((u) => { + setAvatarETag(u.avatarETag); + }); + } + } catch { + // Do nothing + } + })(); } if (item?.observe) { @@ -87,6 +86,7 @@ const RoomItemContainer = React.memo(({ }); return () => { + userSub?.unsubscribe?.(); subscription?.unsubscribe?.(); }; } From 441ad88743b4e44b24dbd62060bd3bf1921bb136 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Tue, 4 Aug 2020 09:41:17 -0300 Subject: [PATCH 12/34] Use component on avatar container --- app/containers/Avatar/index.js | 100 +++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 43 deletions(-) diff --git a/app/containers/Avatar/index.js b/app/containers/Avatar/index.js index 6cfec18ce9e..75104c9d2eb 100644 --- a/app/containers/Avatar/index.js +++ b/app/containers/Avatar/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Q } from '@nozbe/watermelondb'; @@ -7,50 +7,64 @@ import database from '../../lib/database'; import { getUserSelector } from '../../selectors/login'; import Component from './Avatar'; -const Avatar = React.memo(({ type, text, ...props }) => { - const [avatarETag, setAvatarETag] = useState(); - - useEffect(() => { - let subscription; - (async() => { - if (type === 'd') { - const db = database.active; - const usersCollection = db.collections.get('users'); - try { - const [userRecord] = await usersCollection.query(Q.where('username', text)).fetch(); - if (userRecord) { - const observable = userRecord.observe(); - subscription = observable.subscribe((u) => { - setAvatarETag(u.avatarETag); - }); - } - } catch { - // Do nothing +class Avatar extends React.Component { + static propTypes = { + text: PropTypes.string, + type: PropTypes.string + }; + + static defaultProps = { + text: '', + type: 'd' + }; + + constructor(props) { + super(props); + this.state = { avatarETag: '' }; + this.init(); + } + + 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; + this.setState({ avatarETag }); + }); } + } catch { + // User was not found } - })(); - return () => { - subscription?.unsubscribe?.(); - }; - }, []); - - return ( - - ); -}); -Avatar.propTypes = { - text: PropTypes.string, - type: PropTypes.string -}; -Avatar.defaultProps = { - text: '', - type: 'd' -}; + } + } + + render() { + const { avatarETag } = this.state; + return ( + + ); + } +} const mapStateToProps = state => ({ user: getUserSelector(state), From 0ae11142e6014d9142f867e688c09d5bbcacadca Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Tue, 4 Aug 2020 10:11:37 -0300 Subject: [PATCH 13/34] RoomItem as a React.Component --- app/lib/methods/getUsersPresence.js | 4 +- app/presentation/RoomItem/index.js | 335 +++++++++++++++------------- 2 files changed, 179 insertions(+), 160 deletions(-) diff --git a/app/lib/methods/getUsersPresence.js b/app/lib/methods/getUsersPresence.js index 42c669d5540..029371d0d99 100644 --- a/app/lib/methods/getUsersPresence.js +++ b/app/lib/methods/getUsersPresence.js @@ -106,5 +106,7 @@ export function getUserPresence(uid) { }, 2000); } - ids.push(uid); + if (uid) { + ids.push(uid); + } } diff --git a/app/presentation/RoomItem/index.js b/app/presentation/RoomItem/index.js index 3e1c3e676d4..0929acab800 100644 --- a/app/presentation/RoomItem/index.js +++ b/app/presentation/RoomItem/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Q } from '@nozbe/watermelondb'; @@ -21,178 +21,195 @@ const attrs = [ 'showLastMessage' ]; -const arePropsEqual = (oldProps, newProps) => attrs.every(key => oldProps[key] === newProps[key]); - -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); - const [avatarETag, setAvatarETag] = useState(''); - - useEffect(() => { - if (connected && item.t === 'd' && id) { +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 + }; + + static defaultProps = { + avatarSize: 48, + status: 'offline', + getUserPresence: () => {}, + getRoomTitle: () => 'title', + getRoomAvatar: () => '', + getIsGroupChat: () => false, + getIsRead: () => false, + swipeEnabled: true + } + + constructor(props) { + super(props); + this.state = { avatarETag: '' }; + this.init(); + } + + shouldComponentUpdate(nextProps) { + 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(() => { - let userSub; - if (item.t === 'd') { - (async() => { - const db = database.active; - const usersCollection = db.collections.get('users'); - try { - const [user] = await usersCollection.query(Q.where('username', Q.eq(username))).fetch(); - if (user) { - const observable = user.observe(); - userSub = observable.subscribe((u) => { - setAvatarETag(u.avatarETag); - }); - } - } catch { - // Do nothing + } + + componentWillUnmount() { + if (this.userSubscription?.unsubscribe) { + this.userSubscription.unsubscribe(); + } + if (this.roomSubscription?.unsubscribe) { + this.roomSubscription.unsubscribe(); + } + } + + get isDirect() { + const { item: { t } } = this.props; + return t === 'd'; + } + + init = async() => { + if (this.isDirect) { + const { username } = this.props; + const db = database.active; + const usersCollection = db.collections.get('users'); + try { + const [user] = await usersCollection.query(Q.where('username', Q.eq(username))).fetch(); + if (user) { + const observable = user.observe(); + this.userSubscription = observable.subscribe((u) => { + const { avatarETag } = u; + this.setState({ avatarETag }); + }); } - })(); + } catch { + // Do nothing + } } + 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 () => { - userSub?.unsubscribe?.(); - 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 (item.userMentions > 0) { - accessibilityLabel += `, ${ I18n.t('you_were_mentioned') }`; - } + render() { + const { avatarETag } = this.state; + const { + item, + getRoomTitle, + getRoomAvatar, + getIsGroupChat, + getIsRead, + onPress, + 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 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 (date) { - accessibilityLabel += `, ${ I18n.t('last_message') } ${ date }`; - } + if (item.userMentions > 0) { + accessibilityLabel += `, ${ I18n.t('you_were_mentioned') }`; + } - return ( - - ); -}, 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 -}; + if (date) { + accessibilityLabel += `, ${ I18n.t('last_message') } ${ date }`; + } -RoomItemContainer.defaultProps = { - avatarSize: 48, - status: 'offline', - getUserPresence: () => {}, - getRoomTitle: () => 'title', - getRoomAvatar: () => '', - getIsGroupChat: () => false, - getIsRead: () => false, - swipeEnabled: true -}; + return ( + + ); + } +} const mapStateToProps = (state, ownProps) => { let status = 'offline'; From c1ecd072c5856b2f8d81f5bc6ae9107c65bbb17f Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Tue, 4 Aug 2020 10:38:04 -0300 Subject: [PATCH 14/34] Move servers models to servers folder --- app/lib/database/index.js | 14 +++++------ app/lib/database/model/User.js | 16 +++---------- app/lib/database/model/Users.js | 14 ----------- .../database/model/{ => servers}/Server.js | 0 app/lib/database/model/servers/User.js | 24 +++++++++++++++++++ .../migrations.js} | 0 6 files changed, 34 insertions(+), 34 deletions(-) delete mode 100644 app/lib/database/model/Users.js rename app/lib/database/model/{ => servers}/Server.js (100%) create mode 100644 app/lib/database/model/servers/User.js rename app/lib/database/model/{serversMigrations.js => servers/migrations.js} (100%) diff --git a/app/lib/database/index.js b/app/lib/database/index.js index 2438a66984d..eb32c4a5c21 100644 --- a/app/lib/database/index.js +++ b/app/lib/database/index.js @@ -16,15 +16,15 @@ 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 Users from './model/Users'; + +import LoggedUser from './model/servers/User'; +import Server from './model/servers/Server'; 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'; @@ -59,7 +59,7 @@ export const getDatabase = (database = '') => { Role, Permission, SlashCommand, - Users + User ], actionsEnabled: true }); @@ -73,7 +73,7 @@ class DB { schema: serversSchema, migrations: serversMigrations }), - modelClasses: [Server, User], + modelClasses: [Server, LoggedUser], actionsEnabled: true }) } @@ -115,7 +115,7 @@ class DB { Permission, CustomEmoji, FrequentlyUsedEmoji, - Users + User ], actionsEnabled: true }); diff --git a/app/lib/database/model/User.js b/app/lib/database/model/User.js index a220a9f4bd3..11073a90639 100644 --- a/app/lib/database/model/User.js +++ b/app/lib/database/model/User.js @@ -1,24 +1,14 @@ import { Model } from '@nozbe/watermelondb'; -import { field, json } from '@nozbe/watermelondb/decorators'; - -import { sanitizer } from '../utils'; +import { field } from '@nozbe/watermelondb/decorators'; 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('status') status; - - @field('statusText') statusText; - - @json('roles', sanitizer) roles; + @field('username') username; @field('avatar_etag') avatarETag; } diff --git a/app/lib/database/model/Users.js b/app/lib/database/model/Users.js deleted file mode 100644 index 11073a90639..00000000000 --- a/app/lib/database/model/Users.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Model } from '@nozbe/watermelondb'; -import { field } from '@nozbe/watermelondb/decorators'; - -export default class User extends Model { - static table = 'users'; - - @field('_id') _id; - - @field('name') name; - - @field('username') username; - - @field('avatar_etag') avatarETag; -} diff --git a/app/lib/database/model/Server.js b/app/lib/database/model/servers/Server.js similarity index 100% rename from app/lib/database/model/Server.js rename to app/lib/database/model/servers/Server.js diff --git a/app/lib/database/model/servers/User.js b/app/lib/database/model/servers/User.js new file mode 100644 index 00000000000..3eb9874d03c --- /dev/null +++ b/app/lib/database/model/servers/User.js @@ -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; +} diff --git a/app/lib/database/model/serversMigrations.js b/app/lib/database/model/servers/migrations.js similarity index 100% rename from app/lib/database/model/serversMigrations.js rename to app/lib/database/model/servers/migrations.js From 7caa0e059920aa58e48daae9f0acdf5ea0296731 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 09:30:27 -0300 Subject: [PATCH 15/34] Avatar -> AvatarContainer --- app/containers/Avatar/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/containers/Avatar/index.js b/app/containers/Avatar/index.js index 75104c9d2eb..8f62e707c58 100644 --- a/app/containers/Avatar/index.js +++ b/app/containers/Avatar/index.js @@ -5,9 +5,9 @@ import { Q } from '@nozbe/watermelondb'; import database from '../../lib/database'; import { getUserSelector } from '../../selectors/login'; -import Component from './Avatar'; +import Avatar from './Avatar'; -class Avatar extends React.Component { +class AvatarContainer extends React.Component { static propTypes = { text: PropTypes.string, type: PropTypes.string @@ -58,7 +58,7 @@ class Avatar extends React.Component { render() { const { avatarETag } = this.state; return ( - @@ -70,4 +70,4 @@ const mapStateToProps = state => ({ user: getUserSelector(state), server: state.share.server || state.server.server }); -export default connect(mapStateToProps)(Avatar); +export default connect(mapStateToProps)(AvatarContainer); From 739836e556666fbb411e41b81cf923ffdd148c22 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 09:32:14 -0300 Subject: [PATCH 16/34] Users indexed fields --- app/lib/database/model/migrations.js | 4 ++-- app/lib/database/schema/app.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/lib/database/model/migrations.js b/app/lib/database/model/migrations.js index 7c30ad240aa..b733c245f47 100644 --- a/app/lib/database/model/migrations.js +++ b/app/lib/database/model/migrations.js @@ -136,9 +136,9 @@ export default schemaMigrations({ createTable({ name: 'users', columns: [ - { name: '_id', type: 'string', isOptional: true }, + { name: '_id', type: 'string', isIndexed: true }, { name: 'name', type: 'string', isOptional: true }, - { name: 'username', type: 'string', isOptional: true }, + { name: 'username', type: 'string', isIndexed: true }, { name: 'avatar_etag', type: 'string', isOptional: true } ] }) diff --git a/app/lib/database/schema/app.js b/app/lib/database/schema/app.js index 49d02c4494e..e5102c86904 100644 --- a/app/lib/database/schema/app.js +++ b/app/lib/database/schema/app.js @@ -244,9 +244,9 @@ export default appSchema({ tableSchema({ name: 'users', columns: [ - { name: '_id', type: 'string', isOptional: true }, + { name: '_id', type: 'string', isIndexed: true }, { name: 'name', type: 'string', isOptional: true }, - { name: 'username', type: 'string', isOptional: true }, + { name: 'username', type: 'string', isIndexed: true }, { name: 'avatar_etag', type: 'string', isOptional: true } ] }) From c6ffbf791f0438752ace6ed4c10324fef8f486d8 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 09:37:23 -0300 Subject: [PATCH 17/34] Initialize author and check if u is present --- app/containers/message/index.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/containers/message/index.js b/app/containers/message/index.js index bcc12f3d677..22dd5d505bc 100644 --- a/app/containers/message/index.js +++ b/app/containers/message/index.js @@ -70,7 +70,9 @@ class MessageContainer extends React.Component { theme: 'light' } - state = {} + state = { + author: null + } async componentDidMount() { const { item } = this.props; @@ -84,10 +86,10 @@ class MessageContainer extends React.Component { const db = database.active; const usersCollection = db.collections.get('users'); try { - const user = await usersCollection.find(item.u._id); + const user = await usersCollection.find(item.u?._id); const observable = user.observe(); - this.userSubscription = observable.subscribe((changes) => { - this.setState({ author: changes }); + this.userSubscription = observable.subscribe((author) => { + this.setState({ author }); this.forceUpdate(); }); } catch { From df75c65d808353ca3774c6688e0a6d15dab66c0d Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 09:41:18 -0300 Subject: [PATCH 18/34] Not was found -> User not found (turn comments more relevant) --- app/lib/methods/getUsersPresence.js | 2 +- app/lib/rocketchat.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/methods/getUsersPresence.js b/app/lib/methods/getUsersPresence.js index 029371d0d99..14236723aaa 100644 --- a/app/lib/methods/getUsersPresence.js +++ b/app/lib/methods/getUsersPresence.js @@ -78,7 +78,7 @@ export default async function getUsersPresence() { }); }); } catch (e) { - // Not was found + // User not found await db.action(async() => { await userCollection.create((u) => { u._raw = sanitizedRaw({ id: user._id }, userCollection.schema); diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index f27976d562a..a517a25c8cd 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -309,7 +309,7 @@ const RocketChat = { }); }); } catch { - // Not was found + // User not found await db.action(async() => { await userCollection.create((u) => { u._raw = sanitizedRaw({ id: userNameChanged._id }, userCollection.schema); From c0c61ea41ad597746989c3fe3ee9e951a62cd43c Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 09:45:39 -0300 Subject: [PATCH 19/34] RoomItemInner -> Wrapper --- app/presentation/RoomItem/Wrapper.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/presentation/RoomItem/Wrapper.js b/app/presentation/RoomItem/Wrapper.js index b0f31e852a2..c6638555ac2 100644 --- a/app/presentation/RoomItem/Wrapper.js +++ b/app/presentation/RoomItem/Wrapper.js @@ -6,7 +6,7 @@ import styles from './styles'; import { themes } from '../../constants/colors'; import Avatar from '../../containers/Avatar/Avatar'; -const RoomItemInner = ({ +const Wrapper = ({ accessibilityLabel, avatar, avatarSize, @@ -44,7 +44,7 @@ const RoomItemInner = ({ ); -RoomItemInner.propTypes = { +Wrapper.propTypes = { accessibilityLabel: PropTypes.string, avatar: PropTypes.string, avatarSize: PropTypes.number, @@ -57,4 +57,4 @@ RoomItemInner.propTypes = { children: PropTypes.element }; -export default RoomItemInner; +export default Wrapper; From 78b776775ab2877512d1dc0d98756e0250c6ae28 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 09:49:26 -0300 Subject: [PATCH 20/34] Revert Avatar Touchable logic --- .../__snapshots__/Storyshots.test.js.snap | 14 ++++++++++++++ app/containers/Avatar/Avatar.js | 19 +++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index 4e85a949072..b9d4dc1eefc 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -4199,6 +4199,20 @@ exports[`Storyshots Message list message 1`] = ` } } > + + {image} + + ); + } + return ( - - <> - {image} - {children} - - + + {image} + {children} + ); }); From 19b12fd7febfc3e635a417acc90858ad67e0c4ff Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 09:54:54 -0300 Subject: [PATCH 21/34] Revert responsability of LeftButton on Tablet Mode --- app/views/RoomView/Header/LeftButtons.js | 26 +++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/app/views/RoomView/Header/LeftButtons.js b/app/views/RoomView/Header/LeftButtons.js index 84f3a786df3..68ad9b08f37 100644 --- a/app/views/RoomView/Header/LeftButtons.js +++ b/app/views/RoomView/Header/LeftButtons.js @@ -14,7 +14,7 @@ const styles = StyleSheet.create({ }); const LeftButtons = React.memo(({ - tmid, unreadsCount, navigation, title, t, theme, goRoomActionsView, isMasterDetail + tmid, unreadsCount, navigation, baseUrl, userId, token, title, t, theme, goRoomActionsView, isMasterDetail }) => { if (!isMasterDetail || tmid) { const onPress = useCallback(() => navigation.goBack()); @@ -33,21 +33,27 @@ const LeftButtons = React.memo(({ } const onPress = useCallback(() => goRoomActionsView(), []); - return ( - - ); + if (baseUrl && userId && token) { + return ( + + ); + } + return null; }); LeftButtons.propTypes = { tmid: PropTypes.string, unreadsCount: PropTypes.number, navigation: PropTypes.object, + baseUrl: PropTypes.string, + userId: PropTypes.string, + token: PropTypes.string, title: PropTypes.string, t: PropTypes.string, theme: PropTypes.string, From 615c5c7ae8ade2909e58fa77a256c873d93046d3 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 09:59:48 -0300 Subject: [PATCH 22/34] Prevent setState on constructor --- app/containers/Avatar/index.js | 11 ++++++++++- app/presentation/RoomItem/index.js | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/containers/Avatar/index.js b/app/containers/Avatar/index.js index 8f62e707c58..fff632b8526 100644 --- a/app/containers/Avatar/index.js +++ b/app/containers/Avatar/index.js @@ -20,10 +20,15 @@ class AvatarContainer extends React.Component { constructor(props) { super(props); + this.mounted = false; this.state = { avatarETag: '' }; this.init(); } + componentDidMount() { + this.mounted = true; + } + componentWillUnmount() { if (this.userSubscription?.unsubscribe) { this.userSubscription.unsubscribe(); @@ -46,7 +51,11 @@ class AvatarContainer extends React.Component { const observable = user.observe(); this.userSubscription = observable.subscribe((u) => { const { avatarETag } = u; - this.setState({ avatarETag }); + if (this.mounted) { + this.setState({ avatarETag }); + } else { + this.state.avatarETag = avatarETag; + } }); } } catch { diff --git a/app/presentation/RoomItem/index.js b/app/presentation/RoomItem/index.js index 0929acab800..35bc6ed35ed 100644 --- a/app/presentation/RoomItem/index.js +++ b/app/presentation/RoomItem/index.js @@ -63,10 +63,15 @@ class RoomItemContainer extends React.Component { constructor(props) { super(props); + this.mounted = false; this.state = { avatarETag: '' }; this.init(); } + componentDidMount() { + this.mounted = true; + } + shouldComponentUpdate(nextProps) { const { props } = this; return !attrs.every(key => props[key] === nextProps[key]); @@ -104,7 +109,11 @@ class RoomItemContainer extends React.Component { const observable = user.observe(); this.userSubscription = observable.subscribe((u) => { const { avatarETag } = u; - this.setState({ avatarETag }); + if (this.mounted) { + this.setState({ avatarETag }); + } else { + this.state.avatarETag = avatarETag; + } }); } } catch { From 95e49c843bdddb5b3a906bbde809bdb0b8cc5ad7 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 10:06:37 -0300 Subject: [PATCH 23/34] Run avatarURL only when its not static --- app/containers/Avatar/Avatar.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/app/containers/Avatar/Avatar.js b/app/containers/Avatar/Avatar.js index 9a60acace93..1075524ab53 100644 --- a/app/containers/Avatar/Avatar.js +++ b/app/containers/Avatar/Avatar.js @@ -47,21 +47,24 @@ const Avatar = React.memo(({ /> ); } else { - const uri = avatarURL({ - type, - text, - size, - user, - avatar, - server, - avatarETag - }); + let uri = avatar; + if (!isStatic) { + uri = avatarURL({ + type, + text, + size, + user, + avatar, + server, + avatarETag + }); + } image = ( Date: Wed, 5 Aug 2020 10:16:54 -0300 Subject: [PATCH 24/34] Add streams RC Version --- app/lib/methods/getUsersPresence.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/lib/methods/getUsersPresence.js b/app/lib/methods/getUsersPresence.js index 14236723aaa..3986e67df06 100644 --- a/app/lib/methods/getUsersPresence.js +++ b/app/lib/methods/getUsersPresence.js @@ -23,7 +23,9 @@ export function subscribeUsersPresence() { 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'); } From ddb41eac657f3b05d5d6b53d6685da27c304145a Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 10:19:17 -0300 Subject: [PATCH 25/34] Move entire add user logic to result.success --- app/lib/methods/getUsersPresence.js | 42 +++++++++++++++-------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/app/lib/methods/getUsersPresence.js b/app/lib/methods/getUsersPresence.js index 3986e67df06..c983b5d5284 100644 --- a/app/lib/methods/getUsersPresence.js +++ b/app/lib/methods/getUsersPresence.js @@ -53,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) { @@ -67,28 +69,28 @@ export default async function getUsersPresence() { reduxStore.dispatch(setActiveUsers(activeUsers)); }); ids = []; - } - const db = database.active; - const userCollection = db.collections.get('users'); - result.users?.forEach(async(user) => { - try { - const userRecord = await userCollection.find(user._id); - await db.action(async() => { - await userRecord.update((u) => { - Object.assign(u, user); + 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 (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 } From 94d7f09815be0f21f8e5e2e6357df92bbc0435d3 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 10:43:43 -0300 Subject: [PATCH 26/34] Reorder init on RoomItem --- app/presentation/RoomItem/index.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/app/presentation/RoomItem/index.js b/app/presentation/RoomItem/index.js index 35bc6ed35ed..5c6fc7c63a4 100644 --- a/app/presentation/RoomItem/index.js +++ b/app/presentation/RoomItem/index.js @@ -72,7 +72,11 @@ class RoomItemContainer extends React.Component { this.mounted = true; } - shouldComponentUpdate(nextProps) { + shouldComponentUpdate(nextProps, nextState) { + const { avatarETag } = this.state; + if (nextState.avatarETag !== avatarETag) { + return true; + } const { props } = this; return !attrs.every(key => props[key] === nextProps[key]); } @@ -99,6 +103,14 @@ class RoomItemContainer extends React.Component { } init = async() => { + const { item } = this.props; + if (item?.observe) { + const observable = item.observe(); + this.roomSubscription = observable?.subscribe?.(() => { + this.forceUpdate(); + }); + } + if (this.isDirect) { const { username } = this.props; const db = database.active; @@ -120,14 +132,6 @@ class RoomItemContainer extends React.Component { // Do nothing } } - - const { item } = this.props; - if (item?.observe) { - const observable = item.observe(); - this.roomSubscription = observable?.subscribe?.(() => { - this.forceUpdate(); - }); - } } render() { From 92eac2833c1b55ab32a1627831dff0f234ca89cf Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 10:46:14 -0300 Subject: [PATCH 27/34] onPress as a class function --- app/presentation/RoomItem/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/presentation/RoomItem/index.js b/app/presentation/RoomItem/index.js index 5c6fc7c63a4..4cb82ee24be 100644 --- a/app/presentation/RoomItem/index.js +++ b/app/presentation/RoomItem/index.js @@ -134,6 +134,11 @@ class RoomItemContainer extends React.Component { } } + onPress = () => { + const { item, onPress } = this.props; + return onPress(item); + } + render() { const { avatarETag } = this.state; const { @@ -142,7 +147,6 @@ class RoomItemContainer extends React.Component { getRoomAvatar, getIsGroupChat, getIsRead, - onPress, width, toggleFav, toggleRead, @@ -164,7 +168,6 @@ class RoomItemContainer extends React.Component { 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; @@ -188,7 +191,7 @@ class RoomItemContainer extends React.Component { avatar={avatar} isGroupChat={isGroupChat} isRead={isRead} - onPress={_onPress} + onPress={this.onPress} date={date} accessibilityLabel={accessibilityLabel} userMentions={item.userMentions} From f63738b0226247124c78a8f6907bc7950b2ca3fd Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 10:56:07 -0300 Subject: [PATCH 28/34] Fix roomItem using same username --- app/presentation/RoomItem/index.js | 40 +++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/app/presentation/RoomItem/index.js b/app/presentation/RoomItem/index.js index 4cb82ee24be..7f3a9f2d6af 100644 --- a/app/presentation/RoomItem/index.js +++ b/app/presentation/RoomItem/index.js @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { Q } from '@nozbe/watermelondb'; import I18n from '../../i18n'; import { ROW_HEIGHT } from './styles'; @@ -97,9 +96,14 @@ class RoomItemContainer extends React.Component { } } + get isGroupChat() { + const { item, getIsGroupChat } = this.props; + return getIsGroupChat(item); + } + get isDirect() { - const { item: { t } } = this.props; - return t === 'd'; + const { item: { t }, id } = this.props; + return t === 'd' && id && !this.isGroupChat; } init = async() => { @@ -112,24 +116,22 @@ class RoomItemContainer extends React.Component { } if (this.isDirect) { - const { username } = this.props; + const { id } = this.props; const db = database.active; const usersCollection = db.collections.get('users'); try { - const [user] = await usersCollection.query(Q.where('username', Q.eq(username))).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; - } - }); - } + 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 { - // Do nothing + // User not found } } } @@ -145,7 +147,6 @@ class RoomItemContainer extends React.Component { item, getRoomTitle, getRoomAvatar, - getIsGroupChat, getIsRead, width, toggleFav, @@ -166,7 +167,6 @@ class RoomItemContainer extends React.Component { } = this.props; const name = getRoomTitle(item); const avatar = getRoomAvatar(item); - const isGroupChat = getIsGroupChat(item); const isRead = getIsRead(item); const date = item.lastMessage?.ts && formatDate(item.lastMessage.ts); @@ -189,7 +189,7 @@ class RoomItemContainer extends React.Component { Date: Wed, 5 Aug 2020 14:51:35 -0300 Subject: [PATCH 29/34] Add avatar Stories --- .../__snapshots__/Storyshots.test.js.snap | 968 +++++++++++++++++- app/containers/Avatar/Avatar.js | 3 +- app/containers/markdown/Emoji.js | 11 +- storybook/stories/Avatar.js | 137 +++ storybook/stories/index.js | 3 + 5 files changed, 1101 insertions(+), 21 deletions(-) create mode 100644 storybook/stories/Avatar.js diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index b9d4dc1eefc..9a70781e5d9 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -1,5 +1,926 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Storyshots Avatar list Avatar 1`] = ` + + + + Avatar by text + + + + + + + + Avatar by url + + + + + + + + Avatar by path + + + + + + + + With ETag + + + + + + + + Without ETag + + + + + + + + Emoji + + + + + + + + Direct + + + + + + + + Channel + + + + + + + + Touchable + + + + Static + + + + + + + + Custom borderRadius + + + + + + + + Children + + + + + + + + + Wrong server + + + + + + + + Custom style + + + + + + + + +`; + exports[`Storyshots Markdown list Markdown 1`] = ` @@ -1255,6 +2177,7 @@ exports[`Storyshots Markdown list Markdown 1`] = ` "fontSize": 16, "fontWeight": "400", }, + Object {}, ] } > @@ -1303,10 +2226,13 @@ exports[`Storyshots Markdown list Markdown 1`] = ` Object { "overflow": "hidden", }, - Object { - "height": 20, - "width": 20, - }, + Array [ + Object { + "height": 20, + "width": 20, + }, + Object {}, + ], ] } > @@ -1361,10 +2287,13 @@ exports[`Storyshots Markdown list Markdown 1`] = ` Object { "overflow": "hidden", }, - Object { - "height": 20, - "width": 20, - }, + Array [ + Object { + "height": 20, + "width": 20, + }, + Object {}, + ], ] } > @@ -1419,10 +2348,13 @@ exports[`Storyshots Markdown list Markdown 1`] = ` Object { "overflow": "hidden", }, - Object { - "height": 20, - "width": 20, - }, + Array [ + Object { + "height": 20, + "width": 20, + }, + Object {}, + ], ] } > @@ -1499,6 +2431,7 @@ exports[`Storyshots Markdown list Markdown 1`] = ` "fontSize": 30, "fontWeight": "400", }, + Object {}, ] } > @@ -1541,10 +2474,13 @@ exports[`Storyshots Markdown list Markdown 1`] = ` Object { "overflow": "hidden", }, - Object { - "height": 30, - "width": 30, - }, + Array [ + Object { + "height": 30, + "width": 30, + }, + Object {}, + ], ] } > diff --git a/app/containers/Avatar/Avatar.js b/app/containers/Avatar/Avatar.js index 1075524ab53..387687796d1 100644 --- a/app/containers/Avatar/Avatar.js +++ b/app/containers/Avatar/Avatar.js @@ -25,7 +25,7 @@ const Avatar = React.memo(({ avatarETag, isStatic }) => { - if ((!text && !avatar) || !server) { + if ((!text && !avatar && !emoji) || !server) { return null; } @@ -44,6 +44,7 @@ const Avatar = React.memo(({ getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} + style={avatarStyle} /> ); } else { diff --git a/app/containers/markdown/Emoji.js b/app/containers/markdown/Emoji.js index f0dcc2359ef..800db1735db 100644 --- a/app/containers/markdown/Emoji.js +++ b/app/containers/markdown/Emoji.js @@ -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 ( ); @@ -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 }; diff --git a/storybook/stories/Avatar.js b/storybook/stories/Avatar.js new file mode 100644 index 00000000000..3a7b195a83b --- /dev/null +++ b/storybook/stories/Avatar.js @@ -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 }) => ; +Separator.propTypes = { + title: PropTypes.string, + theme: PropTypes.string +}; + +const AvatarStories = ({ theme }) => ( + + + + + + + + + + + + + ({ name: 'troll', extension: 'jpg' })} + server={server} + size={56} + /> + + + + + + console.log('Pressed!')} + size={56} + /> + + + + + + + + + + + + + +); +AvatarStories.propTypes = { + theme: PropTypes.string +}; +export default AvatarStories; diff --git a/storybook/stories/index.js b/storybook/stories/index.js index cc5a4068f4c..8848009d8e8 100644 --- a/storybook/stories/index.js +++ b/storybook/stories/index.js @@ -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', () => ); storiesOf('Markdown', module) .add('list Markdown', () => ); +storiesOf('Avatar', module) + .add('list Avatar', () => ); // FIXME: I couldn't make these pass on jest :( // storiesOf('RoomViewHeader', module) From f72d272d6942a3466f9e1015a8171ece65668129 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Wed, 5 Aug 2020 16:22:53 -0300 Subject: [PATCH 30/34] Fix pick an image from gallery on ProfileView --- app/views/ProfileView/index.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/views/ProfileView/index.js b/app/views/ProfileView/index.js index dd5b0d573aa..d74ffa259b0 100644 --- a/app/views/ProfileView/index.js +++ b/app/views/ProfileView/index.js @@ -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'; @@ -81,16 +81,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; From 5471d1723a980dd906f7821ab212023afba335a1 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Tue, 29 Sep 2020 09:56:10 -0300 Subject: [PATCH 31/34] get avatar etag on select users of create discussion --- app/views/CreateDiscussionView/SelectUsers.js | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/app/views/CreateDiscussionView/SelectUsers.js b/app/views/CreateDiscussionView/SelectUsers.js index 041894fee81..b9c8ecf9f27 100644 --- a/app/views/CreateDiscussionView/SelectUsers.js +++ b/app/views/CreateDiscussionView/SelectUsers.js @@ -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', user: { id: userId, token }, 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') }...` }} From 4eb7cb21b7d7af81cdeb5234c6443a89667085e8 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Tue, 29 Sep 2020 10:04:35 -0300 Subject: [PATCH 32/34] invalidate ci cache --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 55b295c7f35..b06c782fa8a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index 7ed648fe5a0..b7a512aa2b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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= From 2ffc2692080c090a72d9656dc00f6ccaba7c256e Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 28 Oct 2020 16:04:04 -0300 Subject: [PATCH 33/34] Fix migration --- app/lib/database/model/migrations.js | 35 +++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/app/lib/database/model/migrations.js b/app/lib/database/model/migrations.js index ca3233aa4b8..d7fba898a0d 100644 --- a/app/lib/database/model/migrations.js +++ b/app/lib/database/model/migrations.js @@ -133,13 +133,36 @@ export default schemaMigrations({ { toVersion: 10, steps: [ - createTable({ - name: 'users', + addColumns({ + table: 'subscriptions', 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 } + { name: 'e2e_key', type: 'string', isOptional: true }, + { name: 'encrypted', type: 'boolean', isOptional: true }, + { name: 'e2e_key_id', type: 'string', isOptional: true } + ] + }), + addColumns({ + table: 'messages', + columns: [ + { name: 'e2e', type: 'string', isOptional: true } + ] + }), + addColumns({ + table: 'thread_messages', + columns: [ + { name: 'e2e', type: 'string', isOptional: true } + ] + }), + addColumns({ + table: 'threads', + columns: [ + { name: 'e2e', type: 'string', isOptional: true } + ] + }), + addColumns({ + table: 'rooms', + columns: [ + { name: 'e2e_key_id', type: 'string', isOptional: true } ] }) ] From 53794d6c92b6d16fcea3dfa24dd366b39376382c Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 28 Oct 2020 16:10:46 -0300 Subject: [PATCH 34/34] Fix sidebar avatar not updating --- app/views/SidebarView/index.js | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/app/views/SidebarView/index.js b/app/views/SidebarView/index.js index b629234697e..3caf06da060 100644 --- a/app/views/SidebarView/index.js +++ b/app/views/SidebarView/index.js @@ -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;