diff --git a/app/components/channel_icon.js b/app/components/channel_icon.js
deleted file mode 100644
index e7d26299c..000000000
--- a/app/components/channel_icon.js
+++ /dev/null
@@ -1,236 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {
- Text,
- View,
-} from 'react-native';
-
-import {General} from '@mm-redux/constants';
-
-import CompassIcon from '@components/compass_icon';
-import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
-
-export default class ChannelIcon extends React.PureComponent {
- static propTypes = {
- isActive: PropTypes.bool,
- isInfo: PropTypes.bool,
- isUnread: PropTypes.bool,
- hasDraft: PropTypes.bool,
- membersCount: PropTypes.number,
- size: PropTypes.number,
- status: PropTypes.string,
- theme: PropTypes.object.isRequired,
- type: PropTypes.string.isRequired,
- isArchived: PropTypes.bool.isRequired,
- isBot: PropTypes.bool.isRequired,
- testID: PropTypes.string,
- };
-
- static defaultProps = {
- isActive: false,
- isInfo: false,
- isUnread: false,
- size: 12,
- };
-
- render() {
- const {
- isActive,
- isUnread,
- isInfo,
- hasDraft,
- membersCount,
- size,
- status,
- theme,
- type,
- isArchived,
- isBot,
- testID,
- } = this.props;
- const style = getStyleSheet(theme);
-
- let activeIcon;
- let unreadIcon;
- let activeGroupBox;
- let unreadGroupBox;
- let activeGroup;
- let unreadGroup;
- let offlineColor = changeOpacity(theme.sidebarText, 0.5);
-
- if (isUnread) {
- unreadIcon = style.iconUnread;
- unreadGroupBox = style.groupBoxUnread;
- unreadGroup = style.groupUnread;
- }
-
- if (isActive) {
- activeIcon = style.iconActive;
- activeGroupBox = style.groupBoxActive;
- activeGroup = style.groupActive;
- }
-
- if (isInfo) {
- activeIcon = style.iconInfo;
- activeGroupBox = style.groupBoxInfo;
- activeGroup = style.groupInfo;
- offlineColor = changeOpacity(theme.centerChannelColor, 0.5);
- }
-
- let icon;
- if (isArchived) {
- icon = (
-
- );
- } else if (isBot) {
- icon = (
-
- );
- } else if (hasDraft) {
- icon = (
-
- );
- } else if (type === General.OPEN_CHANNEL) {
- icon = (
-
- );
- } else if (type === General.PRIVATE_CHANNEL) {
- icon = (
-
- );
- } else if (type === General.GM_CHANNEL) {
- const fontSize = (size - 10);
- icon = (
-
-
- {membersCount}
-
-
- );
- } else if (type === General.DM_CHANNEL) {
- switch (status) {
- case General.AWAY:
- icon = (
-
- );
- break;
- case General.DND:
- icon = (
-
- );
- break;
- case General.ONLINE:
- icon = (
-
- );
- break;
- default:
- icon = (
-
- );
- break;
- }
- }
-
- return (
-
- {icon}
-
- );
- }
-}
-
-const getStyleSheet = makeStyleSheetFromTheme((theme) => {
- return {
- container: {
- marginRight: 8,
- alignItems: 'center',
- },
- icon: {
- color: changeOpacity(theme.sidebarText, 0.4),
- },
- iconActive: {
- color: theme.sidebarTextActiveColor,
- },
- iconUnread: {
- color: theme.sidebarUnreadText,
- },
- iconInfo: {
- color: theme.centerChannelColor,
- },
- groupBox: {
- alignSelf: 'flex-start',
- alignItems: 'center',
- backgroundColor: changeOpacity(theme.sidebarText, 0.3),
- borderColor: changeOpacity(theme.sidebarText, 0.3),
- borderWidth: 1,
- borderRadius: 2,
- justifyContent: 'center',
- },
- groupBoxActive: {
- backgroundColor: changeOpacity(theme.sidebarTextActiveColor, 0.3),
- },
- groupBoxUnread: {
- backgroundColor: changeOpacity(theme.sidebarUnreadText, 0.3),
- },
- groupBoxInfo: {
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.3),
- },
- group: {
- color: changeOpacity(theme.sidebarText, 0.6),
- fontSize: 10,
- fontWeight: '600',
- },
- groupActive: {
- color: theme.sidebarTextActiveColor,
- },
- groupUnread: {
- color: theme.sidebarUnreadText,
- },
- groupInfo: {
- color: theme.centerChannelColor,
- },
- };
-});
diff --git a/app/components/channel_icon.tsx b/app/components/channel_icon.tsx
new file mode 100644
index 000000000..1a9b63379
--- /dev/null
+++ b/app/components/channel_icon.tsx
@@ -0,0 +1,184 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {StyleProp, Text, View, ViewStyle} from 'react-native';
+
+import CompassIcon from '@components/compass_icon';
+import ProfilePicture from '@components/profile_picture';
+import {General} from '@mm-redux/constants';
+import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
+
+import type {Theme} from '@mm-redux/types/preferences';
+
+type Props = {
+ hasDraft: boolean;
+ isActive: boolean;
+ isArchived: boolean;
+ isInfo: boolean;
+ isUnread: boolean;
+ membersCount: number;
+ size: number;
+ statusStyle?: StyleProp;
+ testID?: string;
+ theme: Theme;
+ type: string;
+ userId?: string;
+};
+
+const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
+ return {
+ container: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ icon: {
+ color: changeOpacity(theme.sidebarText, 0.4),
+ },
+ iconActive: {
+ color: theme.sidebarTextActiveColor,
+ },
+ iconUnread: {
+ color: theme.sidebarUnreadText,
+ },
+ iconInfo: {
+ color: theme.centerChannelColor,
+ },
+ groupBox: {
+ alignItems: 'center',
+ backgroundColor: changeOpacity(theme.sidebarText, 0.16),
+ borderRadius: 4,
+ justifyContent: 'center',
+ },
+ groupBoxActive: {
+ backgroundColor: changeOpacity(theme.sidebarTextActiveColor, 0.3),
+ },
+ groupBoxUnread: {
+ backgroundColor: changeOpacity(theme.sidebarUnreadText, 0.3),
+ },
+ groupBoxInfo: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.3),
+ },
+ group: {
+ color: theme.sidebarText,
+ fontSize: 10,
+ fontWeight: '600',
+ },
+ groupActive: {
+ color: theme.sidebarTextActiveColor,
+ },
+ groupUnread: {
+ color: theme.sidebarUnreadText,
+ },
+ groupInfo: {
+ color: theme.centerChannelColor,
+ },
+ };
+});
+
+const ChannelIcon = (props: Props) => {
+ const style = getStyleSheet(props.theme);
+
+ let activeIcon;
+ let unreadIcon;
+ let activeGroupBox;
+ let unreadGroupBox;
+ let activeGroup;
+ let unreadGroup;
+
+ if (props.isUnread) {
+ unreadIcon = style.iconUnread;
+ unreadGroupBox = style.groupBoxUnread;
+ unreadGroup = style.groupUnread;
+ }
+
+ if (props.isActive) {
+ activeIcon = style.iconActive;
+ activeGroupBox = style.groupBoxActive;
+ activeGroup = style.groupActive;
+ }
+
+ if (props.isInfo) {
+ activeIcon = style.iconInfo;
+ activeGroupBox = style.groupBoxInfo;
+ activeGroup = style.groupInfo;
+ }
+
+ let icon;
+ let extraStyle;
+ if (props.isArchived) {
+ icon = (
+
+ );
+ } else if (props.hasDraft) {
+ icon = (
+
+ );
+ } else if (props.type === General.OPEN_CHANNEL) {
+ icon = (
+
+ );
+ } else if (props.type === General.PRIVATE_CHANNEL) {
+ icon = (
+
+ );
+ } else if (props.type === General.GM_CHANNEL) {
+ const fontSize = (props.size - 12);
+ const boxSize = (props.size - 4);
+ icon = (
+
+
+ {props.membersCount}
+
+
+ );
+ } else if (props.type === General.DM_CHANNEL) {
+ // extraStyle = {marginRight: 6};
+ icon = (
+
+ );
+ }
+
+ return (
+
+ {icon}
+
+ );
+};
+
+ChannelIcon.defaultProps = {
+ hasDraft: false,
+ isActive: false,
+ isArchived: false,
+ isInfo: false,
+ isUnread: false,
+ membersCount: 0,
+ size: 12,
+};
+
+export default ChannelIcon;
diff --git a/app/components/profile_picture/profile_picture.js b/app/components/profile_picture/profile_picture.js
deleted file mode 100644
index 7fc2fe53b..000000000
--- a/app/components/profile_picture/profile_picture.js
+++ /dev/null
@@ -1,221 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import React, {PureComponent} from 'react';
-import PropTypes from 'prop-types';
-import {Platform, View} from 'react-native';
-import FastImage from 'react-native-fast-image';
-
-import {Client4} from '@client/rest';
-
-import CompassIcon from '@components/compass_icon';
-import UserStatus from '@components/user_status';
-import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
-
-const STATUS_BUFFER = Platform.select({
- ios: 3,
- android: 2,
-});
-
-export default class ProfilePicture extends PureComponent {
- static propTypes = {
- isCurrentUser: PropTypes.bool,
- size: PropTypes.number,
- statusSize: PropTypes.number,
- iconSize: PropTypes.number,
- user: PropTypes.object,
- userId: PropTypes.string,
- showStatus: PropTypes.bool,
- status: PropTypes.string,
- edit: PropTypes.bool,
- imageUri: PropTypes.string,
- profileImageUri: PropTypes.string,
- profileImageRemove: PropTypes.bool,
- theme: PropTypes.object,
- testID: PropTypes.string,
- actions: PropTypes.shape({
- getStatusForId: PropTypes.func.isRequired,
- setProfileImageUri: PropTypes.func.isRequired,
- }),
- };
-
- static defaultProps = {
- showStatus: true,
- size: 128,
- statusSize: 14,
- edit: false,
- };
-
- state = {
- pictureUrl: null,
- };
-
- componentDidMount() {
- const {actions, edit, imageUri, profileImageUri, user, status} = this.props;
- this.mounted = true;
-
- if (!status && user) {
- actions.getStatusForId(user.id);
- }
-
- if (profileImageUri) {
- this.setImageURL(profileImageUri);
- } else if (edit && imageUri) {
- this.setImageURL(imageUri);
- } else if (user) {
- const uri = Client4.getProfilePictureUrl(user.id, user.last_picture_update);
-
- this.setImageURL(uri);
- this.clearProfileImageUri();
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- setImageURL = (pictureUrl) => {
- if (this.mounted) {
- this.setState({pictureUrl});
- }
- };
-
- clearProfileImageUri = () => {
- if (this.props.isCurrentUser && this.props.profileImageUri !== '') {
- this.props.actions.setProfileImageUri('');
- }
- }
-
- componentDidUpdate(prevProps) {
- if (this.props.profileImageRemove !== prevProps.profileImageRemove) {
- this.setImageURL(null);
- } else if (this.mounted) {
- if (this.props.edit && this.props.imageUri && this.props.imageUri !== prevProps.imageUri) {
- this.setImageURL(this.props.imageUri);
- return;
- }
-
- if (this.props.profileImageUri !== '' && this.props.profileImageUri !== prevProps.profileImageUri) {
- this.setImageURL(this.props.profileImageUri);
- }
-
- const url = prevProps.user ? Client4.getProfilePictureUrl(prevProps.user.id, prevProps.user.last_picture_update) : null;
- const nextUrl = this.props.user ? Client4.getProfilePictureUrl(this.props.user.id, this.props.user.last_picture_update) : null;
-
- if (nextUrl && url !== nextUrl) {
- // empty function is so that promise unhandled is not triggered in dev mode
- this.setImageURL(nextUrl);
- this.clearProfileImageUri();
- }
- }
- }
-
- render() {
- const {edit, showStatus, theme, user, size, testID} = this.props;
- const {pictureUrl} = this.state;
- const style = getStyleSheet(theme);
-
- let statusIcon;
- let statusStyle;
- let containerStyle = {
- width: size + STATUS_BUFFER,
- height: size + STATUS_BUFFER,
- };
- if (edit) {
- const iconColor = changeOpacity(theme.centerChannelColor, 0.6);
- statusStyle = {
- width: this.props.statusSize,
- height: this.props.statusSize,
- backgroundColor: theme.centerChannelBg,
- };
- statusIcon = (
-
- );
- } else if (this.props.status && !edit) {
- statusIcon = (
-
- );
- }
-
- let source = null;
- let image;
- if (pictureUrl) {
- let prefix = '';
- if (Platform.OS === 'android' && !pictureUrl.startsWith('content://') &&
- !pictureUrl.startsWith('http://') && !pictureUrl.startsWith('https://')) {
- prefix = 'file://';
- }
-
- source = {
- uri: `${prefix}${pictureUrl}`,
- };
-
- image = (
-
- );
- } else {
- containerStyle = {
- width: size + (STATUS_BUFFER - 1),
- height: size + (STATUS_BUFFER - 1),
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
- };
- image = (
-
- );
- }
-
- return (
-
- {image}
- {(showStatus || edit) && (user && !user.is_bot) &&
-
- {statusIcon}
-
- }
-
- );
- }
-}
-
-const getStyleSheet = makeStyleSheetFromTheme((theme) => {
- return {
- container: {
- justifyContent: 'center',
- alignItems: 'center',
- borderRadius: 80,
- },
- icon: {
- color: changeOpacity(theme.centerChannelColor, 0.48),
- },
- statusWrapper: {
- position: 'absolute',
- bottom: 0,
- right: 0,
- overflow: 'hidden',
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: theme.centerChannelBg,
- },
- status: {
- color: theme.centerChannelBg,
- },
- };
-});
diff --git a/app/components/profile_picture/profile_picture.tsx b/app/components/profile_picture/profile_picture.tsx
new file mode 100644
index 000000000..a23af0cd3
--- /dev/null
+++ b/app/components/profile_picture/profile_picture.tsx
@@ -0,0 +1,216 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, {useEffect, useState} from 'react';
+import {Platform, StyleProp, View, ViewProps, ViewStyle} from 'react-native';
+import FastImage from 'react-native-fast-image';
+
+import {Client4} from '@client/rest';
+import CompassIcon from '@components/compass_icon';
+import UserStatus from '@components/user_status';
+import {useDidUpdate} from '@hooks';
+import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
+
+import type {Theme} from '@mm-redux/types/preferences';
+import type {UserProfile} from '@mm-redux/types/users';
+
+const STATUS_BUFFER = Platform.select({
+ ios: 3,
+ android: 2,
+});
+
+type ProfilePictureProps = {
+ actions: {
+ setProfileImageUri: (imageUri?: string) => void;
+ getStatusForId: (id: string) => void;
+ };
+ edit: boolean;
+ iconSize?: number;
+ imageUri?: string;
+ isCurrentUser: boolean;
+ profileImageRemove?: boolean;
+ profileImageUri?: string;
+ showStatus: boolean;
+ size: number;
+ status?: string;
+ statusSize: number;
+ statusStyle?: StyleProp | any;
+ user?: UserProfile;
+ userId: string;
+ theme: Theme;
+ testID?: string;
+};
+
+const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
+ return {
+ container: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ borderRadius: 80,
+ },
+ icon: {
+ color: changeOpacity(theme.centerChannelColor, 0.48),
+ },
+ statusWrapper: {
+ position: 'absolute',
+ bottom: 0,
+ right: 0,
+ overflow: 'hidden',
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: theme.centerChannelBg,
+ borderWidth: 1,
+ borderColor: theme.centerChannelBg,
+ },
+ status: {
+ color: theme.centerChannelBg,
+ },
+ };
+});
+
+const ProfilePicture = (props: ProfilePictureProps) => {
+ const [pictureUrl, setPictureUrl] = useState();
+ const style = getStyleSheet(props.theme);
+ const buffer = STATUS_BUFFER || 0;
+
+ const clearProfileImageUri = () => {
+ if (props.isCurrentUser && props.profileImageUri !== '') {
+ props.actions.setProfileImageUri('');
+ }
+ };
+
+ useEffect(() => {
+ const {edit, imageUri, profileImageUri, user, status} = props;
+ const {getStatusForId} = props.actions;
+
+ if (!status && user) {
+ getStatusForId(user.id);
+ }
+
+ if (profileImageUri) {
+ setPictureUrl(profileImageUri);
+ } else if (edit && imageUri) {
+ setPictureUrl(imageUri);
+ } else if (user) {
+ const uri = Client4.getProfilePictureUrl(user.id, user.last_picture_update);
+
+ setPictureUrl(uri);
+ clearProfileImageUri();
+ }
+ }, []);
+
+ useDidUpdate(() => {
+ setPictureUrl(undefined);
+ }, [props.profileImageRemove]);
+
+ useDidUpdate(() => {
+ const {edit, imageUri} = props;
+ if (edit && imageUri) {
+ setPictureUrl(imageUri);
+ }
+ }, [props.edit, props.imageUri]);
+
+ useDidUpdate(() => {
+ const {profileImageUri} = props;
+ if (profileImageUri) {
+ setPictureUrl(profileImageUri);
+ }
+ }, [props.profileImageUri]);
+
+ useDidUpdate(() => {
+ const {user} = props;
+
+ const url = user ? Client4.getProfilePictureUrl(user.id, user.last_picture_update) : undefined;
+ if (url !== pictureUrl) {
+ setPictureUrl(url);
+ }
+ }, [props.user]);
+
+ let statusIcon;
+ let statusStyle = props.statusStyle;
+ let containerStyle: StyleProp = {
+ width: props.size + buffer,
+ height: props.size + buffer,
+ };
+
+ if (props.edit) {
+ const iconColor = changeOpacity(props.theme.centerChannelColor, 0.6);
+ statusStyle = {
+ width: props.statusSize,
+ height: props.statusSize,
+ backgroundColor: props.theme.centerChannelBg,
+ };
+ statusIcon = (
+
+ );
+ } else if (props.status && !props.edit) {
+ statusIcon = (
+
+ );
+ }
+
+ let source = null;
+ let image;
+ if (pictureUrl) {
+ let prefix = '';
+ if (Platform.OS === 'android' && !pictureUrl.startsWith('content://') &&
+ !pictureUrl.startsWith('http://') && !pictureUrl.startsWith('https://')) {
+ prefix = 'file://';
+ }
+
+ source = {
+ uri: `${prefix}${pictureUrl}`,
+ };
+
+ image = (
+
+ );
+ } else {
+ containerStyle = {
+ width: props.size + (buffer - 1),
+ height: props.size + (buffer - 1),
+ backgroundColor: changeOpacity(props.theme.centerChannelColor, 0.08),
+ };
+ image = (
+
+ );
+ }
+
+ return (
+
+ {image}
+ {(props.showStatus || props.edit) && (!props.user?.is_bot) &&
+
+ {statusIcon}
+
+ }
+
+ );
+};
+
+ProfilePicture.defaultProps = {
+ showStatus: true,
+ size: 128,
+ statusSize: 14,
+ edit: false,
+};
+
+export default ProfilePicture;
diff --git a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap
index 83694769d..bcecf7e09 100644
--- a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap
+++ b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap
@@ -37,12 +37,16 @@ exports[`ChannelItem should match snapshot 1`] = `
hasDraft={false}
isActive={false}
isArchived={false}
- isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
- size={16}
- status="online"
+ size={24}
+ statusStyle={
+ Object {
+ "backgroundColor": "#145dbf",
+ "borderColor": "transparent",
+ }
+ }
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@@ -87,6 +91,7 @@ exports[`ChannelItem should match snapshot 1`] = `
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
+ "marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@@ -153,12 +158,16 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
hasDraft={false}
isActive={true}
isArchived={false}
- isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
- size={16}
- status="online"
+ size={24}
+ statusStyle={
+ Object {
+ "backgroundColor": "#145dbf",
+ "borderColor": "transparent",
+ }
+ }
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@@ -203,6 +212,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
+ "marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@@ -213,7 +223,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
}
testID="main.sidebar.channels_list.list.channel_item.display_name"
>
- {displayname} (you)
+ display_name
@@ -268,12 +278,16 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
hasDraft={false}
isActive={true}
isArchived={false}
- isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
- size={16}
- status="online"
+ size={24}
+ statusStyle={
+ Object {
+ "backgroundColor": "#145dbf",
+ "borderColor": "transparent",
+ }
+ }
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@@ -318,6 +332,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
+ "marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@@ -383,12 +398,16 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
hasDraft={false}
isActive={true}
isArchived={true}
- isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
- size={16}
- status="online"
+ size={24}
+ statusStyle={
+ Object {
+ "backgroundColor": "#145dbf",
+ "borderColor": "transparent",
+ }
+ }
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@@ -433,6 +452,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
+ "marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@@ -487,12 +507,16 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
hasDraft={false}
isActive={false}
isArchived={false}
- isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
- size={16}
- status="online"
+ size={24}
+ statusStyle={
+ Object {
+ "backgroundColor": "#145dbf",
+ "borderColor": "transparent",
+ }
+ }
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@@ -537,6 +561,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
+ "marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@@ -592,12 +617,16 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe
hasDraft={false}
isActive={false}
isArchived={false}
- isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
- size={16}
- status="online"
+ size={24}
+ statusStyle={
+ Object {
+ "backgroundColor": "#145dbf",
+ "borderColor": "transparent",
+ }
+ }
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@@ -642,6 +671,7 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
+ "marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@@ -697,12 +727,16 @@ exports[`ChannelItem should match snapshot for isManualUnread 1`] = `
hasDraft={false}
isActive={false}
isArchived={false}
- isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
- size={16}
- status="online"
+ size={24}
+ statusStyle={
+ Object {
+ "backgroundColor": "#145dbf",
+ "borderColor": "transparent",
+ }
+ }
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@@ -747,6 +781,7 @@ exports[`ChannelItem should match snapshot for isManualUnread 1`] = `
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
+ "marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@@ -806,12 +841,16 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
hasDraft={true}
isActive={false}
isArchived={false}
- isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
- size={16}
- status="online"
+ size={24}
+ statusStyle={
+ Object {
+ "backgroundColor": "#145dbf",
+ "borderColor": "transparent",
+ }
+ }
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@@ -856,6 +895,7 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
+ "marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
@@ -913,12 +953,16 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
hasDraft={false}
isActive={false}
isArchived={false}
- isBot={false}
isInfo={false}
isUnread={true}
membersCount={1}
- size={16}
- status="online"
+ size={24}
+ statusStyle={
+ Object {
+ "backgroundColor": "#145dbf",
+ "borderColor": "transparent",
+ }
+ }
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
theme={
Object {
@@ -963,6 +1007,7 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
"fontFamily": "Open Sans",
"fontSize": 16,
"lineHeight": 24,
+ "marginLeft": 13,
"maxWidth": "80%",
"paddingRight": 10,
},
diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.js
index 07a832129..93c5b1693 100644
--- a/app/components/sidebars/main/channels_list/channel_item/channel_item.js
+++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.js
@@ -10,11 +10,11 @@ import {
} from 'react-native';
import {intlShape} from 'react-intl';
+import Badge from '@components/badge';
+import ChannelIcon from '@components/channel_icon';
import {General} from '@mm-redux/constants';
-import Badge from 'app/components/badge';
-import ChannelIcon from 'app/components/channel_icon';
-import {preventDoubleTap} from 'app/utils/tap';
-import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+import {preventDoubleTap} from '@utils/tap';
+import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
export default class ChannelItem extends PureComponent {
static propTypes = {
@@ -33,10 +33,10 @@ export default class ChannelItem extends PureComponent {
onSelectChannel: PropTypes.func.isRequired,
shouldHideChannel: PropTypes.bool,
showUnreadForMsgs: PropTypes.bool.isRequired,
+ teammateId: PropTypes.string,
theme: PropTypes.object.isRequired,
unreadMsgs: PropTypes.number.isRequired,
isSearchResult: PropTypes.bool,
- isBot: PropTypes.bool.isRequired,
};
static defaultProps = {
@@ -77,7 +77,7 @@ export default class ChannelItem extends PureComponent {
theme,
isSearchResult,
channel,
- isBot,
+ teammateId,
} = this.props;
// Only ever show an archived channel if it's the currently viewed channel.
@@ -103,7 +103,7 @@ export default class ChannelItem extends PureComponent {
if (isSearchResult) {
isCurrenUser = channel.id === currentUserId;
} else {
- isCurrenUser = channel.teammate_id === currentUserId;
+ isCurrenUser = teammateId === currentUserId;
}
}
if (isCurrenUser) {
@@ -161,13 +161,13 @@ export default class ChannelItem extends PureComponent {
isUnread={isUnread}
hasDraft={hasDraft && channelId !== currentChannelId}
membersCount={displayName.split(',').length}
- size={16}
- status={channel.status}
+ statusStyle={{backgroundColor: theme.sidebarBg, borderColor: 'transparent'}}
+ size={24}
theme={theme}
type={channel.type}
isArchived={isArchived}
- isBot={isBot}
testID={`${testID}.channel_icon`}
+ userId={teammateId}
/>
);
@@ -231,6 +231,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
fontSize: 16,
lineHeight: 24,
paddingRight: 10,
+ marginLeft: 13,
maxWidth: '80%',
flex: 1,
alignSelf: 'center',
diff --git a/app/components/sidebars/main/channels_list/channel_item/index.js b/app/components/sidebars/main/channels_list/channel_item/index.js
index 40db734df..a470ab9f4 100644
--- a/app/components/sidebars/main/channels_list/channel_item/index.js
+++ b/app/components/sidebars/main/channels_list/channel_item/index.js
@@ -30,16 +30,14 @@ function makeMapStateToProps() {
const channelDraft = getDraftForChannel(state, channel.id);
let displayName = channel.display_name;
- let isBot = false;
let isGuest = false;
let isArchived = channel.delete_at > 0;
+ let teammateId;
if (channel.type === General.DM_CHANNEL) {
- const teammateId = getUserIdFromChannelName(currentUserId, channel.name);
+ teammateId = getUserIdFromChannelName(currentUserId, channel.name);
const teammate = getUser(state, teammateId);
- isBot = Boolean(ownProps.isSearchResult ? channel.isBot : teammate?.is_bot); //eslint-disable-line camelcase
-
if (teammate) {
const teammateNameDisplay = getTeammateNameDisplaySetting(state);
displayName = displayUsername(teammate, teammateNameDisplay, false);
@@ -78,13 +76,13 @@ function makeMapStateToProps() {
displayName,
hasDraft: Boolean(channelDraft.draft.trim() || channelDraft?.files?.length),
isArchived,
- isBot,
isChannelMuted: isChannelMuted(member),
isGuest,
isManualUnread: isManuallyUnread(state, ownProps.channelId),
mentions: member ? member.mention_count : 0,
shouldHideChannel,
showUnreadForMsgs,
+ teammateId,
theme: getTheme(state),
unreadMsgs,
};
diff --git a/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js b/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js
index 266a1bcc2..b884f4bf7 100644
--- a/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js
+++ b/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js
@@ -179,7 +179,7 @@ class FilteredList extends Component {
};
buildCurrentDMSForSearch = (props, term) => {
- const {channels, teammateNameDisplay, profiles, statuses, pastDirectMessages, groupChannelMemberDetails} = props;
+ const {channels, currentUserId, teammateNameDisplay, profiles, pastDirectMessages, groupChannelMemberDetails} = props;
const {favoriteChannels} = channels;
const favoriteDms = favoriteChannels.filter((c) => {
@@ -206,7 +206,6 @@ class FilteredList extends Component {
return {
id: u.id,
- status: statuses[u.id],
display_name: displayName,
username: u.username,
email: u.email,
@@ -215,10 +214,7 @@ class FilteredList extends Component {
nickname: u.nickname,
fullname: `${u.first_name} ${u.last_name}`,
delete_at: u.delete_at,
- isBot: u.is_bot,
-
- // need name key for DM's as we use it for sortChannelsByDisplayName with same display_name
- name: displayName,
+ name: `${currentUserId}__${u.id}`,
};
});
@@ -233,7 +229,7 @@ class FilteredList extends Component {
}
buildMembersForSearch = (props, term) => {
- const {channels, currentUserId, teammateNameDisplay, profiles, teamProfiles, statuses, pastDirectMessages, restrictDms} = props;
+ const {channels, currentUserId, teammateNameDisplay, profiles, teamProfiles, pastDirectMessages, restrictDms} = props;
const {favoriteChannels, unreadChannels} = channels;
const favoriteAndUnreadDms = [...favoriteChannels, ...unreadChannels].filter((c) => {
@@ -251,17 +247,15 @@ class FilteredList extends Component {
return {
id: u.id,
- status: statuses[u.id],
display_name: displayName,
username: u.username,
email: u.email,
- name: displayName,
+ name: `${currentUserId}__${u.id}`,
type: General.DM_CHANNEL,
fake: true,
nickname: u.nickname,
fullname: `${u.first_name} ${u.last_name}`,
delete_at: u.delete_at,
- isBot: u.is_bot,
};
});
diff --git a/app/components/sidebars/main/channels_list/list/__snapshots__/list.test.js.snap b/app/components/sidebars/main/channels_list/list/__snapshots__/list.test.js.snap
index 43150d13c..987271dcd 100644
--- a/app/components/sidebars/main/channels_list/list/__snapshots__/list.test.js.snap
+++ b/app/components/sidebars/main/channels_list/list/__snapshots__/list.test.js.snap
@@ -21,7 +21,7 @@ exports[`ChannelsList List should match snapshot 1`] = `
onEndReachedThreshold={2}
onScrollBeginDrag={[Function]}
onViewableItemsChanged={[Function]}
- removeClippedSubviews={true}
+ removeClippedSubviews={false}
renderItem={[Function]}
renderSectionHeader={[Function]}
scrollEventThrottle={50}
diff --git a/app/components/sidebars/main/channels_list/list/list.js b/app/components/sidebars/main/channels_list/list/list.js
index 3f1a6c277..ff34f1ff7 100644
--- a/app/components/sidebars/main/channels_list/list/list.js
+++ b/app/components/sidebars/main/channels_list/list/list.js
@@ -411,7 +411,7 @@ export default class List extends PureComponent {
ref={this.setListRef}
sections={sections}
contentContainerStyle={{paddingBottom}}
- removeClippedSubviews={true}
+ removeClippedSubviews={Platform.OS === 'android'}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
keyboardShouldPersistTaps={'always'}
diff --git a/app/components/user_status/__snapshots__/user_status.test.js.snap b/app/components/user_status/__snapshots__/user_status.test.js.snap
index 148cb501f..9a6c55a80 100644
--- a/app/components/user_status/__snapshots__/user_status.test.js.snap
+++ b/app/components/user_status/__snapshots__/user_status.test.js.snap
@@ -44,7 +44,7 @@ exports[`UserStatus should match snapshot, should default to offline status 1`]
name="circle-outline"
style={
Object {
- "color": "rgba(61,60,64,0.3)",
+ "color": "rgba(184,184,184,0.64)",
"fontSize": 32,
}
}
diff --git a/app/components/user_status/user_status.js b/app/components/user_status/user_status.js
deleted file mode 100644
index 9a88b3851..000000000
--- a/app/components/user_status/user_status.js
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import React, {PureComponent} from 'react';
-import PropTypes from 'prop-types';
-
-import {General} from '@mm-redux/constants';
-
-import CompassIcon from '@components/compass_icon';
-import {changeOpacity} from '@utils/theme';
-
-export default class UserStatus extends PureComponent {
- static propTypes = {
- isAvatar: PropTypes.bool,
- size: PropTypes.number,
- status: PropTypes.string,
- theme: PropTypes.object.isRequired,
- };
-
- static defaultProps = {
- size: 6,
- status: General.OFFLINE,
- };
-
- render() {
- const {size, status, theme} = this.props;
-
- let iconName;
- let iconColor;
- switch (status) {
- case General.AWAY:
- iconName = 'clock';
- iconColor = theme.awayIndicator;
- break;
- case General.DND:
- iconName = 'minus-circle';
- iconColor = theme.dndIndicator;
- break;
- case General.ONLINE:
- iconName = 'check-circle';
- iconColor = theme.onlineIndicator;
- break;
- default:
- iconName = 'circle-outline';
- iconColor = changeOpacity(theme.centerChannelColor, 0.3);
- break;
- }
-
- return (
-
- );
- }
-}
diff --git a/app/components/user_status/user_status.tsx b/app/components/user_status/user_status.tsx
new file mode 100644
index 000000000..6c44da65b
--- /dev/null
+++ b/app/components/user_status/user_status.tsx
@@ -0,0 +1,54 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+
+import CompassIcon from '@components/compass_icon';
+import {General} from '@mm-redux/constants';
+import {changeOpacity} from '@utils/theme';
+
+import type {Theme} from '@mm-redux/types/preferences';
+
+type UserStatusProps = {
+ size: number;
+ status: string;
+ theme: Theme;
+};
+
+const UserStatus = ({size, status, theme}: UserStatusProps) => {
+ let iconName;
+ let iconColor;
+ switch (status) {
+ case General.AWAY:
+ iconName = 'clock';
+ iconColor = theme.awayIndicator;
+ break;
+ case General.DND:
+ iconName = 'minus-circle';
+ iconColor = theme.dndIndicator;
+ break;
+ case General.ONLINE:
+ iconName = 'check-circle';
+ iconColor = theme.onlineIndicator;
+ break;
+ default:
+ iconName = 'circle-outline';
+ iconColor = changeOpacity('#B8B8B8', 0.64);
+ break;
+ }
+
+ return (
+
+ );
+};
+
+UserStatus.defaultProps = {
+ size: 6,
+ status: General.OFFLINE,
+};
+
+export default UserStatus;
diff --git a/app/hooks/did_update.ts b/app/hooks/did_update.ts
new file mode 100644
index 000000000..e65c88c9c
--- /dev/null
+++ b/app/hooks/did_update.ts
@@ -0,0 +1,18 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {useRef, useEffect, EffectCallback, DependencyList} from 'react';
+
+function useDidUpdate(callback: EffectCallback, deps?: DependencyList) {
+ const hasMount = useRef(false);
+
+ useEffect(() => {
+ if (hasMount.current) {
+ callback();
+ } else {
+ hasMount.current = true;
+ }
+ }, deps);
+}
+
+export default useDidUpdate;
diff --git a/app/hooks/index.ts b/app/hooks/index.ts
new file mode 100644
index 000000000..85799a212
--- /dev/null
+++ b/app/hooks/index.ts
@@ -0,0 +1,4 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+export {default as useDidUpdate} from './did_update';
diff --git a/app/screens/channel_info/__snapshots__/channel_info.test.js.snap b/app/screens/channel_info/__snapshots__/channel_info.test.js.snap
index 18e2a2c45..c7626e865 100644
--- a/app/screens/channel_info/__snapshots__/channel_info.test.js.snap
+++ b/app/screens/channel_info/__snapshots__/channel_info.test.js.snap
@@ -33,13 +33,11 @@ exports[`channelInfo should match snapshot 1`] = `
hasGuests={false}
header=""
isArchived={false}
- isBot={false}
isGroupConstrained={false}
isTeammateGuest={false}
memberCount={2}
onPermalinkPress={[Function]}
purpose="Purpose"
- status="status"
testID="channel_info.header"
theme={
Object {
diff --git a/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap b/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap
index 53ce71885..6d009a653 100644
--- a/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap
+++ b/app/screens/channel_info/__snapshots__/channel_info_header.test.js.snap
@@ -27,14 +27,13 @@ exports[`channel_info_header should match snapshot 1`] = `
}
>
0}
isGroupConstrained={currentChannel.group_constrained}
diff --git a/app/screens/channel_info/channel_info.test.js b/app/screens/channel_info/channel_info.test.js
index 1be121f75..914b0c2c8 100644
--- a/app/screens/channel_info/channel_info.test.js
+++ b/app/screens/channel_info/channel_info.test.js
@@ -44,9 +44,7 @@ describe('channelInfo', () => {
currentChannelMemberCount: 2,
currentChannelGuestCount: 0,
currentUserId: '1234',
- status: 'status',
theme: Preferences.THEMES.default,
- isBot: false,
isTeammateGuest: false,
isDirectMessage: false,
actions: {
diff --git a/app/screens/channel_info/channel_info_header.js b/app/screens/channel_info/channel_info_header.js
index 8072ee7e4..666d8403a 100644
--- a/app/screens/channel_info/channel_info_header.js
+++ b/app/screens/channel_info/channel_info_header.js
@@ -12,18 +12,17 @@ import {
import {intlShape} from 'react-intl';
import Clipboard from '@react-native-community/clipboard';
+import {popToRoot} from '@actions/navigation';
+import ChannelIcon from '@components/channel_icon';
+import FormattedDate from '@components/formatted_date';
+import FormattedText from '@components/formatted_text';
+import Markdown from '@components/markdown';
import {General} from '@mm-redux/constants';
-
-import ChannelIcon from 'app/components/channel_icon';
-import FormattedDate from 'app/components/formatted_date';
-import FormattedText from 'app/components/formatted_text';
-import Markdown from 'app/components/markdown';
+import BottomSheet from '@utils/bottom_sheet';
+import {t} from '@utils/i18n';
+import {getMarkdownTextStyles, getMarkdownBlockStyles} from '@utils/markdown';
+import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import mattermostManaged from 'app/mattermost_managed';
-import BottomSheet from 'app/utils/bottom_sheet';
-import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
-import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
-import {t} from 'app/utils/i18n';
-import {popToRoot} from 'app/actions/navigation';
export default class ChannelInfoHeader extends React.PureComponent {
static propTypes = {
@@ -34,11 +33,10 @@ export default class ChannelInfoHeader extends React.PureComponent {
header: PropTypes.string,
onPermalinkPress: PropTypes.func,
purpose: PropTypes.string,
- status: PropTypes.string,
+ teammateId: PropTypes.string,
theme: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
isArchived: PropTypes.bool.isRequired,
- isBot: PropTypes.bool.isRequired,
isTeammateGuest: PropTypes.bool.isRequired,
hasGuests: PropTypes.bool.isRequired,
isGroupConstrained: PropTypes.bool,
@@ -135,11 +133,10 @@ export default class ChannelInfoHeader extends React.PureComponent {
memberCount,
onPermalinkPress,
purpose,
- status,
+ teammateId,
theme,
type,
isArchived,
- isBot,
isGroupConstrained,
testID,
timeZone,
@@ -148,9 +145,10 @@ export default class ChannelInfoHeader extends React.PureComponent {
const style = getStyleSheet(theme);
const textStyles = getMarkdownTextStyles(theme);
const blockStyles = getMarkdownBlockStyles(theme);
- const baseTextStyle = Platform.OS === 'ios' ?
- {...style.detail, lineHeight: 20} :
- style.detail;
+ const baseTextStyle = Platform.select({
+ ios: {...style.detail, lineHeight: 20},
+ android: style.detail,
+ });
return (
@@ -159,11 +157,10 @@ export default class ChannelInfoHeader extends React.PureComponent {
isInfo={true}
membersCount={memberCount}
size={24}
- status={status}
+ userId={teammateId}
theme={theme}
type={type}
isArchived={isArchived}
- isBot={isBot}
testID={`${testID}.channel_icon`}
/>
{
fontSize: 15,
fontWeight: '600',
color: theme.centerChannelColor,
+ marginLeft: 13,
},
channelNameContainer: {
flexDirection: 'row',
diff --git a/app/screens/channel_info/index.js b/app/screens/channel_info/index.js
index d409ba915..3f54901ad 100644
--- a/app/screens/channel_info/index.js
+++ b/app/screens/channel_info/index.js
@@ -11,10 +11,9 @@ import {getCustomEmojisInText} from '@mm-redux/actions/emojis';
import {General} from '@mm-redux/constants';
import {getTeammateNameDisplaySetting, getTheme} from '@mm-redux/selectors/entities/preferences';
import {getCurrentChannel, getCurrentChannelStats} from '@mm-redux/selectors/entities/channels';
-import {getCurrentUserId, getUser, getStatusForUserId} from '@mm-redux/selectors/entities/users';
+import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
import {getUserIdFromChannelName} from '@mm-redux/utils/channel_utils';
import {displayUsername} from '@mm-redux/utils/user_utils';
-import {isLandscape} from '@selectors/device';
import {isGuest} from '@utils/users';
import ChannelInfo from './channel_info';
@@ -29,17 +28,13 @@ function mapStateToProps(state) {
let currentChannelGuestCount = (currentChannelStats && currentChannelStats.guest_count) || 0;
const currentUserId = getCurrentUserId(state);
- let status;
- let isBot = false;
+ let teammateId;
let isTeammateGuest = false;
const isDirectMessage = currentChannel.type === General.DM_CHANNEL;
+
if (isDirectMessage) {
- const teammateId = getUserIdFromChannelName(currentUserId, currentChannel.name);
+ teammateId = getUserIdFromChannelName(currentUserId, currentChannel.name);
const teammate = getUser(state, teammateId);
- status = getStatusForUserId(state, teammateId);
- if (teammate && teammate.is_bot) {
- isBot = true;
- }
if (isGuest(teammate)) {
isTeammateGuest = true;
currentChannelGuestCount = 1;
@@ -56,11 +51,9 @@ function mapStateToProps(state) {
currentChannelGuestCount,
currentChannelMemberCount,
currentUserId,
- isBot,
- isLandscape: isLandscape(state),
isTeammateGuest,
isDirectMessage,
- status,
+ teammateId,
theme: getTheme(state),
};
}
diff --git a/babel.config.js b/babel.config.js
index 57d768854..e8c1fe91a 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -17,6 +17,7 @@ module.exports = {
'@client': './app/client',
'@components': './app/components',
'@constants': './app/constants',
+ '@hooks': './app/hooks',
'@i18n': './app/i18n',
'@init': './app/init',
'@mm-redux': './app/mm-redux',
diff --git a/tsconfig.json b/tsconfig.json
index 29588fe49..76f44fc48 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -38,6 +38,8 @@
"@components/*": ["app/components/*"],
"@constants/*": ["app/constants/*"],
"@constants": ["app/constants/index"],
+ "@hooks": ["app/hooks/index"],
+ "@hooks/*": ["app/hooks/*"],
"@i18n": ["app/i18n/index"],
"@init/*": ["app/init/*"],
"@mm-redux/*": ["app/mm-redux/*"],