diff --git a/android/app/build.gradle b/android/app/build.gradle
index 6e1117e6b..886fb4c15 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -113,7 +113,7 @@ android {
applicationId "com.mattermost.rnbeta"
minSdkVersion 21
targetSdkVersion 26
- versionCode 135
+ versionCode 136
versionName "1.12.0"
multiDexEnabled = true
ndk {
diff --git a/app/components/emoji/emoji.js b/app/components/emoji/emoji.js
index c2112dbe2..dcb6b2126 100644
--- a/app/components/emoji/emoji.js
+++ b/app/components/emoji/emoji.js
@@ -14,13 +14,6 @@ import {
import CustomPropTypes from 'app/constants/custom_prop_types';
import ImageCacheManager from 'app/utils/image_cache_manager';
-const scaleEmojiBasedOnDevice = (size) => {
- if (Platform.OS === 'ios') {
- return size * 1.1; // slightly larger emojis look better on ios
- }
- return size * PixelRatio.get();
-};
-
export default class Emoji extends React.PureComponent {
static propTypes = {
@@ -60,30 +53,28 @@ export default class Emoji extends React.PureComponent {
this.state = {
imageUrl: null,
- originalWidth: 0,
- originalHeight: 0,
};
}
componentWillMount() {
+ const {displayTextOnly, imageUrl} = this.props;
this.mounted = true;
- if (!this.props.displayTextOnly && this.props.imageUrl) {
- ImageCacheManager.cache(this.props.imageUrl, this.props.imageUrl, this.updateImageHeight);
+ if (!displayTextOnly && imageUrl) {
+ ImageCacheManager.cache(imageUrl, imageUrl, this.setImageUrl);
}
}
componentWillReceiveProps(nextProps) {
- if (nextProps.emojiName !== this.props.emojiName) {
+ const {displayTextOnly, emojiName, imageUrl} = nextProps;
+ if (emojiName !== this.props.emojiName) {
this.setState({
imageUrl: null,
- originalWidth: 0,
- originalHeight: 0,
});
}
- if (!nextProps.displayTextOnly && nextProps.imageUrl &&
- nextProps.imageUrl !== this.props.imageUrl) {
- ImageCacheManager.cache(nextProps.imageUrl, nextProps.imageUrl, this.updateImageHeight);
+ if (!displayTextOnly && imageUrl &&
+ imageUrl !== this.props.imageUrl) {
+ ImageCacheManager.cache(imageUrl, imageUrl, this.setImageUrl);
}
}
@@ -91,21 +82,15 @@ export default class Emoji extends React.PureComponent {
this.mounted = false;
}
- updateImageHeight = (imageUrl) => {
+ setImageUrl = (imageUrl) => {
let prefix = '';
if (Platform.OS === 'android') {
prefix = 'file://';
}
const uri = `${prefix}${imageUrl}`;
- Image.getSize(uri, (originalWidth, originalHeight) => {
- if (this.mounted) {
- this.setState({
- imageUrl: uri,
- originalWidth,
- originalHeight,
- });
- }
+ this.setState({
+ imageUrl: uri,
});
};
@@ -122,26 +107,15 @@ export default class Emoji extends React.PureComponent {
if (!size && textStyle) {
const flatten = StyleSheet.flatten(textStyle);
fontSize = flatten.fontSize;
- size = scaleEmojiBasedOnDevice(fontSize);
+ size = fontSize * (Platform.OS === 'android' ? PixelRatio.get() : 1);
}
if (displayTextOnly) {
return {literal};
}
- let width = size;
- let height = size;
- let {originalHeight, originalWidth} = this.state;
- originalHeight = scaleEmojiBasedOnDevice(originalHeight);
- originalWidth = scaleEmojiBasedOnDevice(originalWidth);
- if (originalHeight && originalWidth) {
- if (originalWidth > originalHeight) {
- height = (size * originalHeight) / originalWidth;
- } else if (originalWidth < originalHeight) {
- // This may cause text to reflow, but its impossible to add a horizontal margin
- width = (size * originalWidth) / originalHeight;
- }
- }
+ const width = size;
+ const height = size;
let marginTop = 0;
if (textStyle) {
diff --git a/app/components/emoji_picker/emoji_picker.js b/app/components/emoji_picker/emoji_picker.js
index 10a391c0a..509cf781d 100644
--- a/app/components/emoji_picker/emoji_picker.js
+++ b/app/components/emoji_picker/emoji_picker.js
@@ -407,6 +407,7 @@ export default class EmojiPicker extends PureComponent {
renderItem={this.flatListRenderItem}
pageSize={10}
initialListSize={10}
+ removeClippedSubviews={true}
/>
);
} else {
@@ -558,6 +559,7 @@ const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightWidth: 1,
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2),
+ overflow: 'hidden',
},
listView: {
backgroundColor: theme.centerChannelBg,
diff --git a/app/components/emoji_picker/emoji_picker_row.js b/app/components/emoji_picker/emoji_picker_row.js
index 0c1937bab..4f69eccac 100644
--- a/app/components/emoji_picker/emoji_picker_row.js
+++ b/app/components/emoji_picker/emoji_picker_row.js
@@ -64,7 +64,7 @@ export default class EmojiPickerRow extends Component {
/>
);
- }
+ };
render() {
const {emojiGutter, items} = this.props;
@@ -86,6 +86,7 @@ const styles = StyleSheet.create({
emoji: {
alignItems: 'center',
justifyContent: 'center',
+ overflow: 'hidden',
},
emojiLeft: {
marginLeft: 0,
diff --git a/app/components/failed_network_action/index.js b/app/components/failed_network_action/index.js
index f2f262f37..9126258f8 100644
--- a/app/components/failed_network_action/index.js
+++ b/app/components/failed_network_action/index.js
@@ -14,6 +14,19 @@ export default class FailedNetworkAction extends PureComponent {
static propTypes = {
onRetry: PropTypes.func,
theme: PropTypes.object.isRequired,
+ errorTitle: PropTypes.object,
+ errorDescription: PropTypes.object,
+ };
+
+ static defaultProps = {
+ errorTitle: {
+ id: 'mobile.failed_network_action.title',
+ defaultMessage: 'No internet connection',
+ },
+ errorDescription: {
+ id: 'mobile.failed_network_action.description',
+ defaultMessage: 'There seems to be a problem with your internet connection. Make sure you have an active connection and try again.',
+ },
};
render() {
@@ -28,13 +41,13 @@ export default class FailedNetworkAction extends PureComponent {
width={76}
/>
{onRetry &&
diff --git a/app/components/message_attachments/message_attachment.js b/app/components/message_attachments/message_attachment.js
index d057df232..2ccd9f1a0 100644
--- a/app/components/message_attachments/message_attachment.js
+++ b/app/components/message_attachments/message_attachment.js
@@ -13,9 +13,10 @@ import {
View,
} from 'react-native';
-import FormattedText from 'app/components/formatted_text';
import Markdown from 'app/components/markdown';
import ProgressiveImage from 'app/components/progressive_image';
+import ShowMoreButton from 'app/components/show_more_button';
+
import CustomPropTypes from 'app/constants/custom_prop_types';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {previewImageAtIndex, calculateDimensions} from 'app/utils/images';
@@ -103,10 +104,10 @@ export default class MessageAttachment extends PureComponent {
const {height} = event.nativeEvent.layout;
const {height: deviceHeight} = Dimensions.get('window');
- if (height >= (deviceHeight * 1.2)) {
+ if (height >= (deviceHeight * 0.6)) {
this.setState({
isLongText: true,
- maxHeight: (deviceHeight * 0.6),
+ maxHeight: (deviceHeight * 0.4),
});
}
};
@@ -385,23 +386,6 @@ export default class MessageAttachment extends PureComponent {
let text;
if (attachment.text) {
- let moreLessLocale = {id: 'post_attachment.collapse', defaultMessage: 'Show less...'};
- if (collapsed) {
- moreLessLocale = {id: 'post_attachment.more', defaultMessage: 'Show more...'};
- }
-
- let moreLess;
- if (isLongText) {
- moreLess = (
-
- );
- }
-
text = (
- {moreLess}
+ {isLongText &&
+
+ }
);
}
@@ -523,11 +512,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
height: 45,
width: 45,
},
- moreLess: {
- color: theme.linkColor,
- fontSize: 12,
- marginTop: 5,
- },
headingContainer: {
alignSelf: 'stretch',
flexDirection: 'row',
diff --git a/app/components/options_context/options_context.android.js b/app/components/options_context/options_context.android.js
index 2f1c63a93..f0249d87c 100644
--- a/app/components/options_context/options_context.android.js
+++ b/app/components/options_context/options_context.android.js
@@ -8,7 +8,7 @@ import RNBottomSheet from 'react-native-bottom-sheet';
export default class OptionsContext extends PureComponent {
static propTypes = {
- actions: PropTypes.array,
+ getPostActions: PropTypes.func,
cancelText: PropTypes.string,
children: PropTypes.node.isRequired,
onPress: PropTypes.func.isRequired,
@@ -16,13 +16,13 @@ export default class OptionsContext extends PureComponent {
};
static defaultProps = {
- actions: [],
+ getPostActions: () => [],
cancelText: 'Cancel',
};
show = (additionalAction) => {
- const {actions, cancelText} = this.props;
- const nextActions = [...actions];
+ const {getPostActions, cancelText} = this.props;
+ const nextActions = getPostActions();
if (additionalAction && !additionalAction.nativeEvent && additionalAction.text) {
const copyPostIndex = nextActions.findIndex((action) => action.copyPost);
nextActions.splice(copyPostIndex + 1, 0, additionalAction);
@@ -45,11 +45,11 @@ export default class OptionsContext extends PureComponent {
};
handleHideUnderlay = () => {
- this.props.toggleSelected(false, this.props.actions.length > 0);
+ this.props.toggleSelected(false, this.props.getPostActions().length > 0);
};
handleShowUnderlay = () => {
- this.props.toggleSelected(true, this.props.actions.length > 0);
+ this.props.toggleSelected(true, this.props.getPostActions().length > 0);
};
render() {
diff --git a/app/components/options_context/options_context.ios.js b/app/components/options_context/options_context.ios.js
index 9c3fe3896..0e89b3f2d 100644
--- a/app/components/options_context/options_context.ios.js
+++ b/app/components/options_context/options_context.ios.js
@@ -8,14 +8,14 @@ import ToolTip from 'app/components/tooltip';
export default class OptionsContext extends PureComponent {
static propTypes = {
- actions: PropTypes.array,
+ getPostActions: PropTypes.func,
children: PropTypes.node.isRequired,
onPress: PropTypes.func.isRequired,
toggleSelected: PropTypes.func.isRequired,
};
static defaultProps = {
- actions: [],
+ getPostActions: () => [],
additionalActions: [],
};
@@ -23,16 +23,10 @@ export default class OptionsContext extends PureComponent {
super(props);
this.state = {
- actions: props.actions,
+ actions: props.getPostActions(),
};
}
- componentWillReceiveProps(nextProps) {
- if (this.props.actions !== nextProps.actions) {
- this.setState({actions: nextProps.actions});
- }
- }
-
handleHideUnderlay = () => {
if (!this.isShowing) {
this.props.toggleSelected(false, false);
@@ -45,11 +39,11 @@ export default class OptionsContext extends PureComponent {
handleHide = () => {
this.isShowing = false;
- this.props.toggleSelected(false, this.props.actions.length > 0);
+ this.props.toggleSelected(false, this.props.getPostActions().length > 0);
};
handleShow = () => {
- this.isShowing = this.props.actions.length > 0;
+ this.isShowing = this.props.getPostActions().length > 0;
this.props.toggleSelected(true, this.isShowing);
};
@@ -59,12 +53,12 @@ export default class OptionsContext extends PureComponent {
}
this.setState({
- actions: this.props.actions,
+ actions: this.props.getPostActions(),
});
};
show = (additionalAction) => {
- const nextActions = [...this.props.actions];
+ const nextActions = this.props.getPostActions();
if (additionalAction && additionalAction.text && !additionalAction.nativeEvent) {
const copyPostIndex = nextActions.findIndex((action) => action.copyPost);
nextActions.splice(copyPostIndex + 1, 0, additionalAction);
@@ -80,7 +74,7 @@ export default class OptionsContext extends PureComponent {
};
handlePress = () => {
- this.props.toggleSelected(false, this.props.actions.length > 0);
+ this.props.toggleSelected(false, this.props.getPostActions().length > 0);
this.props.onPress();
};
diff --git a/app/components/post/index.js b/app/components/post/index.js
index d719c3bd5..97d74b1cf 100644
--- a/app/components/post/index.js
+++ b/app/components/post/index.js
@@ -5,6 +5,7 @@ import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {createPost, deletePost, removePost} from 'mattermost-redux/actions/posts';
+import {General, Posts} from 'mattermost-redux/constants';
import {getCurrentChannelId, isCurrentChannelReadOnly} from 'mattermost-redux/selectors/entities/channels';
import {getPost, makeGetCommentCountForPost} from 'mattermost-redux/selectors/entities/posts';
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
@@ -16,8 +17,6 @@ import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general
import {insertToDraft, setPostTooltipVisible} from 'app/actions/views/channel';
import {addReaction} from 'app/actions/views/emoji';
-import {getDimensions} from 'app/selectors/device';
-import {Posts} from 'mattermost-redux/constants';
import Post from './post';
@@ -78,33 +77,36 @@ function makeMapStateToProps() {
}
}
}
- const {deviceWidth} = getDimensions(state);
const isAdmin = checkIsAdmin(roles);
const isSystemAdmin = checkIsSystemAdmin(roles);
let canDelete = false;
let canEdit = false;
+ let canEditUntil = -1;
if (post) {
canDelete = canDeletePost(state, config, license, currentTeamId, currentChannelId, currentUserId, post, isAdmin, isSystemAdmin);
canEdit = canEditPost(state, config, license, currentTeamId, currentChannelId, currentUserId, post);
+ if (canEdit && license.IsLicensed === 'true' &&
+ (config.AllowEditPost === General.ALLOW_EDIT_POST_TIME_LIMIT || (config.PostEditTimeLimit !== -1 && config.PostEditTimeLimit !== '-1'))
+ ) {
+ canEditUntil = post.create_at + (config.PostEditTimeLimit * 1000);
+ }
}
return {
channelIsReadOnly: isCurrentChannelReadOnly(state),
- config,
canDelete,
canEdit,
+ canEditUntil,
currentTeamUrl: getCurrentTeamUrl(state),
currentUserId,
- deviceWidth,
post,
isFirstReply,
isLastReply,
consecutivePost: isConsecutivePost(state, ownProps),
hasComments: getCommentCountForPost(state, {post}) > 0,
commentedOnPost,
- license,
theme: getTheme(state),
isFlagged: isPostFlagged(post.id, myPreferences),
};
diff --git a/app/components/post/post.js b/app/components/post/post.js
index 2dbfa03bc..37bea8265 100644
--- a/app/components/post/post.js
+++ b/app/components/post/post.js
@@ -25,9 +25,8 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {getToolTipVisible} from 'app/utils/tooltip';
import {Posts} from 'mattermost-redux/constants';
-import DelayedAction from 'mattermost-redux/utils/delayed_action';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
-import {editDisable, isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils';
+import {isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils';
import Config from 'assets/config';
@@ -41,10 +40,8 @@ export default class Post extends PureComponent {
removePost: PropTypes.func.isRequired,
}).isRequired,
channelIsReadOnly: PropTypes.bool,
- config: PropTypes.object.isRequired,
currentTeamUrl: PropTypes.string.isRequired,
currentUserId: PropTypes.string.isRequired,
- deviceWidth: PropTypes.number.isRequired,
highlight: PropTypes.bool,
style: ViewPropTypes.style,
post: PropTypes.object,
@@ -56,10 +53,10 @@ export default class Post extends PureComponent {
hasComments: PropTypes.bool,
isSearchResult: PropTypes.bool,
commentedOnPost: PropTypes.object,
- license: PropTypes.object.isRequired,
managedConfig: PropTypes.object.isRequired,
navigator: PropTypes.object,
canEdit: PropTypes.bool.isRequired,
+ canEditUntil: PropTypes.number.isRequired,
canDelete: PropTypes.bool.isRequired,
onPermalinkPress: PropTypes.func,
shouldRenderReplyButton: PropTypes.bool,
@@ -83,37 +80,6 @@ export default class Post extends PureComponent {
intl: intlShape.isRequired,
};
- constructor(props) {
- super(props);
-
- const {config, license, currentUserId, post} = props;
- this.editDisableAction = new DelayedAction(this.handleEditDisable);
- if (post) {
- editDisable(config, license, currentUserId, post, this.editDisableAction);
- }
- this.state = {
- canEdit: this.props.canEdit,
- };
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.config !== this.props.config ||
- nextProps.license !== this.props.license ||
- nextProps.currentUserId !== this.props.currentUserId ||
- nextProps.post !== this.props.post) {
- const {config, license, currentUserId, post} = nextProps;
-
- editDisable(config, license, currentUserId, post, this.editDisableAction);
- this.setState({
- canEdit: nextProps.canEdit,
- });
- }
- }
-
- componentWillUnmount() {
- this.editDisableAction.cancel();
- }
-
goToUserProfile = () => {
const {intl} = this.context;
const {navigator, post, theme} = this.props;
@@ -165,7 +131,6 @@ export default class Post extends PureComponent {
text: formatMessage({id: 'post_info.del', defaultMessage: 'Delete'}),
style: 'destructive',
onPress: () => {
- this.editDisableAction.cancel();
actions.deletePost(post);
if (post.user_id === currentUserId) {
actions.removePost(post);
@@ -479,7 +444,8 @@ export default class Post extends PureComponent {
Date.now())) {
actions.push({text: formatMessage({id: 'post_info.edit', defaultMessage: 'Edit'}), onPress: onPostEdit});
}
@@ -336,64 +337,12 @@ export default class PostBody extends PureComponent {
);
};
- renderShowMoreOption = (style) => {
- const {highlight, theme} = this.props;
- const {isLongPost} = this.state;
-
- if (!isLongPost) {
- return null;
- }
-
- const gradientColors = [];
- if (highlight) {
- gradientColors.push(
- changeOpacity(theme.mentionHighlightBg, 0),
- changeOpacity(theme.mentionHighlightBg, 0.15),
- changeOpacity(theme.mentionHighlightBg, 0.5),
- );
- } else {
- gradientColors.push(
- changeOpacity(theme.centerChannelBg, 0),
- changeOpacity(theme.centerChannelBg, 0.75),
- theme.centerChannelBg,
- );
- }
-
- return (
-
-
-
-
-
-
-
- {'+'}
-
-
-
-
-
-
-
- );
- };
-
render() {
const {formatMessage} = this.context.intl;
const {
hasBeenDeleted,
hasBeenEdited,
+ highlight,
isFailed,
isPending,
isPostAddChannelMember,
@@ -488,7 +437,7 @@ export default class PostBody extends PureComponent {
body = (
{messageComponent}
- {this.renderShowMoreOption(style)}
+ {isLongPost &&
+
+ }
{this.renderPostAdditionalContent(blockStyles, messageStyle, textStyles)}
{this.renderFileAttachments()}
@@ -562,56 +516,5 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
systemMessage: {
opacity: 0.6,
},
- showMoreGradient: {
- flex: 1,
- height: 50,
- position: 'absolute',
- top: -50,
- width: '100%',
- },
- showMoreContainer: {
- alignItems: 'center',
- justifyContent: 'center',
- flex: 1,
- flexDirection: 'row',
- position: 'relative',
- top: -7.5,
- },
- showMoreDividerLeft: {
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
- flex: 1,
- height: 1,
- marginRight: 10,
- },
- showMoreDividerRight: {
- backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
- flex: 1,
- height: 1,
- marginLeft: 10,
- },
- showMoreButtonContainer: {
- backgroundColor: theme.centerChannelBg,
- borderColor: changeOpacity(theme.centerChannelColor, 0.2),
- borderRadius: 4,
- borderWidth: 1,
- height: 37,
- paddingHorizontal: 10,
- },
- showMoreButton: {
- alignItems: 'center',
- flex: 1,
- flexDirection: 'row',
- },
- showMorePlusSign: {
- color: theme.linkColor,
- fontSize: 16,
- fontWeight: '600',
- marginRight: 8,
- },
- showMoreText: {
- color: theme.linkColor,
- fontSize: 13,
- fontWeight: '600',
- },
};
});
diff --git a/app/components/post_header/post_header.js b/app/components/post_header/post_header.js
index d821b1cee..d6c28f752 100644
--- a/app/components/post_header/post_header.js
+++ b/app/components/post_header/post_header.js
@@ -215,7 +215,7 @@ export default class PostHeader extends PureComponent {
}
return (
-
+
{this.getDisplayName(style)}
@@ -253,7 +253,7 @@ export default class PostHeader extends PureComponent {
{this.renderCommentedOnMessage(style)}
}
-
+
);
}
}
diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js
index af86fad6d..c933abc5b 100644
--- a/app/components/post_list/post_list.js
+++ b/app/components/post_list/post_list.js
@@ -234,9 +234,11 @@ export default class PostList extends PureComponent {
nextConfig = await mattermostManaged.getLocalConfig();
}
- this.setState({
- managedConfig: nextConfig,
- });
+ if (Object.keys(nextConfig).length) {
+ this.setState({
+ managedConfig: nextConfig,
+ });
+ }
};
keyExtractor = (item) => {
diff --git a/app/components/post_list/with_layout.js b/app/components/post_list/with_layout.js
index dcef92225..5ed73e34a 100644
--- a/app/components/post_list/with_layout.js
+++ b/app/components/post_list/with_layout.js
@@ -28,9 +28,10 @@ function withLayout(WrappedComponent) {
};
render() {
+ const {index, onLayoutCalled, shouldCallOnLayout, ...otherProps} = this.props; //eslint-disable-line no-unused-vars
return (
-
+
);
}
diff --git a/app/components/show_more_button/__snapshots__/show_more_button.test.js.snap b/app/components/show_more_button/__snapshots__/show_more_button.test.js.snap
new file mode 100644
index 000000000..283dae457
--- /dev/null
+++ b/app/components/show_more_button/__snapshots__/show_more_button.test.js.snap
@@ -0,0 +1,893 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ShowMoreButton should match, button snapshot 1`] = `
+
+
+ +
+
+
+
+`;
+
+exports[`ShowMoreButton should match, button snapshot 2`] = `
+
+
+ -
+
+
+
+`;
+
+exports[`ShowMoreButton should match, full snapshot 1`] = `
+ShallowWrapper {
+ "length": 1,
+ Symbol(enzyme.__root__): [Circular],
+ Symbol(enzyme.__unrendered__): ,
+ Symbol(enzyme.__renderer__): Object {
+ "batchedUpdates": [Function],
+ "getNode": [Function],
+ "render": [Function],
+ "simulateEvent": [Function],
+ "unmount": [Function],
+ },
+ Symbol(enzyme.__node__): Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+ ,
+
+
+
+
+
+ +
+
+
+
+
+
+ ,
+ ],
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "colors": Array [
+ "rgba(47,62,78,0)",
+ "rgba(47,62,78,0.75)",
+ "#2f3e4e",
+ ],
+ "locations": Array [
+ 0,
+ 0.7,
+ 1,
+ ],
+ "style": Object {
+ "flex": 1,
+ "height": 50,
+ "position": "absolute",
+ "top": -50,
+ "width": "100%",
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+ ,
+
+
+
+ +
+
+
+
+ ,
+ ,
+ ],
+ "style": Object {
+ "alignItems": "center",
+ "flex": 1,
+ "flexDirection": "row",
+ "justifyContent": "center",
+ "position": "relative",
+ "top": 10,
+ },
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "style": Object {
+ "backgroundColor": "rgba(221,221,221,0.2)",
+ "flex": 1,
+ "height": 1,
+ "marginRight": 10,
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "activeOpacity": 0.2,
+ "children":
+
+ +
+
+
+ ,
+ "onPress": [MockFunction],
+ "style": Object {
+ "backgroundColor": "#2f3e4e",
+ "borderColor": "rgba(221,221,221,0.2)",
+ "borderRadius": 4,
+ "borderWidth": 1,
+ "height": 37,
+ "paddingHorizontal": 10,
+ },
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+
+ +
+ ,
+ ,
+ ],
+ "style": Object {
+ "alignItems": "center",
+ "flex": 1,
+ "flexDirection": "row",
+ },
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "accessible": true,
+ "allowFontScaling": true,
+ "children": "+",
+ "ellipsizeMode": "tail",
+ "style": Object {
+ "color": undefined,
+ "fontSize": 16,
+ "fontWeight": "600",
+ "marginRight": 8,
+ },
+ },
+ "ref": null,
+ "rendered": "+",
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "defaultMessage": "Show More",
+ "id": "post_info.message.show_more",
+ "style": Object {
+ "color": undefined,
+ "fontSize": 13,
+ "fontWeight": "600",
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "style": Object {
+ "backgroundColor": "rgba(221,221,221,0.2)",
+ "flex": 1,
+ "height": 1,
+ "marginLeft": 10,
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ Symbol(enzyme.__nodes__): Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+ ,
+
+
+
+
+
+ +
+
+
+
+
+
+ ,
+ ],
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "colors": Array [
+ "rgba(47,62,78,0)",
+ "rgba(47,62,78,0.75)",
+ "#2f3e4e",
+ ],
+ "locations": Array [
+ 0,
+ 0.7,
+ 1,
+ ],
+ "style": Object {
+ "flex": 1,
+ "height": 50,
+ "position": "absolute",
+ "top": -50,
+ "width": "100%",
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+ ,
+
+
+
+ +
+
+
+
+ ,
+ ,
+ ],
+ "style": Object {
+ "alignItems": "center",
+ "flex": 1,
+ "flexDirection": "row",
+ "justifyContent": "center",
+ "position": "relative",
+ "top": 10,
+ },
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "style": Object {
+ "backgroundColor": "rgba(221,221,221,0.2)",
+ "flex": 1,
+ "height": 1,
+ "marginRight": 10,
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "activeOpacity": 0.2,
+ "children":
+
+ +
+
+
+ ,
+ "onPress": [MockFunction],
+ "style": Object {
+ "backgroundColor": "#2f3e4e",
+ "borderColor": "rgba(221,221,221,0.2)",
+ "borderRadius": 4,
+ "borderWidth": 1,
+ "height": 37,
+ "paddingHorizontal": 10,
+ },
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+
+ +
+ ,
+ ,
+ ],
+ "style": Object {
+ "alignItems": "center",
+ "flex": 1,
+ "flexDirection": "row",
+ },
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "accessible": true,
+ "allowFontScaling": true,
+ "children": "+",
+ "ellipsizeMode": "tail",
+ "style": Object {
+ "color": undefined,
+ "fontSize": 16,
+ "fontWeight": "600",
+ "marginRight": 8,
+ },
+ },
+ "ref": null,
+ "rendered": "+",
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "defaultMessage": "Show More",
+ "id": "post_info.message.show_more",
+ "style": Object {
+ "color": undefined,
+ "fontSize": 13,
+ "fontWeight": "600",
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "style": Object {
+ "backgroundColor": "rgba(221,221,221,0.2)",
+ "flex": 1,
+ "height": 1,
+ "marginLeft": 10,
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ ],
+ Symbol(enzyme.__options__): Object {
+ "adapter": ReactSixteenAdapter {
+ "options": Object {
+ "enableComponentDidUpdateOnSetState": true,
+ },
+ },
+ },
+}
+`;
diff --git a/app/components/show_more_button/index.js b/app/components/show_more_button/index.js
new file mode 100644
index 000000000..0002f53a2
--- /dev/null
+++ b/app/components/show_more_button/index.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {connect} from 'react-redux';
+
+import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+
+import ShowMoreButton from './show_more_button';
+
+function mapStateToProps(state) {
+ return {
+ theme: getTheme(state),
+ };
+}
+
+export default connect(mapStateToProps)(ShowMoreButton);
diff --git a/app/components/show_more_button/show_more_button.js b/app/components/show_more_button/show_more_button.js
new file mode 100644
index 000000000..48e783378
--- /dev/null
+++ b/app/components/show_more_button/show_more_button.js
@@ -0,0 +1,142 @@
+// 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 {Text, TouchableOpacity, View} from 'react-native';
+import LinearGradient from 'react-native-linear-gradient';
+
+import FormattedText from 'app/components/formatted_text';
+
+import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
+
+export default class ShowMoreButton extends PureComponent {
+ static propTypes = {
+ highlight: PropTypes.bool,
+ onPress: PropTypes.func.isRequired,
+ showMore: PropTypes.bool.isRequired,
+ theme: PropTypes.object.isRequired,
+ };
+
+ static defaultProps = {
+ showMore: true,
+ };
+
+ renderButton(showMore, style) {
+ let sign = '+';
+ let textId = 'post_info.message.show_more';
+ let textMessage = 'Show More';
+ if (!showMore) {
+ sign = '-';
+ textId = 'post_info.message.show_less';
+ textMessage = 'Show Less';
+ }
+
+ return (
+
+ {sign}
+
+
+ );
+ }
+
+ render() {
+ const {highlight, showMore, theme} = this.props;
+ const style = getStyleSheet(theme, showMore);
+
+ let gradientColors = [
+ changeOpacity(theme.centerChannelBg, 0),
+ changeOpacity(theme.centerChannelBg, 0.75),
+ theme.centerChannelBg,
+ ];
+ if (highlight) {
+ gradientColors = [
+ changeOpacity(theme.mentionHighlightBg, 0),
+ changeOpacity(theme.mentionHighlightBg, 0.15),
+ changeOpacity(theme.mentionHighlightBg, 0.5),
+ ];
+ }
+
+ return (
+
+ {showMore &&
+
+ }
+
+
+
+ {this.renderButton(showMore, style)}
+
+
+
+
+ );
+ }
+}
+
+const getStyleSheet = makeStyleSheetFromTheme((theme, showMore) => {
+ return {
+ gradient: {
+ flex: 1,
+ height: 50,
+ position: 'absolute',
+ top: -50,
+ width: '100%',
+ },
+ container: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ flex: 1,
+ flexDirection: 'row',
+ position: 'relative',
+ top: showMore ? -7.5 : 10,
+ },
+ dividerLeft: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
+ flex: 1,
+ height: 1,
+ marginRight: 10,
+ },
+ buttonContainer: {
+ backgroundColor: theme.centerChannelBg,
+ borderColor: changeOpacity(theme.centerChannelColor, 0.2),
+ borderRadius: 4,
+ borderWidth: 1,
+ height: 37,
+ paddingHorizontal: 10,
+ },
+ button: {
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'row',
+ },
+ sign: {
+ color: theme.linkColor,
+ fontSize: 16,
+ fontWeight: '600',
+ marginRight: 8,
+ },
+ text: {
+ color: theme.linkColor,
+ fontSize: 13,
+ fontWeight: '600',
+ },
+ dividerRight: {
+ backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
+ flex: 1,
+ height: 1,
+ marginLeft: 10,
+ },
+ };
+});
diff --git a/app/components/show_more_button/show_more_button.test.js b/app/components/show_more_button/show_more_button.test.js
new file mode 100644
index 000000000..7c63aa8cd
--- /dev/null
+++ b/app/components/show_more_button/show_more_button.test.js
@@ -0,0 +1,64 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {TouchableOpacity} from 'react-native';
+import {configure, shallow} from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
+configure({adapter: new Adapter()});
+
+import LinearGradient from 'react-native-linear-gradient';
+
+import ShowMoreButton from './show_more_button';
+
+describe('ShowMoreButton', () => {
+ const baseProps = {
+ highlight: false,
+ onPress: jest.fn(),
+ showMore: true,
+ theme: {
+ centerChannelBg: '#2f3e4e',
+ centerChannelColor: '#dddddd',
+ },
+ };
+
+ test('should match, full snapshot', () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('should match, button snapshot', () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.instance().renderButton(true, {button: {}, sign: {}, text: {}})).toMatchSnapshot();
+ expect(wrapper.instance().renderButton(false, {button: {}, sign: {}, text: {}})).toMatchSnapshot();
+ });
+
+ test('should LinearGradient exists', () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.find(LinearGradient).exists()).toBe(true);
+ wrapper.setProps({showMore: false});
+ expect(wrapper.find(LinearGradient).exists()).toBe(false);
+ });
+
+ test('should call props.onPress on press of TouchableOpacity', () => {
+ const onPress = jest.fn();
+ const wrapper = shallow(
+
+ );
+
+ wrapper.find(TouchableOpacity).props().onPress();
+ expect(onPress).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/app/screens/channel_info/channel_info.js b/app/screens/channel_info/channel_info.js
index 0567d0626..64f94917e 100644
--- a/app/screens/channel_info/channel_info.js
+++ b/app/screens/channel_info/channel_info.js
@@ -29,6 +29,7 @@ export default class ChannelInfo extends PureComponent {
closeGMChannel: PropTypes.func.isRequired,
deleteChannel: PropTypes.func.isRequired,
getChannelStats: PropTypes.func.isRequired,
+ getChannel: PropTypes.func.isRequired,
leaveChannel: PropTypes.func.isRequired,
loadChannelsByTeamName: PropTypes.func.isRequired,
favoriteChannel: PropTypes.func.isRequired,
@@ -202,6 +203,9 @@ export default class ChannelInfo extends PureComponent {
displayName: channel.display_name,
}
);
+ if (result.error.server_error_id === 'api.channel.delete_channel.deleted.app_error') {
+ this.props.actions.getChannel(channel.id);
+ }
} else {
this.close();
}
diff --git a/app/screens/channel_info/index.js b/app/screens/channel_info/index.js
index 81c95e8db..4f3c391a8 100644
--- a/app/screens/channel_info/index.js
+++ b/app/screens/channel_info/index.js
@@ -7,6 +7,7 @@ import {connect} from 'react-redux';
import {
favoriteChannel,
getChannelStats,
+ getChannel,
deleteChannel,
unfavoriteChannel,
updateChannelNotifyProps,
@@ -89,6 +90,7 @@ function mapDispatchToProps(dispatch) {
closeGMChannel,
deleteChannel,
getChannelStats,
+ getChannel,
leaveChannel,
loadChannelsByTeamName,
favoriteChannel,
diff --git a/app/screens/edit_channel/edit_channel.js b/app/screens/edit_channel/edit_channel.js
index 22d04d8c8..917359ea9 100644
--- a/app/screens/edit_channel/edit_channel.js
+++ b/app/screens/edit_channel/edit_channel.js
@@ -52,6 +52,7 @@ export default class EditChannel extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
patchChannel: PropTypes.func.isRequired,
+ getChannel: PropTypes.func.isRequired,
setChannelDisplayName: PropTypes.func.isRequired,
}),
navigator: PropTypes.object.isRequired,
@@ -204,7 +205,7 @@ export default class EditChannel extends PureComponent {
return {error: formatMessage(messages.name_lowercase)};
};
- onUpdateChannel = () => {
+ onUpdateChannel = async () => {
Keyboard.dismiss();
const {displayName, channelURL, purpose, header} = this.state;
const {channel: {id, type}} = this.props;
@@ -230,7 +231,10 @@ export default class EditChannel extends PureComponent {
}
}
- this.props.actions.patchChannel(id, channel);
+ const data = await this.props.actions.patchChannel(id, channel);
+ if (data.error && data.error.server_error_id === 'store.sql_channel.update.archived_channel.app_error') {
+ this.props.actions.getChannel(id);
+ }
};
onNavigatorEvent = (event) => {
diff --git a/app/screens/edit_channel/index.js b/app/screens/edit_channel/index.js
index e98433c02..4a0eb2182 100644
--- a/app/screens/edit_channel/index.js
+++ b/app/screens/edit_channel/index.js
@@ -6,7 +6,7 @@ import {connect} from 'react-redux';
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentTeamUrl} from 'mattermost-redux/selectors/entities/teams';
-import {patchChannel} from 'mattermost-redux/actions/channels';
+import {patchChannel, getChannel} from 'mattermost-redux/actions/channels';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {setChannelDisplayName} from 'app/actions/views/channel';
@@ -33,6 +33,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
patchChannel,
+ getChannel,
setChannelDisplayName,
}, dispatch),
};
diff --git a/app/screens/entry/entry.js b/app/screens/entry/entry.js
index 640396b19..8f61e1884 100644
--- a/app/screens/entry/entry.js
+++ b/app/screens/entry/entry.js
@@ -11,6 +11,7 @@ import {
import DeviceInfo from 'react-native-device-info';
+import {setSystemEmojis} from 'mattermost-redux/actions/emojis';
import {Client4} from 'mattermost-redux/client';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
@@ -56,15 +57,14 @@ const lazyLoadReplyPushNotifications = () => {
*/
export default class Entry extends PureComponent {
static propTypes = {
- config: PropTypes.object,
theme: PropTypes.object,
navigator: PropTypes.object,
isLandscape: PropTypes.bool,
- hydrationComplete: PropTypes.bool,
enableTimezone: PropTypes.bool,
deviceTimezone: PropTypes.string,
initializeModules: PropTypes.func.isRequired,
actions: PropTypes.shape({
+ autoUpdateTimezone: PropTypes.func.isRequired,
setDeviceToken: PropTypes.func.isRequired,
}).isRequired,
};
@@ -132,6 +132,7 @@ export default class Entry extends PureComponent {
this.setAppCredentials();
this.setStartupThemes();
this.handleNotification();
+ this.loadSystemEmojis();
if (Platform.OS === 'android') {
this.launchForAndroid();
@@ -226,6 +227,11 @@ export default class Entry extends PureComponent {
}
};
+ loadSystemEmojis = () => {
+ const EmojiIndicesByAlias = require('app/utils/emojis').EmojiIndicesByAlias;
+ setSystemEmojis(EmojiIndicesByAlias);
+ };
+
renderLogin = () => {
const SelectServer = lazyLoadSelectServer();
const props = {
diff --git a/app/screens/entry/index.js b/app/screens/entry/index.js
index 5048a5537..dab3808b8 100644
--- a/app/screens/entry/index.js
+++ b/app/screens/entry/index.js
@@ -6,7 +6,6 @@ import {connect} from 'react-redux';
import {setDeviceToken} from 'mattermost-redux/actions/general';
import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
-import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {isLandscape} from 'app/selectors/device';
import {getDeviceTimezone, isTimezoneEnabled} from 'app/utils/timezone';
@@ -16,16 +15,12 @@ const lazyLoadEntry = () => {
};
function mapStateToProps(state) {
- const config = getConfig(state);
-
const enableTimezone = isTimezoneEnabled(state);
const deviceTimezone = getDeviceTimezone();
return {
- config,
theme: getTheme(state),
isLandscape: isLandscape(state),
- hydrationComplete: state.views.root.hydrationComplete,
enableTimezone,
deviceTimezone,
};
@@ -34,8 +29,8 @@ function mapStateToProps(state) {
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
- setDeviceToken,
autoUpdateTimezone,
+ setDeviceToken,
}, dispatch),
};
}
diff --git a/app/screens/search/index.js b/app/screens/search/index.js
index fd0a060b1..c4cf4f39d 100644
--- a/app/screens/search/index.js
+++ b/app/screens/search/index.js
@@ -10,11 +10,14 @@ import {getCurrentChannelId, filterPostIds} from 'mattermost-redux/selectors/ent
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
+import {getUserCurrentTimezone} from 'mattermost-redux/utils/timezone_utils';
+import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
import {loadChannelsByTeamName, loadThreadIfNecessary} from 'app/actions/views/channel';
import {isLandscape} from 'app/selectors/device';
import {makePreparePostIdsForSearchPosts} from 'app/selectors/post_list';
import {handleSearchDraftChanged} from 'app/actions/views/search';
+import {getDeviceUtcOffset, getUtcOffsetForTimeZone, isTimezoneEnabled} from 'app/utils/timezone';
import Search from './search';
@@ -30,6 +33,11 @@ function makeMapStateToProps() {
const {recent} = state.entities.search;
const {searchPosts: searchRequest} = state.requests.search;
+ const currentUser = getCurrentUser(state);
+ const enableTimezone = isTimezoneEnabled(state);
+ const userCurrentTimezone = enableTimezone ? getUserCurrentTimezone(currentUser.timezone) : '';
+ const timezoneOffsetInSeconds = (userCurrentTimezone.length > 0 ? getUtcOffsetForTimeZone(userCurrentTimezone) : getDeviceUtcOffset()) * 60;
+
const serverVersion = state.entities.general.serverVersion;
const enableDateSuggestion = isMinimumServerVersion(serverVersion, 5, 3);
@@ -43,6 +51,7 @@ function makeMapStateToProps() {
searchingStatus: searchRequest.status,
theme: getTheme(state),
enableDateSuggestion,
+ timezoneOffsetInSeconds,
};
};
}
diff --git a/app/screens/search/search.js b/app/screens/search/search.js
index 58929854f..2b3bc2736 100644
--- a/app/screens/search/search.js
+++ b/app/screens/search/search.js
@@ -31,7 +31,6 @@ import SearchBar from 'app/components/search_bar';
import StatusBar from 'app/components/status_bar';
import mattermostManaged from 'app/mattermost_managed';
import {preventDoubleTap} from 'app/utils/tap';
-import {getDeviceUtcOffset} from 'app/utils/timezone';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import ChannelDisplayName from './channel_display_name';
@@ -65,6 +64,7 @@ export default class Search extends PureComponent {
searchingStatus: PropTypes.string,
theme: PropTypes.object.isRequired,
enableDateSuggestion: PropTypes.bool,
+ timezoneOffsetInSeconds: PropTypes.number.isRequired,
};
static defaultProps = {
@@ -469,8 +469,7 @@ export default class Search extends PureComponent {
});
// timezone offset in seconds
- const timeZoneOffset = getDeviceUtcOffset() * 60;
- actions.searchPostsWithParams(currentTeamId, {terms: terms.trim(), is_or_search: isOrSearch, time_zone_offset: timeZoneOffset}, true);
+ actions.searchPostsWithParams(currentTeamId, {terms: terms.trim(), is_or_search: isOrSearch, time_zone_offset: this.props.timezoneOffsetInSeconds}, true);
};
handleSearchButtonPress = preventDoubleTap((text) => {
diff --git a/app/screens/select_team/__snapshots__/select_team.test.js.snap b/app/screens/select_team/__snapshots__/select_team.test.js.snap
new file mode 100644
index 000000000..792a26e69
--- /dev/null
+++ b/app/screens/select_team/__snapshots__/select_team.test.js.snap
@@ -0,0 +1,609 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SelectTeam should match snapshot for fail of teams 1`] = `
+ShallowWrapper {
+ "length": 1,
+ Symbol(enzyme.__root__): [Circular],
+ Symbol(enzyme.__unrendered__): ,
+ Symbol(enzyme.__renderer__): Object {
+ "batchedUpdates": [Function],
+ "getNode": [Function],
+ "render": [Function],
+ "simulateEvent": [Function],
+ "unmount": [Function],
+ },
+ Symbol(enzyme.__node__): Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "errorDescription": Object {
+ "defaultMessage": "Make sure you have an active connection and try again.",
+ "id": "mobile.failed_network_action.shortDescription",
+ },
+ "errorTitle": Object {
+ "defaultMessage": "Team Not Found",
+ "id": "error.team_not_found.title",
+ },
+ "onRetry": [Function],
+ "theme": Object {},
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ Symbol(enzyme.__nodes__): Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "errorDescription": Object {
+ "defaultMessage": "Make sure you have an active connection and try again.",
+ "id": "mobile.failed_network_action.shortDescription",
+ },
+ "errorTitle": Object {
+ "defaultMessage": "Team Not Found",
+ "id": "error.team_not_found.title",
+ },
+ "onRetry": [Function],
+ "theme": Object {},
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ ],
+ Symbol(enzyme.__options__): Object {
+ "adapter": ReactSixteenAdapter {
+ "options": Object {
+ "enableComponentDidUpdateOnSetState": true,
+ },
+ },
+ },
+}
+`;
+
+exports[`SelectTeam should match snapshot for teams 1`] = `
+ShallowWrapper {
+ "length": 1,
+ Symbol(enzyme.__root__): [Circular],
+ Symbol(enzyme.__unrendered__): ,
+ Symbol(enzyme.__renderer__): Object {
+ "batchedUpdates": [Function],
+ "getNode": [Function],
+ "render": [Function],
+ "simulateEvent": [Function],
+ "unmount": [Function],
+ },
+ Symbol(enzyme.__node__): Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+ ,
+
+
+
+
+
+ ,
+ ,
+ ],
+ "style": Object {
+ "backgroundColor": undefined,
+ "flex": 1,
+ },
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {},
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+
+
+ ,
+ ,
+ ],
+ "style": Object {
+ "alignItems": "center",
+ "flexDirection": "row",
+ "marginHorizontal": 16,
+ "marginTop": 20,
+ },
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": ,
+ "style": Object {
+ "marginRight": 15,
+ },
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "defaultMessage": "Open teams you can join",
+ "id": "mobile.select_team.join_open",
+ "style": Object {
+ "color": undefined,
+ "fontSize": 13,
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "style": Object {
+ "backgroundColor": undefined,
+ "height": 1,
+ "width": "100%",
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "data": Array [
+ Object {
+ "id": "kemjcpu9bi877yegqjs18ndp4r",
+ "invite_id": "ojsnudhqzbfzpk6e4n6ip1hwae",
+ "name": "test",
+ },
+ ],
+ "disableVirtualization": false,
+ "horizontal": false,
+ "initialNumToRender": 10,
+ "keyExtractor": [Function],
+ "maxToRenderPerBatch": 10,
+ "numColumns": 1,
+ "onEndReachedThreshold": 2,
+ "renderItem": [Function],
+ "scrollEventThrottle": 50,
+ "updateCellsBatchingPeriod": 50,
+ "viewabilityConfig": Object {
+ "viewAreaCoveragePercentThreshold": 3,
+ "waitForInteraction": false,
+ },
+ "windowSize": 21,
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ Symbol(enzyme.__nodes__): Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+ ,
+
+
+
+
+
+ ,
+ ,
+ ],
+ "style": Object {
+ "backgroundColor": undefined,
+ "flex": 1,
+ },
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {},
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": Array [
+
+
+ ,
+ ,
+ ],
+ "style": Object {
+ "alignItems": "center",
+ "flexDirection": "row",
+ "marginHorizontal": 16,
+ "marginTop": 20,
+ },
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "children": ,
+ "style": Object {
+ "marginRight": 15,
+ },
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "defaultMessage": "Open teams you can join",
+ "id": "mobile.select_team.join_open",
+ "style": Object {
+ "color": undefined,
+ "fontSize": 13,
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "style": Object {
+ "backgroundColor": undefined,
+ "height": 1,
+ "width": "100%",
+ },
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "class",
+ "props": Object {
+ "data": Array [
+ Object {
+ "id": "kemjcpu9bi877yegqjs18ndp4r",
+ "invite_id": "ojsnudhqzbfzpk6e4n6ip1hwae",
+ "name": "test",
+ },
+ ],
+ "disableVirtualization": false,
+ "horizontal": false,
+ "initialNumToRender": 10,
+ "keyExtractor": [Function],
+ "maxToRenderPerBatch": 10,
+ "numColumns": 1,
+ "onEndReachedThreshold": 2,
+ "renderItem": [Function],
+ "scrollEventThrottle": 50,
+ "updateCellsBatchingPeriod": 50,
+ "viewabilityConfig": Object {
+ "viewAreaCoveragePercentThreshold": 3,
+ "waitForInteraction": false,
+ },
+ "windowSize": 21,
+ },
+ "ref": null,
+ "rendered": null,
+ "type": [Function],
+ },
+ ],
+ "type": [Function],
+ },
+ ],
+ Symbol(enzyme.__options__): Object {
+ "adapter": ReactSixteenAdapter {
+ "options": Object {
+ "enableComponentDidUpdateOnSetState": true,
+ },
+ },
+ },
+}
+`;
diff --git a/app/screens/select_team/index.js b/app/screens/select_team/index.js
index 91c6843da..8978cd48d 100644
--- a/app/screens/select_team/index.js
+++ b/app/screens/select_team/index.js
@@ -28,7 +28,7 @@ function mapStateToProps(state) {
}
return {
- teamsRequest: state.requests.teams.getMyTeams,
+ teamsRequest: state.requests.teams.getTeams,
teams: Object.values(getJoinableTeams(state)).sort(sortTeams),
currentChannelId: getCurrentChannelId(state),
joinTeamRequest: state.requests.teams.joinTeam,
diff --git a/app/screens/select_team/select_team.js b/app/screens/select_team/select_team.js
index 82d369cce..5a5f9e2db 100644
--- a/app/screens/select_team/select_team.js
+++ b/app/screens/select_team/select_team.js
@@ -15,6 +15,7 @@ import {
import {RequestStatus} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
+import FailedNetworkAction from 'app/components/failed_network_action';
import FormattedText from 'app/components/formatted_text';
import Loading from 'app/components/loading';
import StatusBar from 'app/components/status_bar';
@@ -28,6 +29,16 @@ const VIEWABILITY_CONFIG = ListTypes.VISIBILITY_CONFIG_DEFAULTS;
const TEAMS_PER_PAGE = 200;
+const errorTitle = {
+ id: 'error.team_not_found.title',
+ defaultMessage: 'Team Not Found',
+};
+
+const errorDescription = {
+ id: 'mobile.failed_network_action.shortDescription',
+ defaultMessage: 'Make sure you have an active connection and try again.',
+};
+
export default class SelectTeam extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
@@ -44,6 +55,7 @@ export default class SelectTeam extends PureComponent {
userWithoutTeams: PropTypes.bool,
teams: PropTypes.array.isRequired,
theme: PropTypes.object,
+ teamsRequest: PropTypes.object.isRequired,
};
constructor(props) {
@@ -57,9 +69,7 @@ export default class SelectTeam extends PureComponent {
}
componentDidMount() {
- this.props.actions.getTeams(0, TEAMS_PER_PAGE).then(() => {
- this.buildData(this.props);
- });
+ this.getTeams();
}
componentWillReceiveProps(nextProps) {
@@ -77,6 +87,14 @@ export default class SelectTeam extends PureComponent {
}
}
+ getTeams = () => {
+ this.setState({loading: true});
+ this.props.actions.getTeams(0, TEAMS_PER_PAGE).then(() => {
+ this.setState({loading: false});
+ this.buildData(this.props);
+ });
+ }
+
buildData = (props) => {
if (props.teams.length) {
this.setState({teams: props.teams});
@@ -213,10 +231,21 @@ export default class SelectTeam extends PureComponent {
const {teams} = this.state;
const styles = getStyleSheet(theme);
- if (this.state.joining) {
+ if (this.state.joining || this.state.loading) {
return ;
}
+ if (this.props.teamsRequest.status === RequestStatus.FAILURE) {
+ return (
+
+ );
+ }
+
return (
diff --git a/app/screens/select_team/select_team.test.js b/app/screens/select_team/select_team.test.js
new file mode 100644
index 000000000..90f7ec1f8
--- /dev/null
+++ b/app/screens/select_team/select_team.test.js
@@ -0,0 +1,97 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+import React from 'react';
+import {configure, shallow} from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
+import {RequestStatus} from 'mattermost-redux/constants';
+configure({adapter: new Adapter()});
+
+import SelectTeam from './select_team.js';
+
+jest.mock('rn-fetch-blob', () => ({
+ fs: {
+ dirs: {
+ DocumentDir: () => jest.fn(),
+ CacheDir: () => jest.fn(),
+ },
+ },
+}));
+
+jest.mock('rn-fetch-blob/fs', () => ({
+ dirs: {
+ DocumentDir: () => jest.fn(),
+ CacheDir: () => jest.fn(),
+ },
+}));
+
+jest.mock('app/utils/theme', () => {
+ const original = require.requireActual('app/utils/theme');
+ return {
+ ...original,
+ changeOpacity: jest.fn(),
+ };
+});
+
+const getTeams = async () => {
+ return {
+ error: {},
+ };
+};
+
+describe('SelectTeam', () => {
+ const actions = {
+ getTeams,
+ handleTeamChange: jest.fn(),
+ joinTeam: jest.fn(),
+ logout: jest.fn(),
+ markChannelAsRead: jest.fn(),
+ };
+
+ const baseProps = {
+ actions,
+ currentChannelId: 'someId',
+ currentUrl: 'test',
+ joinTeamRequest: {},
+ navigator: {
+ setOnNavigatorEvent: jest.fn(),
+ },
+ userWithoutTeams: false,
+ teams: [],
+ theme: {},
+ teamsRequest: {
+ status: RequestStatus.FAILURE,
+ },
+ };
+
+ test('should match snapshot for fail of teams', async () => {
+ const wrapper = shallow(
+ ,
+ );
+ expect(wrapper.state('loading')).toEqual(true);
+ await getTeams();
+ expect(wrapper.state('loading')).toEqual(false);
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('should match snapshot for teams', async () => {
+ const props = {
+ ...baseProps,
+ teams: [{
+ id: 'kemjcpu9bi877yegqjs18ndp4r',
+ invite_id: 'ojsnudhqzbfzpk6e4n6ip1hwae',
+ name: 'test',
+ }],
+ teamsRequest: {
+ status: RequestStatus.SUCCESS,
+ },
+ };
+
+ const wrapper = shallow(
+ ,
+ );
+ await getTeams();
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/app/utils/timezone.js b/app/utils/timezone.js
index 8498efecf..a91b961d7 100644
--- a/app/utils/timezone.js
+++ b/app/utils/timezone.js
@@ -5,13 +5,18 @@ import DeviceInfo from 'react-native-device-info';
import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
+import moment from 'moment-timezone';
+
export function getDeviceTimezone() {
return DeviceInfo.getTimezone();
}
export function getDeviceUtcOffset() {
- const reverseOffsetInMinutes = new Date().getTimezoneOffset();
- return -reverseOffsetInMinutes;
+ return moment().utcOffset();
+}
+
+export function getUtcOffsetForTimeZone(timezone) {
+ return moment.tz(timezone).utcOffset();
}
export function isTimezoneEnabled(state) {
diff --git a/app/utils/why_did_you_update.js b/app/utils/why_did_you_update.js
index 891a0aff7..973476d37 100644
--- a/app/utils/why_did_you_update.js
+++ b/app/utils/why_did_you_update.js
@@ -40,10 +40,10 @@ function deepDiff(o1, o2, p) {
}
}
-function whyDidYouUpdate(prevProps, prevState) {
+function whyDidYouUpdate(theClass, prevProps, prevState) {
deepDiff({props: prevProps, state: prevState},
- {props: this.props, state: this.state},
- this.constructor.name);
+ {props: theClass.props, state: theClass.state},
+ theClass.constructor.name);
}
export default whyDidYouUpdate;
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index 09d63872d..b7dc7a1c0 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -2455,6 +2455,7 @@
"mobile.failed_network_action.description": "There seems to be a problem with your internet connection. Make sure you have an active connection and try again.",
"mobile.failed_network_action.retry": "Try Again",
"mobile.failed_network_action.title": "No internet connection",
+ "mobile.failed_network_action.shortDescription": "Make sure you have an active connection and try again.",
"mobile.file_upload.browse": "Browse Files",
"mobile.file_upload.camera": "Take Photo or Video",
"mobile.file_upload.library": "Photo Library",
diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj
index 363e2b0e3..a60eb2048 100644
--- a/ios/Mattermost.xcodeproj/project.pbxproj
+++ b/ios/Mattermost.xcodeproj/project.pbxproj
@@ -2427,7 +2427,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 135;
+ CURRENT_PROJECT_VERSION = 136;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@@ -2476,7 +2476,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 135;
+ CURRENT_PROJECT_VERSION = 136;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist
index c72ccb751..a5f221f54 100644
--- a/ios/Mattermost/Info.plist
+++ b/ios/Mattermost/Info.plist
@@ -34,7 +34,7 @@
CFBundleVersion
- 135
+ 136
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist
index f28831374..72fcd2680 100644
--- a/ios/MattermostShare/Info.plist
+++ b/ios/MattermostShare/Info.plist
@@ -23,7 +23,7 @@
CFBundleShortVersionString
1.12.0
CFBundleVersion
- 135
+ 136
NSAppTransportSecurity
NSAllowsArbitraryLoads
diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist
index 9e5b4b303..1be755e85 100644
--- a/ios/MattermostTests/Info.plist
+++ b/ios/MattermostTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 135
+ 136
diff --git a/package-lock.json b/package-lock.json
index 47934b919..ecb6c9a1d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10545,8 +10545,15 @@
"moment": {
"version": "2.22.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz",
- "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==",
- "dev": true
+ "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ=="
+ },
+ "moment-timezone": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.21.tgz",
+ "integrity": "sha512-j96bAh4otsgj3lKydm3K7kdtA3iKf2m6MY2iSYCzCm5a1zmHo1g+aK3068dDEeocLZQIS9kU8bsdQHLqEvgW0A==",
+ "requires": {
+ "moment": ">= 2.9.0"
+ }
},
"morgan": {
"version": "1.9.0",
diff --git a/package.json b/package.json
index 8c98bfdb2..f5f511838 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"jsc-android": "216113.0.3",
"mattermost-redux": "github:mattermost/mattermost-redux#28be3683bff6ae8c563883bb0ff04d7bd9ea31ee",
"mime-db": "1.33.0",
+ "moment-timezone": "0.5.21",
"prop-types": "15.6.1",
"react": "16.3.2",
"react-intl": "2.4.0",