diff --git a/app/components/channel_drawer/channels_list/channel_item/channel_item.js b/app/components/channel_drawer/channels_list/channel_item/channel_item.js
index bd2bc46b4..261291e63 100644
--- a/app/components/channel_drawer/channels_list/channel_item/channel_item.js
+++ b/app/components/channel_drawer/channels_list/channel_item/channel_item.js
@@ -4,16 +4,21 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
+ Animated,
+ Platform,
TouchableHighlight,
Text,
View
} from 'react-native';
+import {intlShape} from 'react-intl';
import Badge from 'app/components/badge';
import ChannelIcon from 'app/components/channel_icon';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+const {View: AnimatedView} = Animated;
+
export default class ChannelItem extends PureComponent {
static propTypes = {
channelId: PropTypes.string.isRequired,
@@ -22,12 +27,17 @@ export default class ChannelItem extends PureComponent {
fake: PropTypes.bool,
isUnread: PropTypes.bool,
mentions: PropTypes.number.isRequired,
+ navigator: PropTypes.object,
onSelectChannel: PropTypes.func.isRequired,
status: PropTypes.string,
type: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired
};
+ static contextTypes = {
+ intl: intlShape
+ };
+
onPress = wrapWithPreventDoubleTap(() => {
const {channelId, currentChannelId, displayName, fake, onSelectChannel, type} = this.props;
requestAnimationFrame(() => {
@@ -35,6 +45,30 @@ export default class ChannelItem extends PureComponent {
});
});
+ onPreview = () => {
+ const {channelId, navigator} = this.props;
+ if (Platform.OS === 'ios' && navigator && this.previewRef) {
+ const {intl} = this.context;
+
+ navigator.push({
+ screen: 'ChannelPeek',
+ previewCommit: false,
+ previewView: this.previewRef,
+ previewActions: [{
+ id: 'action-mark-as-read',
+ title: intl.formatMessage({id: 'mobile.channel.markAsRead', defaultMessage: 'Mark As Read'})
+ }],
+ passProps: {
+ channelId
+ }
+ });
+ }
+ };
+
+ setPreviewRef = (ref) => {
+ this.previewRef = ref;
+ };
+
render() {
const {
channelId,
@@ -93,25 +127,28 @@ export default class ChannelItem extends PureComponent {
);
return (
-
-
- {extraBorder}
-
- {icon}
-
- {displayName}
-
- {badge}
+
+
+
+ {extraBorder}
+
+ {icon}
+
+ {displayName}
+
+ {badge}
+
-
-
+
+
);
}
}
diff --git a/app/components/channel_drawer/channels_list/list/list.js b/app/components/channel_drawer/channels_list/list/list.js
index 60edccd05..0acfbf46b 100644
--- a/app/components/channel_drawer/channels_list/list/list.js
+++ b/app/components/channel_drawer/channels_list/list/list.js
@@ -10,7 +10,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 {General} from 'mattermost-redux/constants';
@@ -21,12 +21,11 @@ import UnreadIndicator from 'app/components/channel_drawer/channels_list/unread_
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity} from 'app/utils/theme';
-class List extends PureComponent {
+export default class List extends PureComponent {
static propTypes = {
canCreatePrivateChannels: PropTypes.bool.isRequired,
directChannelIds: PropTypes.array.isRequired,
favoriteChannelIds: PropTypes.array.isRequired,
- intl: intlShape.isRequired,
navigator: PropTypes.object,
onSelectChannel: PropTypes.func.isRequired,
publicChannelIds: PropTypes.array.isRequired,
@@ -36,6 +35,10 @@ class List extends PureComponent {
unreadChannelIds: PropTypes.array.isRequired
};
+ static contextTypes = {
+ intl: intlShape
+ };
+
constructor(props) {
super(props);
@@ -140,7 +143,8 @@ class List extends PureComponent {
};
goToCreatePrivateChannel = wrapWithPreventDoubleTap(() => {
- const {intl, navigator, theme} = this.props;
+ const {navigator, theme} = this.props;
+ const {intl} = this.context;
navigator.showModal({
screen: 'CreateChannel',
@@ -162,7 +166,8 @@ class List extends PureComponent {
});
goToDirectMessages = wrapWithPreventDoubleTap(() => {
- const {intl, navigator, theme} = this.props;
+ const {navigator, theme} = this.props;
+ const {intl} = this.context;
navigator.showModal({
screen: 'MoreDirectMessages',
@@ -186,7 +191,8 @@ class List extends PureComponent {
});
goToMoreChannels = wrapWithPreventDoubleTap(() => {
- const {intl, navigator, theme} = this.props;
+ const {navigator, theme} = this.props;
+ const {intl} = this.context;
navigator.showModal({
screen: 'MoreChannels',
@@ -245,6 +251,7 @@ class List extends PureComponent {
return (
);
@@ -255,13 +262,15 @@ class List extends PureComponent {
);
};
renderSectionHeader = ({section}) => {
- const {intl, styles} = this.props;
+ const {styles} = this.props;
+ const {intl} = this.context;
const {
action,
bottomSeparator,
@@ -346,5 +355,3 @@ class List extends PureComponent {
);
}
}
-
-export default injectIntl(List);
diff --git a/app/components/channel_intro/index.js b/app/components/channel_intro/index.js
index 78224f0d7..47a20cda6 100644
--- a/app/components/channel_intro/index.js
+++ b/app/components/channel_intro/index.js
@@ -3,40 +3,45 @@
import {connect} from 'react-redux';
import {General, RequestStatus} from 'mattermost-redux/constants';
-import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
-import {getCurrentUser, getProfilesInCurrentChannel} from 'mattermost-redux/selectors/entities/users';
+import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
+import {getCurrentUser, makeGetProfilesInChannel} from 'mattermost-redux/selectors/entities/users';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import ChannelIntro from './channel_intro';
-function mapStateToProps(state) {
- const currentChannel = getCurrentChannel(state) || {};
- const currentUser = getCurrentUser(state) || {};
- const {status: getPostsRequestStatus} = state.requests.posts.getPosts;
+function makeMapStateToProps() {
+ const getChannel = makeGetChannel();
+ const getProfilesInChannel = makeGetProfilesInChannel();
- let currentChannelMembers = [];
- if (currentChannel.type === General.DM_CHANNEL) {
- const otherChannelMember = currentChannel.name.split('__').find((m) => m !== currentUser.id);
- const otherProfile = state.entities.users.profiles[otherChannelMember];
- if (otherProfile) {
- currentChannelMembers.push(otherProfile);
+ return function mapStateToProps(state, ownProps) {
+ const currentChannel = getChannel(state, {id: ownProps.channelId}) || {};
+ const currentUser = getCurrentUser(state) || {};
+ const {status: getPostsRequestStatus} = state.requests.posts.getPosts;
+
+ let currentChannelMembers = [];
+ if (currentChannel.type === General.DM_CHANNEL) {
+ const otherChannelMember = currentChannel.name.split('__').find((m) => m !== currentUser.id);
+ const otherProfile = state.entities.users.profiles[otherChannelMember];
+ if (otherProfile) {
+ currentChannelMembers.push(otherProfile);
+ }
+ } else {
+ currentChannelMembers = getProfilesInChannel(state, ownProps.channelId) || [];
+ currentChannelMembers = currentChannelMembers.filter((m) => m.id !== currentUser.id);
}
- } else {
- currentChannelMembers = getProfilesInCurrentChannel(state) || [];
- currentChannelMembers = currentChannelMembers.filter((m) => m.id !== currentUser.id);
- }
- const creator = currentChannel.creator_id === currentUser.id ? currentUser : state.entities.users.profiles[currentChannel.creator_id];
- const postsInChannel = state.entities.posts.postsInChannel[currentChannel.id] || [];
+ const creator = currentChannel.creator_id === currentUser.id ? currentUser : state.entities.users.profiles[currentChannel.creator_id];
+ const postsInChannel = state.entities.posts.postsInChannel[ownProps.channelId] || [];
- return {
- creator,
- currentChannel,
- currentChannelMembers,
- isLoadingPosts: !postsInChannel.length && getPostsRequestStatus === RequestStatus.STARTED,
- theme: getTheme(state)
+ return {
+ creator,
+ currentChannel,
+ currentChannelMembers,
+ isLoadingPosts: !postsInChannel.length && getPostsRequestStatus === RequestStatus.STARTED,
+ theme: getTheme(state)
+ };
};
}
-export default connect(mapStateToProps)(ChannelIntro);
+export default connect(makeMapStateToProps)(ChannelIntro);
diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js
index 058ab2ff5..a84bf0bb9 100644
--- a/app/components/post_list/post_list.js
+++ b/app/components/post_list/post_list.js
@@ -207,7 +207,12 @@ export default class PostList extends PureComponent {
);
}
- return ;
+ return (
+
+ );
};
render() {
diff --git a/app/screens/channel_peek/channel_peek.js b/app/screens/channel_peek/channel_peek.js
new file mode 100644
index 000000000..6fab98887
--- /dev/null
+++ b/app/screens/channel_peek/channel_peek.js
@@ -0,0 +1,102 @@
+// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {View} from 'react-native';
+
+import PostList from 'app/components/post_list';
+import {makeStyleSheetFromTheme} from 'app/utils/theme';
+
+export default class ChannelPeek extends PureComponent {
+ static propTypes = {
+ actions: PropTypes.shape({
+ loadPostsIfNecessaryWithRetry: PropTypes.func.isRequired,
+ markChannelAsRead: PropTypes.func.isRequired
+ }).isRequired,
+ channelId: PropTypes.string.isRequired,
+ currentUserId: PropTypes.string,
+ lastViewedAt: PropTypes.number,
+ navigator: PropTypes.object,
+ postIds: PropTypes.array.isRequired,
+ theme: PropTypes.object.isRequired
+ };
+
+ static defaultProps = {
+ postVisibility: 15
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
+ props.actions.loadPostsIfNecessaryWithRetry(props.channelId);
+ this.state = {
+ visiblePostIds: this.getVisiblePostIds(props)
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const {postIds: nextPostIds} = nextProps;
+
+ let visiblePostIds = this.state.visiblePostIds;
+
+ if (nextPostIds !== this.props.postIds) {
+ visiblePostIds = this.getVisiblePostIds(nextProps);
+ }
+
+ this.setState({
+ visiblePostIds
+ });
+ }
+
+ getVisiblePostIds = (props) => {
+ return props.postIds.slice(0, 15);
+ };
+
+ onNavigatorEvent = (event) => {
+ if (event.type === 'PreviewActionPress') {
+ if (event.id === 'action-mark-as-read') {
+ const {actions, channelId} = this.props;
+ actions.markChannelAsRead(channelId);
+ }
+ }
+ };
+
+ render() {
+ const {
+ channelId,
+ currentUserId,
+ lastViewedAt,
+ navigator,
+ theme
+ } = this.props;
+
+ const {visiblePostIds} = this.state;
+ const style = getStyle(theme);
+
+ return (
+
+
+
+ );
+ }
+}
+
+const getStyle = makeStyleSheetFromTheme((theme) => {
+ return {
+ container: {
+ flex: 1,
+ backgroundColor: theme.centerChannelBg
+ }
+ };
+});
diff --git a/app/screens/channel_peek/index.js b/app/screens/channel_peek/index.js
new file mode 100644
index 000000000..5184cfdab
--- /dev/null
+++ b/app/screens/channel_peek/index.js
@@ -0,0 +1,37 @@
+// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {bindActionCreators} from 'redux';
+import {connect} from 'react-redux';
+
+import {markChannelAsRead} from 'mattermost-redux/actions/channels';
+import {getPostIdsInChannel} from 'mattermost-redux/selectors/entities/posts';
+import {getMyChannelMember} from 'mattermost-redux/selectors/entities/channels';
+import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
+import {loadPostsIfNecessaryWithRetry} from 'app/actions/views/channel';
+import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+
+import ChannelPeek from './channel_peek';
+
+function mapStateToProps(state, ownProps) {
+ const channelId = ownProps.channelId;
+
+ return {
+ channelId,
+ currentUserId: getCurrentUserId(state),
+ postIds: getPostIdsInChannel(state, channelId),
+ lastViewedAt: getMyChannelMember(state, channelId).last_viewed_at,
+ theme: getTheme(state)
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ loadPostsIfNecessaryWithRetry,
+ markChannelAsRead
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ChannelPeek);
diff --git a/app/screens/index.js b/app/screens/index.js
index 10d3ee475..5a719352a 100644
--- a/app/screens/index.js
+++ b/app/screens/index.js
@@ -12,6 +12,7 @@ import Channel from 'app/screens/channel';
import ChannelAddMembers from 'app/screens/channel_add_members';
import ChannelInfo from 'app/screens/channel_info';
import ChannelMembers from 'app/screens/channel_members';
+import ChannelPeek from 'app/screens/channel_peek';
import ClientUpgrade from 'app/screens/client_upgrade';
import Code from 'app/screens/code';
import CreateChannel from 'app/screens/create_channel';
@@ -63,6 +64,7 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('ChannelAddMembers', () => wrapWithContextProvider(ChannelAddMembers), store, Provider);
Navigation.registerComponent('ChannelInfo', () => wrapWithContextProvider(ChannelInfo), store, Provider);
Navigation.registerComponent('ChannelMembers', () => wrapWithContextProvider(ChannelMembers), store, Provider);
+ Navigation.registerComponent('ChannelPeek', () => wrapWithContextProvider(ChannelPeek), store, Provider);
Navigation.registerComponent('ClientUpgrade', () => wrapWithContextProvider(ClientUpgrade), store, Provider);
Navigation.registerComponent('Code', () => wrapWithContextProvider(Code), store, Provider);
Navigation.registerComponent('CreateChannel', () => wrapWithContextProvider(CreateChannel), store, Provider);
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index be0b5398c..dcfa86a85 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -1908,6 +1908,7 @@
"mobile.android.photos_permission_denied_title": "Photo library access is required",
"mobile.android.videos_permission_denied_description": "To upload videos from your library, please change your permission settings.",
"mobile.android.videos_permission_denied_title": "Video library access is required",
+ "mobile.channel.markAsRead": "Mark As Read",
"mobile.channel_drawer.search": "Jump to...",
"mobile.channel_info.alertMessageDeleteChannel": "Are you sure you want to delete the {term} {name}?",
"mobile.channel_info.alertMessageLeaveChannel": "Are you sure you want to leave the {term} {name}?",
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 537e7e042..90a858eae 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -9,4 +9,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 100c8053d4d12b6c3889d716c02d8b6e1840ed7c
-COCOAPODS: 1.3.0
+COCOAPODS: 1.3.1
diff --git a/package.json b/package.json
index 40b5393c5..19aabaaa4 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
"react-native-keyboard-aware-scroll-view": "0.4.1",
"react-native-linear-gradient": "2.3.0",
"react-native-local-auth": "enahum/react-native-local-auth.git",
- "react-native-navigation": "1.1.260",
+ "react-native-navigation": "1.1.295",
"react-native-notifications": "enahum/react-native-notifications.git",
"react-native-orientation": "enahum/react-native-orientation.git",
"react-native-passcode-status": "1.1.0",
diff --git a/yarn.lock b/yarn.lock
index ab180ad24..90937c041 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3959,27 +3959,9 @@ makeerror@1.0.x:
dependencies:
tmpl "1.0.x"
-"mattermost-redux@file:../mattermost-redux":
+mattermost-redux@mattermost/mattermost-redux#master:
version "1.0.1"
- dependencies:
- deep-equal "1.0.1"
- form-data "2.3.1"
- harmony-reflect "1.5.1"
- isomorphic-fetch "2.2.1"
- mime-db "1.30.0"
- redux "3.7.2"
- redux-action-buffer "1.1.0"
- redux-batched-actions "0.2.0"
- redux-offline "git+https://github.com/enahum/redux-offline.git"
- redux-persist "4.9.1"
- redux-thunk "2.2.0"
- reselect "3.0.1"
- serialize-error "2.1.0"
- shallow-equals "1.0.0"
-
-mattermost-redux@mattermost/mattermost-redux:
- version "1.0.1"
- resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/c293dff26e4db3cde91bf405677700c9fc6a58d8"
+ resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/4f36c293b136cdfb9f44b9536a5006084d0dc758"
dependencies:
deep-equal "1.0.1"
form-data "2.3.1"
@@ -5137,9 +5119,9 @@ react-native-mock@0.3.1:
react-timer-mixin "^0.13.3"
warning "^2.1.0"
-react-native-navigation@1.1.260:
- version "1.1.260"
- resolved "https://registry.yarnpkg.com/react-native-navigation/-/react-native-navigation-1.1.260.tgz#b886e23ab302f330145ecba94029807920421f97"
+react-native-navigation@1.1.295:
+ version "1.1.295"
+ resolved "https://registry.yarnpkg.com/react-native-navigation/-/react-native-navigation-1.1.295.tgz#3ee6b2fc4f94671246498ed285f3592355c063d3"
dependencies:
lodash "4.x.x"