diff --git a/app/components/autocomplete/autocomplete.js b/app/components/autocomplete/autocomplete.js index aeda11ad7..0936ee97e 100644 --- a/app/components/autocomplete/autocomplete.js +++ b/app/components/autocomplete/autocomplete.js @@ -1,4 +1,4 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import React, {PureComponent} from 'react'; @@ -70,11 +70,11 @@ export default class Autocomplete extends PureComponent { keyboardDidShow = (e) => { const {height} = e.endCoordinates; this.setState({keyboardOffset: height}); - } + }; keyboardDidHide = () => { this.setState({keyboardOffset: 0}); - } + }; listHeight() { let offset = Platform.select({ios: 65, android: 75}); @@ -85,6 +85,10 @@ export default class Autocomplete extends PureComponent { } render() { + if (!this.props.value) { + return null; + } + const style = getStyleFromTheme(this.props.theme); const wrapperStyle = []; diff --git a/app/components/channel_drawer/channel_drawer.js b/app/components/channel_drawer/channel_drawer.js index 3abc18a4e..e1b3a79dd 100644 --- a/app/components/channel_drawer/channel_drawer.js +++ b/app/components/channel_drawer/channel_drawer.js @@ -18,7 +18,6 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter'; import Drawer from 'app/components/drawer'; import SafeAreaView from 'app/components/safe_area_view'; import {ViewTypes} from 'app/constants'; -import {alertErrorWithFallback} from 'app/utils/general'; import tracker from 'app/utils/time_tracker'; import ChannelsList from './channels_list'; @@ -69,6 +68,7 @@ export default class ChannelDrawer extends Component { openDrawerOffset = DRAWER_LANDSCAPE_OFFSET; } this.state = { + show: false, openDrawerOffset, }; } @@ -79,6 +79,7 @@ export default class ChannelDrawer extends Component { componentDidMount() { EventEmitter.on('close_channel_drawer', this.closeChannelDrawer); + EventEmitter.on('renderDrawer', this.handleShowDrawerContent); EventEmitter.on(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle); BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack); } @@ -100,7 +101,7 @@ export default class ChannelDrawer extends Component { const {currentTeamId, isLandscape, teamsCount} = this.props; const {openDrawerOffset} = this.state; - if (nextState.openDrawerOffset !== openDrawerOffset) { + if (nextState.openDrawerOffset !== openDrawerOffset || nextState.show !== this.state.show) { return true; } @@ -112,6 +113,7 @@ export default class ChannelDrawer extends Component { componentWillUnmount() { EventEmitter.off('close_channel_drawer', this.closeChannelDrawer); EventEmitter.off(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle); + EventEmitter.off('renderDrawer', this.handleShowDrawerContent); BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack); } @@ -124,6 +126,10 @@ export default class ChannelDrawer extends Component { return false; }; + handleShowDrawerContent = () => { + this.setState({show: true}); + }; + closeChannelDrawer = () => { if (this.refs.drawer && this.refs.drawer.isOpened()) { this.refs.drawer.close(); @@ -246,6 +252,7 @@ export default class ChannelDrawer extends Component { } = actions; const displayValue = {displayName: channel.display_name}; + const utils = require('app/utils/general'); let result; if (channel.type === General.DM_CHANNEL) { @@ -256,7 +263,7 @@ export default class ChannelDrawer extends Component { id: 'mobile.open_dm.error', defaultMessage: "We couldn't open a direct message with {displayName}. Please check your connection and try again.", }; - alertErrorWithFallback(intl, result.error, dmFailedMessage, displayValue); + utils.alertErrorWithFallback(intl, result.error, dmFailedMessage, displayValue); } } else { result = await joinChannel(currentUserId, currentTeamId, channel.id); @@ -266,7 +273,7 @@ export default class ChannelDrawer extends Component { id: 'mobile.join_channel.error', defaultMessage: "We couldn't join the channel {displayName}. Please check your connection and try again.", }; - alertErrorWithFallback(intl, result.error, joinFailedMessage, displayValue); + utils.alertErrorWithFallback(intl, result.error, joinFailedMessage, displayValue); } } @@ -324,9 +331,14 @@ export default class ChannelDrawer extends Component { } = this.props; const { + show, openDrawerOffset, } = this.state; + if (!show) { + return null; + } + const multipleTeams = teamsCount > 1; const showTeams = openDrawerOffset !== 0 && multipleTeams; if (this.drawerSwiper) { @@ -403,7 +415,7 @@ export default class ChannelDrawer extends Component { onClose={this.handleDrawerClose} onCloseStart={this.handleDrawerCloseStart} captureGestures='open' - type='static' + type={Platform.OS === 'ios' ? 'static' : 'displace'} acceptTap={true} acceptPanOnDrawer={false} disabled={false} diff --git a/app/components/channel_drawer/channels_list/channels_list.js b/app/components/channel_drawer/channels_list/channels_list.js index 1dfdc8cfa..4d34082fb 100644 --- a/app/components/channel_drawer/channels_list/channels_list.js +++ b/app/components/channel_drawer/channels_list/channels_list.js @@ -1,28 +1,27 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import React from 'react'; +import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { Platform, View, } from 'react-native'; -import {injectIntl, intlShape} from 'react-intl'; +import {intlShape} from 'react-intl'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import SearchBar from 'app/components/search_bar'; import {ViewTypes} from 'app/constants'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; -import FilteredList from './filtered_list'; import List from './list'; import SwitchTeamsButton from './switch_teams_button'; const {ANDROID_TOP_PORTRAIT} = ViewTypes; +let FilteredList = null; -class ChannelsList extends React.PureComponent { +export default class ChannelsList extends PureComponent { static propTypes = { - intl: intlShape.isRequired, navigator: PropTypes.object, onJoinChannel: PropTypes.func.isRequired, onSearchEnds: PropTypes.func.isRequired, @@ -32,6 +31,10 @@ class ChannelsList extends React.PureComponent { theme: PropTypes.object.isRequired, }; + static contextTypes = { + intl: intlShape.isRequired, + }; + constructor(props) { super(props); @@ -62,6 +65,9 @@ class ChannelsList extends React.PureComponent { }; onSearchFocused = () => { + if (!FilteredList) { + FilteredList = require('./filtered_list').default; + } this.setState({searching: true}); this.props.onSearchStart(); }; @@ -73,8 +79,8 @@ class ChannelsList extends React.PureComponent { }; render() { + const {intl} = this.context; const { - intl, navigator, onShowTeams, theme, @@ -243,5 +249,3 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }, }; }); - -export default injectIntl(ChannelsList); diff --git a/app/components/channel_drawer/channels_list/list/list.js b/app/components/channel_drawer/channels_list/list/list.js index 887a56285..d44694c5f 100644 --- a/app/components/channel_drawer/channels_list/list/list.js +++ b/app/components/channel_drawer/channels_list/list/list.js @@ -1,4 +1,4 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import React, {PureComponent} from 'react'; @@ -17,7 +17,6 @@ import {General} from 'mattermost-redux/constants'; import {debounce} from 'mattermost-redux/actions/helpers'; import ChannelItem from 'app/components/channel_drawer/channels_list/channel_item'; -import UnreadIndicator from 'app/components/channel_drawer/channels_list/unread_indicator'; import {ListTypes} from 'app/constants'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity} from 'app/utils/theme'; @@ -27,6 +26,8 @@ const VIEWABILITY_CONFIG = { waitForInteraction: true, }; +let UnreadIndicator = null; + export default class List extends PureComponent { static propTypes = { canCreatePrivateChannels: PropTypes.bool.isRequired, @@ -311,6 +312,9 @@ export default class List extends PureComponent { }; emitUnreadIndicatorChange = debounce((showIndicator) => { + if (showIndicator && !UnreadIndicator) { + UnreadIndicator = require('app/components/channel_drawer/channels_list/unread_indicator').default; + } this.setState({showIndicator}); }, 100); @@ -349,12 +353,14 @@ export default class List extends PureComponent { stickySectionHeadersEnabled={false} viewabilityConfig={VIEWABILITY_CONFIG} /> + {showIndicator && + } ); } diff --git a/app/components/channel_drawer/index.js b/app/components/channel_drawer/index.js index e0df094d8..803fc94bd 100644 --- a/app/components/channel_drawer/index.js +++ b/app/components/channel_drawer/index.js @@ -1,4 +1,4 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import {bindActionCreators} from 'redux'; @@ -6,12 +6,12 @@ import {connect} from 'react-redux'; import {joinChannel, markChannelAsRead, markChannelAsViewed} from 'mattermost-redux/actions/channels'; import {getTeams} from 'mattermost-redux/actions/teams'; +import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentTeamId, getMyTeamsCount} from 'mattermost-redux/selectors/entities/teams'; import {handleSelectChannel, setChannelDisplayName, setChannelLoading} from 'app/actions/views/channel'; import {makeDirectChannel} from 'app/actions/views/more_dms'; import {isLandscape, isTablet} from 'app/selectors/device'; -import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import ChannelDrawer from './channel_drawer.js'; diff --git a/app/components/channel_drawer/teams_list/teams_list.js b/app/components/channel_drawer/teams_list/teams_list.js index 7a91e099a..0b705d79b 100644 --- a/app/components/channel_drawer/teams_list/teams_list.js +++ b/app/components/channel_drawer/teams_list/teams_list.js @@ -1,4 +1,4 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import React, {PureComponent} from 'react'; @@ -11,7 +11,7 @@ import { TouchableHighlight, View, } from 'react-native'; -import {injectIntl, intlShape} from 'react-intl'; +import {intlShape} from 'react-intl'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import FormattedText from 'app/components/formatted_text'; @@ -28,7 +28,7 @@ const VIEWABILITY_CONFIG = { waitForInteraction: true, }; -class TeamsList extends PureComponent { +export default class TeamsList extends PureComponent { static propTypes = { actions: PropTypes.shape({ handleTeamChange: PropTypes.func.isRequired, @@ -36,12 +36,15 @@ class TeamsList extends PureComponent { closeChannelDrawer: PropTypes.func.isRequired, currentTeamId: PropTypes.string.isRequired, currentUrl: PropTypes.string.isRequired, - intl: intlShape.isRequired, navigator: PropTypes.object.isRequired, teamIds: PropTypes.array.isRequired, theme: PropTypes.object.isRequired, }; + static contextTypes = { + intl: intlShape.isRequired, + }; + constructor(props) { super(props); @@ -64,7 +67,8 @@ class TeamsList extends PureComponent { }; goToSelectTeam = preventDoubleTap(() => { - const {currentUrl, intl, navigator, theme} = this.props; + const {intl} = this.context; + const {currentUrl, navigator, theme} = this.props; navigator.showModal({ screen: 'SelectTeam', @@ -195,5 +199,3 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }, }; }); - -export default injectIntl(TeamsList); diff --git a/app/components/channel_icon.js b/app/components/channel_icon.js index 244c61bc1..bb2fd2959 100644 --- a/app/components/channel_icon.js +++ b/app/components/channel_icon.js @@ -1,22 +1,15 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// 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 { + Image, Text, View, } from 'react-native'; import Icon from 'react-native-vector-icons/FontAwesome'; -import { - ArchiveIcon, - AwayAvatar, - DndAvatar, - OfflineAvatar, - OnlineAvatar, -} from 'app/components/status_icons'; - import {General} from 'mattermost-redux/constants'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; @@ -108,47 +101,42 @@ export default class ChannelIcon extends React.PureComponent { ); } else if (type === General.DM_CHANNEL && teammateDeletedAt) { icon = ( - ); } 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; diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel.js index 7f70194ef..557e290fd 100644 --- a/app/screens/channel/channel.js +++ b/app/screens/channel/channel.js @@ -81,6 +81,8 @@ export default class Channel extends PureComponent { if (tracker.initialLoad) { this.props.actions.recordLoadTime('Start time', 'initialLoad'); } + + EventEmitter.emit('renderDrawer'); } componentWillReceiveProps(nextProps) { @@ -99,10 +101,15 @@ export default class Channel extends PureComponent { } } - componentDidUpdate() { + componentDidUpdate(prevProps) { if (tracker.teamSwitch) { this.props.actions.recordLoadTime('Switch Team', 'teamSwitch'); } + + // When the team changes emit the event to render the drawer content + if (this.props.currentChannelId && !prevProps.currentChannelId) { + EventEmitter.emit('renderDrawer'); + } } componentWillUnmount() { diff --git a/assets/base/images/status/archive_avatar.png b/assets/base/images/status/archive_avatar.png new file mode 100644 index 000000000..f0d16cf23 Binary files /dev/null and b/assets/base/images/status/archive_avatar.png differ diff --git a/assets/base/images/status/away_avatar.png b/assets/base/images/status/away_avatar.png new file mode 100644 index 000000000..42c5ddd69 Binary files /dev/null and b/assets/base/images/status/away_avatar.png differ diff --git a/assets/base/images/status/dnd_avatar.png b/assets/base/images/status/dnd_avatar.png new file mode 100644 index 000000000..c23f47b20 Binary files /dev/null and b/assets/base/images/status/dnd_avatar.png differ diff --git a/assets/base/images/status/offline_avatar.png b/assets/base/images/status/offline_avatar.png new file mode 100644 index 000000000..2c4cfe5ea Binary files /dev/null and b/assets/base/images/status/offline_avatar.png differ diff --git a/assets/base/images/status/online_avatar.png b/assets/base/images/status/online_avatar.png new file mode 100644 index 000000000..e58cc0915 Binary files /dev/null and b/assets/base/images/status/online_avatar.png differ