From e72bb48d984843d74868c8a0cd9d84fc4dedd8db Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Thu, 24 May 2018 11:04:24 -0400 Subject: [PATCH] Cold-start channel drawer improvements (#1702) --- app/components/autocomplete/autocomplete.js | 10 ++-- .../channel_drawer/channel_drawer.js | 22 +++++++-- .../channels_list/channels_list.js | 22 +++++---- .../channel_drawer/channels_list/list/list.js | 10 +++- app/components/channel_drawer/index.js | 4 +- .../channel_drawer/teams_list/teams_list.js | 16 +++--- app/components/channel_icon.js | 46 +++++++----------- app/screens/channel/channel.js | 9 +++- assets/base/images/status/archive_avatar.png | Bin 0 -> 325 bytes assets/base/images/status/away_avatar.png | Bin 0 -> 675 bytes assets/base/images/status/dnd_avatar.png | Bin 0 -> 553 bytes assets/base/images/status/offline_avatar.png | Bin 0 -> 727 bytes assets/base/images/status/online_avatar.png | Bin 0 -> 664 bytes 13 files changed, 81 insertions(+), 58 deletions(-) create mode 100644 assets/base/images/status/archive_avatar.png create mode 100644 assets/base/images/status/away_avatar.png create mode 100644 assets/base/images/status/dnd_avatar.png create mode 100644 assets/base/images/status/offline_avatar.png create mode 100644 assets/base/images/status/online_avatar.png 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 0000000000000000000000000000000000000000..f0d16cf231a0a27242c3bc397c899005d9e8a433 GIT binary patch literal 325 zcmV-L0lNN)P))kr`F$j$>|4kR(WOqj)>ARz{911c)Vp-=@V z9)v>+NF>8jKi_)Sciuh!?xFX7@Ok%~-|u^VzkBXI z_ncREpqRBF>xK((6jtCP%tB4T!5(C;A7r`vxYX3rWB{6qYegefS za21qq9xg$$X_Q<`%M+z*zd&~DGN-8#sgS_J7B%eHk{^;6KY>X>ofi5`VGGE zbHwx%D(nS4-vHC!Q!&c!=)llcX^zRa-w@aK{MYnYCLk}$_n-cgM}PGRehK6oFf-_V z-vfQG`GQ%0nV9vi@VQLe?nBTT%Es%eL+CVEb_UO5_y*>Htlede2s@eyzXtu4QVvZn zc=wu_PN{a#&L+-+o`@FN7NpoU#5#<0^!`a1m}#|x>9zcq?EwJ%z8P8IzQ6zg002ov JPDHLkV1ftQJfHvo literal 0 HcmV?d00001 diff --git a/assets/base/images/status/dnd_avatar.png b/assets/base/images/status/dnd_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..c23f47b20bb1e86e6828acfc2a8c797e672bad66 GIT binary patch literal 553 zcmV+^0@nSBP)yL$fn>R zY=-yn7+%0kP)F4j_zr&z+$GilQ$?O?N6=&#jy8 ztJd*+*3LHu<6r;>^3h*6wG0}1Z`-m!F> z(D_b^`DuyMgwE1E7-zngrfE=1k9`TgK-&QB$R$~N$o$bsmsR)xO#{%cZ_RLxUb%5t rfPF>wa@7+Vf;z$e723h{T57^CDPqN))-JOL00000NkvXXu0mjfPC)jR literal 0 HcmV?d00001 diff --git a/assets/base/images/status/offline_avatar.png b/assets/base/images/status/offline_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..2c4cfe5ea10e0d5ed0f966b33a64f132051c83cb GIT binary patch literal 727 zcmV;|0x127P)mVi~YXK>P#Q zPDFX|3c*GM3kwSiQPecSCWQnGv9O9ZS{jm|F$6T{#GPflZ|>dqm4V}(ojG&n-urg< zt~F6{)&|;Z;05pqI0d$Vec->@11L`ezkpiy? z2)^cD9bPe)CD-_x^96EKOh?7pP=3Yk927#2m(Cr6TVHWUCBWC{5ulq`{%>%4R9)5F zCs0}jtfq_|0Np@Y##g9oj5PrlDlr0o0&owO6~z81UfC{Jxd^c7UI{9r;POr%$f$XY zJay+S-Zp}=z1Q%$`YdDPD}Z(10pA4J_;bP|HxH!N2TvNy$h$quxMJaRv?ky-7JjdY z=Nu6d_?*8Lf@d5=oDD_Loq(@#to4o^0WG$U+z?Q!3zYAhz)NuSR*Pn=YNJM6aFvqu zjXYwfbfNOC30OL@s`=**GQbxSrqie@|o zmdI0|2;#n*i#pa*5b;#|84z?+zMGDXcK~j^Wz6pa1@8fVfmo}wVqJ!^Z^hi|&|{cN zEfhI{H)+M4<7*rzJgziz0z8Y?0Bf~6Hk*jb@X4r&C7F!YQ@{zeYaW09_rMQ8H`M+K zay<3Ka&UA$mV`696X3@O53&MlLF5B)254dN`B|+kj|=8v9W;EI02{N%y;Zn;uCN~% z0Xh@e*s!tJs%mek%a&N002ov JPDHLkV1h$8KR^Hg literal 0 HcmV?d00001 diff --git a/assets/base/images/status/online_avatar.png b/assets/base/images/status/online_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..e58cc0915fbe3482b57e92c1b0723dbbae0fde66 GIT binary patch literal 664 zcmV;J0%!e+P)p$d#+Js2 zz%6PE41$)1P=vOYh6V>|aH_jln}Z-0LHT>+zH&PEyyyGw=O;e!+;h(R?|IL;-*@yj zy2@dQuxWS$o`y}h3ctW(MK{$S!9Dmd!ME5h!ec>$sw*%h9NK;mqZ6tf52+Xm<>nvj zR=-5S-3}U1b8}Ui(081|6vneDN5(dr@wqOqfl)WZcU<>fzQ=2z*H3XhJ+3F?@EZ7* z$?;rOa=N?*bjkenlpOm9$6nd|oXp&-7y34c?W?ThBVr)Blqnz$1hMhQ1 zTpBv%am(9eV#4wG9^YMf%7TMY_y14Wgd;FSk@%zqpJ$@o#=`MhPwt{6F`Br=HL;CA zp|rSy??Ee4G|nmkT!z|t9N)xt1uiFiiq)|n?wt7n=J|{Zd274ziguXFPl~I9ryVq) zof~pZ#0|LOD)t!03Eq0p)LUbjCbHtHGYUPy*nYDm91giA3dOC^i;_*uSVAxjCUNbpDEZu*hEL-R!;>M;0}z1ZT%fz1A0)6m$2%si4gbz0000