diff --git a/Makefile b/Makefile index 5e12971f5..3c02fee6a 100644 --- a/Makefile +++ b/Makefile @@ -122,6 +122,7 @@ post-install: @sed -i'' -e 's|"./locale-data/complete.js": false|"./locale-data/complete.js": "./locale-data/complete.js"|g' node_modules/intl/package.json @sed -i'' -e 's|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_FULL_USER);|g' node_modules/react-native-navigation/android/app/src/main/java/com/reactnativenavigation/params/Orientation.java @sed -i'' -e "s|var AndroidTextInput = requireNativeComponent('AndroidTextInput', null);|var AndroidTextInput = requireNativeComponent('CustomTextInput', null);|g" node_modules/react-native/Libraries/Components/TextInput/TextInput.js + @sed -i'' -e 's^getItemLayout || index <= this._highestMeasuredFrameIndex,^!getItemLayout || index <= this._highestMeasuredFrameIndex,^g' node_modules/react-native/Libraries/Lists/VirtualizedList.js @cd ./node_modules/react-native-svg/ios && rm -rf PerformanceBezier && git clone https://github.com/adamwulf/PerformanceBezier.git @cd ./node_modules/mattermost-redux && yarn run build diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js index d749f4fc7..cbc52009c 100644 --- a/app/actions/views/channel.js +++ b/app/actions/views/channel.js @@ -8,6 +8,7 @@ import {ViewTypes} from 'app/constants'; import {UserTypes} from 'mattermost-redux/action_types'; import { fetchMyChannelsAndMembers, + markChannelAsRead, selectChannel, leaveChannel as serviceLeaveChannel, unfavoriteChannel @@ -236,20 +237,27 @@ export function selectInitialChannel(teamId) { if (lastChannelId && myMembers[lastChannelId] && (lastChannel.team_id === teamId || isDMVisible || isGMVisible)) { handleSelectChannel(lastChannelId)(dispatch, getState); + markChannelAsRead(lastChannelId)(dispatch, getState); return; } const channel = Object.values(channels).find((c) => c.team_id === teamId && c.name === General.DEFAULT_CHANNEL); + let channelId; if (channel) { - dispatch(setChannelDisplayName('')); - handleSelectChannel(channel.id)(dispatch, getState); + channelId = channel.id; } else { // Handle case when the default channel cannot be found // so we need to get the first available channel of the team const channelsInTeam = Object.values(channels).filter((c) => c.team_id === teamId); const firstChannel = channelsInTeam.length ? channelsInTeam[0].id : {id: ''}; + + channelId = firstChannel.id; + } + + if (channelId) { dispatch(setChannelDisplayName('')); - handleSelectChannel(firstChannel.id)(dispatch, getState); + handleSelectChannel(channelId)(dispatch, getState); + markChannelAsRead(channelId)(dispatch, getState); } }; } diff --git a/app/actions/views/select_team.js b/app/actions/views/select_team.js index c0357bb5d..24685e2fe 100644 --- a/app/actions/views/select_team.js +++ b/app/actions/views/select_team.js @@ -28,9 +28,10 @@ export function handleTeamChange(teamId, selectChannel = true) { if (selectChannel) { actions.push({type: ChannelTypes.SELECT_CHANNEL, data: ''}); - const lastChannelId = state.views.team.lastChannelForTeam[teamId] || ''; + const lastChannels = state.views.team.lastChannelForTeam[teamId] || []; + const lastChannelId = lastChannels[0] || ''; const currentChannelId = getCurrentChannelId(state); - viewChannel(lastChannelId, currentChannelId)(dispatch, getState); + viewChannel(currentChannelId)(dispatch, getState); markChannelAsRead(lastChannelId, currentChannelId)(dispatch, getState); } diff --git a/app/components/inverted_flat_list/index.js b/app/components/inverted_flat_list/index.js deleted file mode 100644 index e487abe21..000000000 --- a/app/components/inverted_flat_list/index.js +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; -import {FlatList, Platform, ScrollView, StyleSheet, View} from 'react-native'; - -import RefreshList from 'app/components/refresh_list'; - -import VirtualList from './virtual_list'; - -export default class InvertibleFlatList extends PureComponent { - static propTypes = { - horizontal: PropTypes.bool, - inverted: PropTypes.bool, - ListFooterComponent: PropTypes.func, - renderItem: PropTypes.func.isRequired, - theme: PropTypes.object.isRequired - }; - - static defaultProps = { - horizontal: false, - inverted: true - }; - - constructor(props) { - super(props); - - this.inversionDirection = props.horizontal ? styles.horizontal : styles.vertical; - } - - getMetrics = () => { - return this.flatListRef.getMetrics(); - }; - - recordInteraction = () => { - this.flatListRef.recordInteraction(); - }; - - renderFooter = () => { - const {ListFooterComponent: footer} = this.props; - if (!footer) { - return null; - } - - return ( - - {footer()} - - ); - }; - - renderItem = (info) => { - return ( - - {this.props.renderItem(info)} - - ); - }; - - renderScrollComponent = (props) => { - const {theme} = this.props; - - if (props.onRefresh) { - return ( - - } - /> - ); - } - - return ; - }; - - scrollToEnd = (params) => { - this.flatListRef.scrollToEnd(params); - }; - - scrollToIndex = (params) => { - this.flatListRef.scrollToIndex(params); - }; - - scrollToItem = (params) => { - this.flatListRef.scrollToItem(params); - }; - - scrollToOffset = (params) => { - this.flatListRef.scrollToOffset(params); - }; - - setFlatListRef = (flatListRef) => { - this.flatListRef = flatListRef; - }; - - render() { - const {inverted, ...forwardedProps} = this.props; - - // If not inverted, render as an ordinary FlatList - if (!inverted) { - return ( - - ); - } - - return ( - - - - ); - } -} - -const styles = StyleSheet.create({ - container: { - flex: 1 - }, - vertical: Platform.select({ - android: { - transform: [ - {perspective: 1}, - {scaleY: -1} - ] - }, - ios: { - transform: [{scaleY: -1}] - } - }), - horizontal: Platform.select({ - android: { - transform: [ - {perspective: 1}, - {scaleY: -1} - ] - }, - ios: { - transform: [{scaleX: -1}] - } - }) -}); - diff --git a/app/components/inverted_flat_list/virtual_list.js b/app/components/inverted_flat_list/virtual_list.js deleted file mode 100644 index 5bb05424b..000000000 --- a/app/components/inverted_flat_list/virtual_list.js +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {VirtualizedList} from 'react-native'; - -/* eslint-disable */ - -export default class Virtualized extends VirtualizedList { - _onScroll = (e) => { - if (this.props.onScroll) { - this.props.onScroll(e); - } - const timestamp = e.timeStamp; - const visibleLength = this._selectLength(e.nativeEvent.layoutMeasurement); - const contentLength = this._selectLength(e.nativeEvent.contentSize); - const offset = this._selectOffset(e.nativeEvent.contentOffset); - const dt = Math.max(1, timestamp - this._scrollMetrics.timestamp); - const dOffset = offset - this._scrollMetrics.offset; - const velocity = dOffset / dt; - this._scrollMetrics = {contentLength, dt, offset, timestamp, velocity, visibleLength}; - const {data, getItemCount, onEndReached, onEndReachedThreshold, windowSize} = this.props; - this._updateViewableItems(data); - if (!data) { - return; - } - const distanceFromEnd = contentLength - visibleLength - offset; - const itemCount = getItemCount(data); - if (this.state.last === itemCount - 1 && - distanceFromEnd <= onEndReachedThreshold && - (this._hasDataChangedSinceEndReached || - this._scrollMetrics.contentLength !== this._sentEndForContentLength)) { - // Only call onEndReached once for a given dataset + content length. - this._hasDataChangedSinceEndReached = false; - this._sentEndForContentLength = this._scrollMetrics.contentLength; - onEndReached({distanceFromEnd}); - } - const {first, last} = this.state; - if ((first > 0 && velocity < 0) || (last < itemCount - 1 && velocity > 0)) { - const distanceToContentEdge = Math.min( - Math.abs(this._getFrameMetricsApprox(first).offset - offset), - Math.abs(this._getFrameMetricsApprox(last).offset - (offset + visibleLength)), - ); - const hiPri = distanceToContentEdge < (windowSize * visibleLength / 4); - if (hiPri) { - // Don't worry about interactions when scrolling quickly; focus on filling content as fast - // as possible. - this._updateCellsToRenderBatcher.dispose({abort: true}); - this._updateCellsToRender(); - return; - } - } - this._updateCellsToRenderBatcher.schedule(); - }; -} diff --git a/app/components/post_list/new_messages_divider.js b/app/components/post_list/new_messages_divider.js index fa27dbeb0..4725b2f28 100644 --- a/app/components/post_list/new_messages_divider.js +++ b/app/components/post_list/new_messages_divider.js @@ -4,7 +4,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - StyleSheet, View, ViewPropTypes } from 'react-native'; @@ -47,7 +46,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { }, line: { flex: 1, - height: StyleSheet.hairlineWidth, + height: 1, backgroundColor: theme.newMessageSeparator }, text: { diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js index dc21bfb5e..bec49293c 100644 --- a/app/components/post_list/post_list.js +++ b/app/components/post_list/post_list.js @@ -3,10 +3,12 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import {StyleSheet} from 'react-native'; +import { + StyleSheet, + FlatList +} from 'react-native'; import ChannelIntro from 'app/components/channel_intro'; -import FlatList from 'app/components/inverted_flat_list'; import Post from 'app/components/post'; import {DATE_LINE, START_OF_NEW_MESSAGES} from 'app/selectors/post_list'; import mattermostManaged from 'app/mattermost_managed'; @@ -44,6 +46,7 @@ export default class PostList extends PureComponent { constructor(props) { super(props); + this.newMessagesIndex = -1; this.state = { managedConfig: {} }; @@ -55,15 +58,27 @@ export default class PostList extends PureComponent { componentDidMount() { this.setManagedConfig(); + this.scrollList(); } componentDidUpdate(prevProps) { - if (prevProps.channelId !== this.props.channelId && this.refs.list) { - // When switching channels make sure we start from the bottom - this.refs.list.scrollToOffset({y: 0, animated: false}); + const initialPosts = !prevProps.postIds.length && prevProps.postIds !== this.props.postIds; + if ((prevProps.channelId !== this.props.channelId || initialPosts) && this.refs.list) { + this.scrollList(); } } + scrollList = () => { + requestAnimationFrame(() => { + if (this.props.postIds.length && this.newMessagesIndex !== -1) { + this.refs.list.scrollToIndex({index: this.newMessagesIndex, viewPosition: 1, viewOffset: -10, animated: true}); + this.newMessagesIndex = -1; + } else { + this.refs.list.scrollToOffset({y: 0, animated: false}); + } + }); + }; + setManagedConfig = async (config) => { let nextConfig = config; if (!nextConfig) { @@ -73,11 +88,7 @@ export default class PostList extends PureComponent { this.setState({ managedConfig: nextConfig }); - } - - getItem = (data, index) => data[index]; - - getItemCount = (data) => data.length; + }; keyExtractor = (item) => { // All keys are strings (either post IDs or special keys) @@ -102,6 +113,7 @@ export default class PostList extends PureComponent { renderItem = ({item, index}) => { if (item === START_OF_NEW_MESSAGES) { + this.newMessagesIndex = index; return ( ); diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel.js index 3da4afc82..c3932f2be 100644 --- a/app/screens/channel/channel.js +++ b/app/screens/channel/channel.js @@ -64,14 +64,6 @@ class Channel extends PureComponent { } } - componentDidMount() { - // Mark current channel as read when opening app while logged in - if (this.props.currentChannelId) { - this.props.actions.markChannelAsRead(this.props.currentChannelId); - this.props.actions.viewChannel(this.props.currentChannelId); - } - } - componentWillReceiveProps(nextProps) { if (nextProps.currentTeamId && this.props.currentTeamId !== nextProps.currentTeamId) { this.loadChannels(nextProps.currentTeamId); diff --git a/app/selectors/post_list.js b/app/selectors/post_list.js index 2d2730a23..b21e1556d 100644 --- a/app/selectors/post_list.js +++ b/app/selectors/post_list.js @@ -62,8 +62,8 @@ export function makePreparePostIdsForPostList() { } // Only add the new messages line if a lastViewedAt time is set - const postIsUnread = post.create_at > lastViewedAt; - if (lastViewedAt != null && !addedNewMessagesIndicator && postIsUnread) { + const postIsUnread = post.create_at > lastViewedAt && post.user_id !== currentUserId; + if (lastViewedAt !== null && !addedNewMessagesIndicator && postIsUnread) { out.push(START_OF_NEW_MESSAGES); addedNewMessagesIndicator = true; } diff --git a/yarn.lock b/yarn.lock index 739a3117c..1e1e993eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3938,7 +3938,7 @@ makeerror@1.0.x: mattermost-redux@mattermost/mattermost-redux#master: version "1.0.1" - resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/ae530a32d0d4c5b0f3c8200f7d7890439e6cf9ca" + resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/3c8c5c315e44daec2cbe0d6d88a79555e011c8d4" dependencies: deep-equal "1.0.1" form-data "2.3.1"