Chore: Migrate containers/Avatar to hooks (#4139)

* Migrate containers/Avatar to hooks

* Migrate to `useSelector` hook and `useRef`

* Change user object prop to primitive type

* fix re-render and update snapshot

* fix lint

* update pods

Co-authored-by: GleidsonDaniel <gleidson10daniel@hotmail.com>
This commit is contained in:
Danish Ahmed Mirza 2022-06-06 21:03:36 +05:30 committed by GitHub
parent 614d9afe65
commit 748e87acf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 103 additions and 114 deletions

View File

@ -8,7 +8,6 @@ import { getAvatarURL } from '../../lib/methods/helpers/getAvatarUrl';
import { SubscriptionType } from '../../definitions';
import Emoji from '../markdown/Emoji';
import { IAvatar } from './interfaces';
import { useTheme } from '../../theme';
const Avatar = React.memo(
({
@ -16,7 +15,8 @@ const Avatar = React.memo(
style,
avatar,
children,
user,
userId,
token,
onPress,
emoji,
getCustomEmoji,
@ -31,8 +31,6 @@ const Avatar = React.memo(
type = SubscriptionType.DIRECT,
externalProviderUrl
}: IAvatar) => {
const { theme } = useTheme();
if ((!text && !avatar && !emoji && !rid) || !server) {
return null;
}
@ -46,14 +44,7 @@ const Avatar = React.memo(
let image;
if (emoji) {
image = (
<Emoji
theme={theme}
baseUrl={server}
getCustomEmoji={getCustomEmoji}
isMessageContainsOnlyEmoji
literal={emoji}
style={avatarStyle}
/>
<Emoji baseUrl={server} getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />
);
} else {
let uri = avatar;
@ -62,7 +53,8 @@ const Avatar = React.memo(
type,
text,
size,
user,
userId,
token,
avatar,
server,
avatarETag,

View File

@ -1,84 +1,65 @@
import React from 'react';
import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb';
import React, { useEffect, useRef, useState } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { Observable, Subscription } from 'rxjs';
import { IApplicationState, TSubscriptionModel, TUserModel } from '../../definitions';
import database from '../../lib/database';
import { getUserSelector } from '../../selectors/login';
import { IApplicationState, TSubscriptionModel, TUserModel } from '../../definitions';
import Avatar from './Avatar';
import { IAvatar } from './interfaces';
class AvatarContainer extends React.Component<IAvatar, any> {
private subscription?: Subscription;
const AvatarContainer = ({
style,
text = '',
avatar,
emoji,
size,
borderRadius,
type,
children,
onPress,
getCustomEmoji,
isStatic,
rid
}: IAvatar): React.ReactElement => {
const subscription = useRef<Subscription>();
const [avatarETag, setAvatarETag] = useState<string | undefined>('');
static defaultProps = {
text: '',
type: 'd'
};
const isDirect = () => type === 'd';
constructor(props: IAvatar) {
super(props);
this.state = { avatarETag: '' };
this.init();
}
const server = useSelector((state: IApplicationState) => state.share.server.server || state.server.server);
const serverVersion = useSelector((state: IApplicationState) => state.share.server.version || state.server.version);
const { id, token } = useSelector(
(state: IApplicationState) => ({
id: getUserSelector(state).id,
token: getUserSelector(state).token
}),
shallowEqual
);
componentDidUpdate(prevProps: IAvatar) {
const { text, type } = this.props;
if (prevProps.text !== text || prevProps.type !== type) {
this.init();
}
}
const externalProviderUrl = useSelector(
(state: IApplicationState) => state.settings.Accounts_AvatarExternalProviderUrl as string
);
const blockUnauthenticatedAccess = useSelector(
(state: IApplicationState) =>
(state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess as boolean) ??
state.settings.Accounts_AvatarBlockUnauthenticatedAccess ??
true
);
shouldComponentUpdate(nextProps: IAvatar, nextState: { avatarETag: string }) {
const { avatarETag } = this.state;
const { text, type, size, externalProviderUrl } = this.props;
if (nextProps.externalProviderUrl !== externalProviderUrl) {
return true;
}
if (nextState.avatarETag !== avatarETag) {
return true;
}
if (nextProps.text !== text) {
return true;
}
if (nextProps.type !== type) {
return true;
}
if (nextProps.size !== size) {
return true;
}
return false;
}
componentWillUnmount() {
if (this.subscription?.unsubscribe) {
this.subscription.unsubscribe();
}
}
get isDirect() {
const { type } = this.props;
return type === 'd';
}
init = async () => {
const init = async () => {
const db = database.active;
const usersCollection = db.get('users');
const subsCollection = db.get('subscriptions');
let record;
try {
if (this.isDirect) {
const { text } = this.props;
if (isDirect()) {
const [user] = await usersCollection.query(Q.where('username', text)).fetch();
record = user;
} else {
const { rid } = this.props;
if (rid) {
record = await subsCollection.find(rid);
}
} else if (rid) {
record = await subsCollection.find(rid);
}
} catch {
// Record not found
@ -86,28 +67,46 @@ class AvatarContainer extends React.Component<IAvatar, any> {
if (record) {
const observable = record.observe() as Observable<TSubscriptionModel | TUserModel>;
this.subscription = observable.subscribe(r => {
const { avatarETag } = r;
this.setState({ avatarETag });
subscription.current = observable.subscribe(r => {
setAvatarETag(r.avatarETag);
});
}
};
render() {
const { avatarETag } = this.state;
const { serverVersion } = this.props;
return <Avatar {...this.props} avatarETag={avatarETag} serverVersion={serverVersion} />;
}
}
useEffect(() => {
if (!avatarETag) {
init();
}
return () => {
if (subscription?.current?.unsubscribe) {
subscription.current.unsubscribe();
}
};
}, [text, type, size, avatarETag, externalProviderUrl]);
const mapStateToProps = (state: IApplicationState) => ({
user: getUserSelector(state),
server: state.share.server.server || state.server.server,
serverVersion: state.share.server.version || state.server.version,
blockUnauthenticatedAccess:
(state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess as boolean) ??
state.settings.Accounts_AvatarBlockUnauthenticatedAccess ??
true,
externalProviderUrl: state.settings.Accounts_AvatarExternalProviderUrl as string
});
export default connect(mapStateToProps)(AvatarContainer);
return (
<Avatar
server={server}
style={style}
text={text}
avatar={avatar}
emoji={emoji}
size={size}
borderRadius={borderRadius}
type={type}
children={children}
userId={id}
token={token}
onPress={onPress}
getCustomEmoji={getCustomEmoji}
isStatic={isStatic}
rid={rid}
blockUnauthenticatedAccess={blockUnauthenticatedAccess}
externalProviderUrl={externalProviderUrl}
avatarETag={avatarETag}
serverVersion={serverVersion}
/>
);
};
export default AvatarContainer;

View File

@ -5,23 +5,21 @@ import { TGetCustomEmoji } from '../../definitions/IEmoji';
export interface IAvatar {
server?: string;
style?: any;
text: string;
text?: string;
avatar?: string;
emoji?: string;
size?: number;
borderRadius?: number;
type?: string;
children?: React.ReactElement | null;
user?: {
id?: string;
token?: string;
};
userId?: string;
token?: string;
onPress?: () => void;
getCustomEmoji?: TGetCustomEmoji;
avatarETag?: string;
isStatic?: boolean | string;
rid?: string;
blockUnauthenticatedAccess?: boolean;
serverVersion: string | null;
serverVersion?: string | null;
externalProviderUrl?: string;
}

View File

@ -3,9 +3,8 @@ import { Text } from 'react-native';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
import CustomEmoji from '../EmojiPicker/CustomEmoji';
import { themes } from '../../lib/constants';
import styles from './styles';
import { TSupportedThemes } from '../../theme';
import { useTheme } from '../../theme';
interface IEmoji {
literal: string;
@ -14,13 +13,13 @@ interface IEmoji {
baseUrl: string;
customEmojis?: any;
style?: object;
theme: TSupportedThemes;
onEmojiSelected?: Function;
tabEmojiStyle?: object;
}
const Emoji = React.memo(
({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis = true, style = {}, theme }: IEmoji) => {
({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis = true, style = {} }: IEmoji) => {
const { colors } = useTheme();
const emojiUnicode = shortnameToUnicode(literal);
const emoji: any = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, ''));
if (emoji && customEmojis) {
@ -33,7 +32,7 @@ const Emoji = React.memo(
);
}
return (
<Text style={[{ color: themes[theme].bodyText }, isMessageContainsOnlyEmoji ? styles.textBig : styles.text, style]}>
<Text style={[{ color: colors.bodyText }, isMessageContainsOnlyEmoji ? styles.textBig : styles.text, style]}>
{emojiUnicode}
</Text>
);

View File

@ -233,7 +233,7 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
};
renderEmoji = ({ literal }: TLiteral) => {
const { getCustomEmoji, baseUrl = '', customEmojis, style, theme } = this.props;
const { getCustomEmoji, baseUrl = '', customEmojis, style } = this.props;
return (
<MarkdownEmoji
literal={literal}
@ -242,7 +242,6 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
baseUrl={baseUrl}
customEmojis={customEmojis}
style={style}
theme={theme}
/>
);
};

View File

@ -6,9 +6,10 @@ const formatUrl = (url: string, size: number, query?: string) => `${url}?format=
export const getAvatarURL = ({
type,
text,
text = '',
size = 25,
user = {},
userId,
token,
avatar,
server,
avatarETag,
@ -30,10 +31,9 @@ export const getAvatarURL = ({
room = `@${text}`;
}
const { id, token } = user;
let query = '';
if (id && token && blockUnauthenticatedAccess) {
query += `&rc_token=${token}&rc_uid=${id}`;
if (userId && token && blockUnauthenticatedAccess) {
query += `&rc_token=${token}&rc_uid=${userId}`;
}
if (avatarETag) {
query += `&etag=${avatarETag}`;

View File

@ -36,7 +36,8 @@ const SelectChannel = ({
getAvatarURL({
text: getRoomAvatar(item),
type: item.t,
user: { id: userId, token },
userId,
token,
server,
avatarETag: item.avatarETag,
rid: item.rid,

View File

@ -40,7 +40,8 @@ const SelectUsers = ({
getAvatarURL({
text: getRoomAvatar(item),
type: SubscriptionType.DIRECT,
user: { id: userId, token },
userId,
token,
server,
avatarETag: item.avatarETag,
blockUnauthenticatedAccess,