* MM-34407 Use Avatars for Sidebar DMs
* Update app/components/channel_icon.js
Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
* UI feedback
* Align the private channel icon
Co-authored-by: Joseph Baylon <joseph.baylon@mattermost.com>
(cherry picked from commit 1b36529853)
Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
parent
562d3ac9ad
commit
a60d3eeb92
23 changed files with 610 additions and 624 deletions
|
|
@ -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 = (
|
||||
<CompassIcon
|
||||
name='archive-outline'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
|
||||
testID={`${testID}.archive`}
|
||||
/>
|
||||
);
|
||||
} else if (isBot) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='robot-happy'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, left: -1.5, top: -1}]}
|
||||
testID={`${testID}.bot`}
|
||||
/>
|
||||
);
|
||||
} else if (hasDraft) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='pencil-outline'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
|
||||
testID={`${testID}.draft`}
|
||||
/>
|
||||
);
|
||||
} else if (type === General.OPEN_CHANNEL) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='globe'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
|
||||
testID={`${testID}.public`}
|
||||
/>
|
||||
);
|
||||
} else if (type === General.PRIVATE_CHANNEL) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='lock-outline'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, left: 0.5}]}
|
||||
testID={`${testID}.private`}
|
||||
/>
|
||||
);
|
||||
} else if (type === General.GM_CHANNEL) {
|
||||
const fontSize = (size - 10);
|
||||
icon = (
|
||||
<View style={[style.groupBox, unreadGroupBox, activeGroupBox, {width: size, height: size}]}>
|
||||
<Text
|
||||
style={[style.group, unreadGroup, activeGroup, {fontSize}]}
|
||||
testID={`${testID}.gm_member_count`}
|
||||
>
|
||||
{membersCount}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else if (type === General.DM_CHANNEL) {
|
||||
switch (status) {
|
||||
case General.AWAY:
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='clock'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.awayIndicator}]}
|
||||
testID={`${testID}.away`}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case General.DND:
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='minus-circle'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.dndIndicator}]}
|
||||
testID={`${testID}.dnd`}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case General.ONLINE:
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='check-circle'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.onlineIndicator}]}
|
||||
testID={`${testID}.online`}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='circle-outline'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: offlineColor}]}
|
||||
testID={`${testID}.offline`}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[style.container, {height: size}]}>
|
||||
{icon}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
});
|
||||
184
app/components/channel_icon.tsx
Normal file
184
app/components/channel_icon.tsx
Normal file
|
|
@ -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<ViewStyle>;
|
||||
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 = (
|
||||
<CompassIcon
|
||||
name='archive-outline'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: props.size, left: 1}]}
|
||||
testID={`${props.testID}.archive`}
|
||||
/>
|
||||
);
|
||||
} else if (props.hasDraft) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='pencil-outline'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: props.size, left: 2}]}
|
||||
testID={`${props.testID}.draft`}
|
||||
/>
|
||||
);
|
||||
} else if (props.type === General.OPEN_CHANNEL) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='globe'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: props.size, left: 1}]}
|
||||
testID={`${props.testID}.public`}
|
||||
/>
|
||||
);
|
||||
} else if (props.type === General.PRIVATE_CHANNEL) {
|
||||
icon = (
|
||||
<CompassIcon
|
||||
name='lock-outline'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: props.size, left: 0.5}]}
|
||||
testID={`${props.testID}.private`}
|
||||
/>
|
||||
);
|
||||
} else if (props.type === General.GM_CHANNEL) {
|
||||
const fontSize = (props.size - 12);
|
||||
const boxSize = (props.size - 4);
|
||||
icon = (
|
||||
<View style={[style.groupBox, unreadGroupBox, activeGroupBox, {width: boxSize, height: boxSize}]}>
|
||||
<Text
|
||||
style={[style.group, unreadGroup, activeGroup, {fontSize}]}
|
||||
testID={`${props.testID}.gm_member_count`}
|
||||
>
|
||||
{props.membersCount}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else if (props.type === General.DM_CHANNEL) {
|
||||
// extraStyle = {marginRight: 6};
|
||||
icon = (
|
||||
<ProfilePicture
|
||||
size={props.size}
|
||||
statusSize={12}
|
||||
userId={props.userId}
|
||||
testID={props.testID}
|
||||
statusStyle={props.statusStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[style.container, extraStyle, {width: props.size, height: props.size}]}>
|
||||
{icon}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
ChannelIcon.defaultProps = {
|
||||
hasDraft: false,
|
||||
isActive: false,
|
||||
isArchived: false,
|
||||
isInfo: false,
|
||||
isUnread: false,
|
||||
membersCount: 0,
|
||||
size: 12,
|
||||
};
|
||||
|
||||
export default ChannelIcon;
|
||||
|
|
@ -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 = (
|
||||
<CompassIcon
|
||||
name='camera-outline'
|
||||
size={this.props.statusSize / 1.7}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
} else if (this.props.status && !edit) {
|
||||
statusIcon = (
|
||||
<UserStatus
|
||||
size={this.props.statusSize}
|
||||
status={this.props.status}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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 = (
|
||||
<FastImage
|
||||
key={pictureUrl}
|
||||
style={{width: this.props.size, height: this.props.size, borderRadius: this.props.size / 2}}
|
||||
source={source}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
containerStyle = {
|
||||
width: size + (STATUS_BUFFER - 1),
|
||||
height: size + (STATUS_BUFFER - 1),
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
};
|
||||
image = (
|
||||
<CompassIcon
|
||||
name='account-outline'
|
||||
size={this.props.iconSize || this.props.size}
|
||||
style={style.icon}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[style.container, containerStyle]}
|
||||
testID={`${testID}.${user?.id}`}
|
||||
>
|
||||
{image}
|
||||
{(showStatus || edit) && (user && !user.is_bot) &&
|
||||
<View style={[style.statusWrapper, statusStyle, {borderRadius: this.props.statusSize / 2}]}>
|
||||
{statusIcon}
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
});
|
||||
216
app/components/profile_picture/profile_picture.tsx
Normal file
216
app/components/profile_picture/profile_picture.tsx
Normal file
|
|
@ -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<ViewProps> | 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<string|undefined>();
|
||||
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<ViewStyle> = {
|
||||
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 = (
|
||||
<CompassIcon
|
||||
name='camera-outline'
|
||||
size={props.statusSize / 1.7}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
} else if (props.status && !props.edit) {
|
||||
statusIcon = (
|
||||
<UserStatus
|
||||
size={props.statusSize}
|
||||
status={props.status}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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 = (
|
||||
<FastImage
|
||||
key={pictureUrl}
|
||||
style={{width: props.size, height: props.size, borderRadius: props.size / 2}}
|
||||
source={source}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
containerStyle = {
|
||||
width: props.size + (buffer - 1),
|
||||
height: props.size + (buffer - 1),
|
||||
backgroundColor: changeOpacity(props.theme.centerChannelColor, 0.08),
|
||||
};
|
||||
image = (
|
||||
<CompassIcon
|
||||
name='account-outline'
|
||||
size={props.iconSize || props.size}
|
||||
style={style.icon}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[style.container, containerStyle]}
|
||||
testID={`${props.testID}.${props.user?.id}`}
|
||||
>
|
||||
{image}
|
||||
{(props.showStatus || props.edit) && (!props.user?.is_bot) &&
|
||||
<View style={[style.statusWrapper, statusStyle, {borderRadius: props.statusSize / 2}]}>
|
||||
{statusIcon}
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
ProfilePicture.defaultProps = {
|
||||
showStatus: true,
|
||||
size: 128,
|
||||
statusSize: 14,
|
||||
edit: false,
|
||||
};
|
||||
|
||||
export default ProfilePicture;
|
||||
|
|
@ -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
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<CompassIcon
|
||||
name={iconName}
|
||||
style={{fontSize: size, color: iconColor}}
|
||||
testID={`user_status.icon.${status}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
54
app/components/user_status/user_status.tsx
Normal file
54
app/components/user_status/user_status.tsx
Normal file
|
|
@ -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 (
|
||||
<CompassIcon
|
||||
name={iconName}
|
||||
style={{fontSize: size, color: iconColor}}
|
||||
testID={`user_status.icon.${status}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
UserStatus.defaultProps = {
|
||||
size: 6,
|
||||
status: General.OFFLINE,
|
||||
};
|
||||
|
||||
export default UserStatus;
|
||||
18
app/hooks/did_update.ts
Normal file
18
app/hooks/did_update.ts
Normal file
|
|
@ -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;
|
||||
4
app/hooks/index.ts
Normal file
4
app/hooks/index.ts
Normal file
|
|
@ -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';
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -27,14 +27,13 @@ exports[`channel_info_header should match snapshot 1`] = `
|
|||
}
|
||||
>
|
||||
<ChannelIcon
|
||||
hasDraft={false}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isBot={false}
|
||||
isInfo={true}
|
||||
isUnread={false}
|
||||
membersCount={3}
|
||||
size={24}
|
||||
status="status"
|
||||
testID="channel_info.header.channel_icon"
|
||||
theme={
|
||||
Object {
|
||||
|
|
@ -76,6 +75,7 @@ exports[`channel_info_header should match snapshot 1`] = `
|
|||
"flex": 1,
|
||||
"fontSize": 15,
|
||||
"fontWeight": "600",
|
||||
"marginLeft": 13,
|
||||
}
|
||||
}
|
||||
testID="channel_info.header.display_name"
|
||||
|
|
@ -334,14 +334,13 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
|
|||
}
|
||||
>
|
||||
<ChannelIcon
|
||||
hasDraft={false}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isBot={false}
|
||||
isInfo={true}
|
||||
isUnread={false}
|
||||
membersCount={3}
|
||||
size={24}
|
||||
status="status"
|
||||
testID="channel_info.header.channel_icon"
|
||||
theme={
|
||||
Object {
|
||||
|
|
@ -383,6 +382,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
|
|||
"flex": 1,
|
||||
"fontSize": 15,
|
||||
"fontWeight": "600",
|
||||
"marginLeft": 13,
|
||||
}
|
||||
}
|
||||
testID="channel_info.header.display_name"
|
||||
|
|
@ -669,14 +669,13 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
|
|||
}
|
||||
>
|
||||
<ChannelIcon
|
||||
hasDraft={false}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isBot={false}
|
||||
isInfo={true}
|
||||
isUnread={false}
|
||||
membersCount={3}
|
||||
size={24}
|
||||
status="status"
|
||||
testID="channel_info.header.channel_icon"
|
||||
theme={
|
||||
Object {
|
||||
|
|
@ -718,6 +717,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
|
|||
"flex": 1,
|
||||
"fontSize": 15,
|
||||
"fontWeight": "600",
|
||||
"marginLeft": 13,
|
||||
}
|
||||
}
|
||||
testID="channel_info.header.display_name"
|
||||
|
|
@ -976,14 +976,13 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
|
|||
}
|
||||
>
|
||||
<ChannelIcon
|
||||
hasDraft={false}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isBot={false}
|
||||
isInfo={true}
|
||||
isUnread={false}
|
||||
membersCount={3}
|
||||
size={24}
|
||||
status="status"
|
||||
testID="channel_info.header.channel_icon"
|
||||
theme={
|
||||
Object {
|
||||
|
|
@ -1025,6 +1024,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
|
|||
"flex": 1,
|
||||
"fontSize": 15,
|
||||
"fontWeight": "600",
|
||||
"marginLeft": 13,
|
||||
}
|
||||
}
|
||||
testID="channel_info.header.display_name"
|
||||
|
|
@ -1311,14 +1311,13 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
|
|||
}
|
||||
>
|
||||
<ChannelIcon
|
||||
hasDraft={false}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isBot={false}
|
||||
isInfo={true}
|
||||
isUnread={false}
|
||||
membersCount={3}
|
||||
size={24}
|
||||
status="status"
|
||||
testID="channel_info.header.channel_icon"
|
||||
theme={
|
||||
Object {
|
||||
|
|
@ -1360,6 +1359,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
|
|||
"flex": 1,
|
||||
"fontSize": 15,
|
||||
"fontWeight": "600",
|
||||
"marginLeft": 13,
|
||||
}
|
||||
}
|
||||
testID="channel_info.header.display_name"
|
||||
|
|
@ -1639,14 +1639,13 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
|
|||
}
|
||||
>
|
||||
<ChannelIcon
|
||||
hasDraft={false}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isBot={false}
|
||||
isInfo={true}
|
||||
isUnread={false}
|
||||
membersCount={3}
|
||||
size={24}
|
||||
status="status"
|
||||
testID="channel_info.header.channel_icon"
|
||||
theme={
|
||||
Object {
|
||||
|
|
@ -1688,6 +1687,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
|
|||
"flex": 1,
|
||||
"fontSize": 15,
|
||||
"fontWeight": "600",
|
||||
"marginLeft": 13,
|
||||
}
|
||||
}
|
||||
testID="channel_info.header.display_name"
|
||||
|
|
|
|||
|
|
@ -45,11 +45,9 @@ export default class ChannelInfo extends PureComponent {
|
|||
currentChannelGuestCount: PropTypes.number,
|
||||
currentChannelMemberCount: PropTypes.number,
|
||||
currentUserId: PropTypes.string,
|
||||
isBot: PropTypes.bool.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
isTeammateGuest: PropTypes.bool.isRequired,
|
||||
isDirectMessage: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string,
|
||||
teammateId: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
|
@ -172,9 +170,8 @@ export default class ChannelInfo extends PureComponent {
|
|||
currentChannelCreatorName,
|
||||
currentChannelMemberCount,
|
||||
currentChannelGuestCount,
|
||||
status,
|
||||
teammateId,
|
||||
theme,
|
||||
isBot,
|
||||
isTeammateGuest,
|
||||
} = this.props;
|
||||
|
||||
|
|
@ -201,11 +198,10 @@ export default class ChannelInfo extends PureComponent {
|
|||
memberCount={currentChannelMemberCount}
|
||||
onPermalinkPress={this.handlePermalinkPress}
|
||||
purpose={currentChannel.purpose}
|
||||
status={status}
|
||||
teammateId={teammateId}
|
||||
theme={theme}
|
||||
type={currentChannel.type}
|
||||
isArchived={channelIsArchived}
|
||||
isBot={isBot}
|
||||
isTeammateGuest={isTeammateGuest}
|
||||
hasGuests={currentChannelGuestCount > 0}
|
||||
isGroupConstrained={currentChannel.group_constrained}
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={style.container}>
|
||||
|
|
@ -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`}
|
||||
/>
|
||||
<Text
|
||||
|
|
@ -268,6 +265,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
color: theme.centerChannelColor,
|
||||
marginLeft: 13,
|
||||
},
|
||||
channelNameContainer: {
|
||||
flexDirection: 'row',
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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/*"],
|
||||
|
|
|
|||
Loading…
Reference in a new issue