From be637c789cba90a06d42e81ef9d5c858de1e9824 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 3 Jul 2018 13:14:03 -0400 Subject: [PATCH 01/20] Fix merge conflict --- ios/Mattermost.xcodeproj/project.pbxproj | 8 -------- ios/Mattermost/Info.plist | 8 -------- ios/MattermostShare/Info.plist | 6 ------ ios/MattermostTests/Info.plist | 8 -------- 4 files changed, 30 deletions(-) diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 00c4697d7..d0cfef621 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -2532,11 +2532,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; -<<<<<<< HEAD - CURRENT_PROJECT_VERSION = 118; -======= CURRENT_PROJECT_VERSION = 119; ->>>>>>> release-1.9 DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2586,11 +2582,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; -<<<<<<< HEAD - CURRENT_PROJECT_VERSION = 118; -======= CURRENT_PROJECT_VERSION = 119; ->>>>>>> release-1.9 DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index 71a8bdbb1..f074dcdce 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -19,11 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString -<<<<<<< HEAD 1.10.0 -======= - 1.9.3 ->>>>>>> release-1.9 CFBundleSignature ???? CFBundleURLTypes @@ -38,11 +34,7 @@ CFBundleVersion -<<<<<<< HEAD - 118 -======= 119 ->>>>>>> release-1.9 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index 6228ba53d..d46265ada 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -21,15 +21,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString -<<<<<<< HEAD 1.10.0 CFBundleVersion - 118 -======= - 1.9.3 - CFBundleVersion 119 ->>>>>>> release-1.9 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index 074beee82..8cf3f041a 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -15,18 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString -<<<<<<< HEAD 1.10.0 CFBundleSignature ???? CFBundleVersion - 118 -======= - 1.9.3 - CFBundleSignature - ???? - CFBundleVersion 119 ->>>>>>> release-1.9 From 478b72f0658538326a90d6cf48a24db33385aae0 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 3 Jul 2018 15:10:33 -0400 Subject: [PATCH 02/20] Bump iOS build number to 120 (#1868) --- ios/Mattermost.xcodeproj/project.pbxproj | 4 ++-- ios/Mattermost/Info.plist | 2 +- ios/MattermostShare/Info.plist | 2 +- ios/MattermostTests/Info.plist | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index d0cfef621..a1cfb7e79 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -2532,7 +2532,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 119; + CURRENT_PROJECT_VERSION = 120; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2582,7 +2582,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 119; + CURRENT_PROJECT_VERSION = 120; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index f074dcdce..a13013223 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 119 + 120 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index d46265ada..5fb661e66 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -23,7 +23,7 @@ CFBundleShortVersionString 1.10.0 CFBundleVersion - 119 + 120 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index 8cf3f041a..278c953a9 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 119 + 120 From b3f1e486a8ba59e052ff2da909bd24ee058fa35a Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 3 Jul 2018 15:15:40 -0400 Subject: [PATCH 03/20] Bump Android build number to 120 (#1869) --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 1015f3d89..33a2beda3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -113,7 +113,7 @@ android { applicationId "com.mattermost.rnbeta" minSdkVersion 21 targetSdkVersion 23 - versionCode 119 + versionCode 120 versionName "1.10.0" ndk { abiFilters "armeabi-v7a", "x86" From 3633206204de0f6e4bda1c19937c9d866c1a62e6 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Tue, 3 Jul 2018 18:55:01 -0400 Subject: [PATCH 04/20] Remember announcement banner dismissal after app closes (#1867) --- app/store/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/store/index.js b/app/store/index.js index ad8860e20..c3dc08c38 100644 --- a/app/store/index.js +++ b/app/store/index.js @@ -51,7 +51,7 @@ const setTransforms = [ export default function configureAppStore(initialState) { const viewsBlackListFilter = createBlacklistFilter( 'views', - ['announcement', 'extension', 'login', 'root'] + ['extension', 'login', 'root'] ); const typingBlackListFilter = createBlacklistFilter( From 02c89faeada9c643dee3b74f7f460de63b367bc8 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Tue, 3 Jul 2018 19:02:41 -0400 Subject: [PATCH 05/20] MM-11116 Work around setNativeProps not working for TextInputs (#1873) --- app/components/quick_text_input.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/app/components/quick_text_input.js b/app/components/quick_text_input.js index 0c82ad833..e576cb4eb 100644 --- a/app/components/quick_text_input.js +++ b/app/components/quick_text_input.js @@ -11,6 +11,10 @@ import {TextInput} from 'react-native'; // We're using this in place of a connected TextInput due to changes made in RN v0.54 // that break input in Chinese and Japanese when using a connected TextInput. See // https://github.com/facebook/react-native/issues/18403 for more information. +// +// In addition to that, there's also an ugly hack to change the key on the TextInput +// when this is triggered because that same version made setNativeProps work inconsistently. +// See https://github.com/facebook/react-native/issues/18272 for more information on that. export default class QuickTextInput extends React.PureComponent { static propTypes = { @@ -31,16 +35,37 @@ export default class QuickTextInput extends React.PureComponent { super(props); this.storedValue = props.value; + + this.state = { + key: 0, + }; } componentDidMount() { this.updateInputFromProps(); } - componentDidUpdate() { + UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line camelcase + // This will force the base TextInput to re-render if the value is changing + // from something other than the user typing in it. This does however cause + // the TextInput to flicker when this happens. + if (nextProps.value !== this.storedValue) { + this.setState({ + key: this.state.key + 1, + }); + } + + this.hadFocus = this.input.isFocused(); + } + + componentDidUpdate(prevProps, prevState) { if (this.props.value !== this.storedValue) { this.updateInputFromProps(); } + + if (prevState.key !== this.state.key && this.hadFocus) { + this.input.focus(); + } } updateInputFromProps = () => { @@ -82,6 +107,7 @@ export default class QuickTextInput extends React.PureComponent { return ( From 40c7d9cb07880d47611e6db9e1952ef2074adc13 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 3 Jul 2018 19:03:58 -0400 Subject: [PATCH 06/20] Add defensive code to prevent extension from not working (#1872) --- app/components/quick_text_input.js | 4 ++++ share_extension/ios/error_message.js | 1 - share_extension/ios/extension.js | 3 ++- share_extension/ios/extension_post.js | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/components/quick_text_input.js b/app/components/quick_text_input.js index e576cb4eb..94900ac9d 100644 --- a/app/components/quick_text_input.js +++ b/app/components/quick_text_input.js @@ -77,6 +77,10 @@ export default class QuickTextInput extends React.PureComponent { this.storedValue = this.props.value; } + isFocused() { + return this.input.isFocused(); + } + focus() { this.input.focus(); } diff --git a/share_extension/ios/error_message.js b/share_extension/ios/error_message.js index 5bc28bac0..bdadbb996 100644 --- a/share_extension/ios/error_message.js +++ b/share_extension/ios/error_message.js @@ -43,7 +43,6 @@ export default function errorMessage(props) { errorMessage.propTypes = { close: PropTypes.func.isRequired, - formatMessage: PropTypes.func.isRequired, }; const getStyleSheet = makeStyleSheetFromTheme((theme) => { diff --git a/share_extension/ios/extension.js b/share_extension/ios/extension.js index d82a6ce60..e6c4e8936 100644 --- a/share_extension/ios/extension.js +++ b/share_extension/ios/extension.js @@ -7,6 +7,7 @@ import {Animated, Dimensions, NavigatorIOS, StyleSheet, View} from 'react-native import {Preferences} from 'mattermost-redux/constants'; +import initialState from 'app/initial_state'; import mattermostBucket from 'app/mattermost_bucket'; import ExtensionPost from './extension_post'; @@ -32,7 +33,7 @@ export default class SharedApp extends PureComponent { }; mattermostBucket.readFromFile('entities', props.appGroupId).then((value) => { - this.entities = value; + this.entities = value || initialState; this.setState({init: true}); }); } diff --git a/share_extension/ios/extension_post.js b/share_extension/ios/extension_post.js index 532d1a292..6d697a0dc 100644 --- a/share_extension/ios/extension_post.js +++ b/share_extension/ios/extension_post.js @@ -232,7 +232,7 @@ export default class ExtensionPost extends PureComponent { let totalSize = 0; let exceededSize = false; - if (channel.type === General.GM_CHANNEL || channel.type === General.DM_CHANNEL) { + if (channel && (channel.type === General.GM_CHANNEL || channel.type === General.DM_CHANNEL)) { channel = getChannel({entities}, channel.id); } @@ -590,7 +590,7 @@ export default class ExtensionPost extends PureComponent { const {currentUserId} = entities.users; // If no text and no files do nothing - if (!value && !files.length) { + if ((!value && !files.length) || !channel) { return; } From 4fef264e248734440833820d7aa72267f1eb8d20 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 3 Jul 2018 19:39:18 -0400 Subject: [PATCH 07/20] Bump iOS build number to 121 (#1874) --- ios/Mattermost.xcodeproj/project.pbxproj | 4 ++-- ios/Mattermost/Info.plist | 2 +- ios/MattermostShare/Info.plist | 2 +- ios/MattermostTests/Info.plist | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index a1cfb7e79..849780a47 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -2532,7 +2532,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 120; + CURRENT_PROJECT_VERSION = 121; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2582,7 +2582,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 120; + CURRENT_PROJECT_VERSION = 121; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index a13013223..cefafed16 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 120 + 121 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index 5fb661e66..14cd89a01 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -23,7 +23,7 @@ CFBundleShortVersionString 1.10.0 CFBundleVersion - 120 + 121 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index 278c953a9..328bb4339 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 120 + 121 From 1b549d3a518051d4847d991ef431fe1733420b2f Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 3 Jul 2018 19:44:54 -0400 Subject: [PATCH 08/20] Bump Android build number to 121 (#1875) --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 33a2beda3..1918a1df4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -113,7 +113,7 @@ android { applicationId "com.mattermost.rnbeta" minSdkVersion 21 targetSdkVersion 23 - versionCode 120 + versionCode 121 versionName "1.10.0" ndk { abiFilters "armeabi-v7a", "x86" From d9eb29a7248fd04610f8872d91829b2954b87938 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Wed, 4 Jul 2018 08:44:33 -0400 Subject: [PATCH 09/20] MM-10826 Show error when viewing permalink for locally deleted post (#1870) * MM-10826 Show error when viewing permalink for locally deleted post * Don't hide current user's deleted posts --- app/screens/permalink/index.js | 9 ++++++--- app/selectors/post_list.js | 4 ---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/screens/permalink/index.js b/app/screens/permalink/index.js index 9b34d1af7..7480e13da 100644 --- a/app/screens/permalink/index.js +++ b/app/screens/permalink/index.js @@ -6,6 +6,7 @@ import {connect} from 'react-redux'; import {getChannel as getChannelAction, joinChannel, markChannelAsRead, markChannelAsViewed} from 'mattermost-redux/actions/channels'; import {getPostsAfter, getPostsBefore, getPostThread, selectPost} from 'mattermost-redux/actions/posts'; +import {Posts} from 'mattermost-redux/constants'; import {makeGetChannel, getMyChannelMemberships} from 'mattermost-redux/selectors/entities/channels'; import {makeGetPostIdsAroundPost, getPost} from 'mattermost-redux/selectors/entities/posts'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; @@ -29,11 +30,13 @@ function makeMapStateToProps() { return function mapStateToProps(state) { const {currentFocusedPostId} = state.entities.posts; const post = getPost(state, currentFocusedPostId); - const channel = post ? getChannel(state, {id: post.channel_id}) : null; + + let channel; let postIds; - if (channel && channel.id) { - postIds = getPostIdsAroundPost(state, currentFocusedPostId, channel.id, { + if (post && post.delete_at === 0 && post.state !== Posts.POST_DELETED) { + channel = getChannel(state, {id: post.channel_id}); + postIds = getPostIdsAroundPost(state, currentFocusedPostId, post.channel_id, { postsBeforeCount: 10, postsAfterCount: 10, }); diff --git a/app/selectors/post_list.js b/app/selectors/post_list.js index dd53aab91..b138aa6f8 100644 --- a/app/selectors/post_list.js +++ b/app/selectors/post_list.js @@ -47,10 +47,6 @@ export function makePreparePostIdsForPostList() { continue; } - if (post.state === Posts.POST_DELETED && post.user_id === currentUser.id) { - continue; - } - // Filter out join/leave messages if necessary if (shouldFilterJoinLeavePost(post, showJoinLeave, currentUser.username)) { continue; From 939040c3ccb89999b8bd2191b9a3032678ad8b57 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Wed, 4 Jul 2018 09:38:14 -0400 Subject: [PATCH 10/20] Add setNativeProps to QuickTextInput (#1881) --- app/components/quick_text_input.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/components/quick_text_input.js b/app/components/quick_text_input.js index 94900ac9d..2cac479a0 100644 --- a/app/components/quick_text_input.js +++ b/app/components/quick_text_input.js @@ -77,6 +77,10 @@ export default class QuickTextInput extends React.PureComponent { this.storedValue = this.props.value; } + setNativeProps(nativeProps) { + this.input.setNativeProps(nativeProps); + } + isFocused() { return this.input.isFocused(); } From 5d579cdfcf4fcef7bbb31d1900ef8dd474973ec0 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 4 Jul 2018 10:40:56 -0400 Subject: [PATCH 11/20] Bold unread channels in jump to channel (#1879) --- .../main/channels_list/filtered_list/filtered_list.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js b/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js index 9632a6983..378cc5cf0 100644 --- a/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js +++ b/app/components/sidebars/main/channels_list/filtered_list/filtered_list.js @@ -114,7 +114,7 @@ class FilteredList extends Component { channelId={channel.id} channel={channel} isSearchResult={true} - isUnread={false} + isUnread={channel.isUnread} mentions={0} onSelectChannel={this.onSelectChannel} /> @@ -173,8 +173,11 @@ class FilteredList extends Component { buildUnreadChannelsForSearch = (props, term) => { const {unreadChannels} = props.channels; - return this.filterChannels(unreadChannels, term); - } + return this.filterChannels(unreadChannels, term).map((item) => { + item.isUnread = true; + return item; + }); + }; buildCurrentDMSForSearch = (props, term) => { const {channels, teammateNameDisplay, profiles, statuses, pastDirectMessages, groupChannelMemberDetails} = props; From 0e692c208873f9722cdc704c9e6104b36fe86a9f Mon Sep 17 00:00:00 2001 From: Saturnino Abril Date: Wed, 4 Jul 2018 23:06:45 +0800 Subject: [PATCH 12/20] [MM-11004] Fix continuous channel loading (#1803) * fix continuous channel loading * revert change on limiting number of pages requested --- app/actions/views/channel.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js index a37e4518e..f1e6603e4 100644 --- a/app/actions/views/channel.js +++ b/app/actions/views/channel.js @@ -511,17 +511,19 @@ export function increasePostVisibility(channelId, focusedPostId) { }]; const posts = result.data; + let hasMorePost = false; if (posts) { + hasMorePost = posts.order.length >= pageSize; + // make sure to increment the posts visibility // only if we got results actions.push(doIncreasePostVisibility(channelId)); - actions.push(setLoadMorePostsVisible(posts.order.length >= pageSize)); + actions.push(setLoadMorePostsVisible(hasMorePost)); } dispatch(batchActions(actions)); - - return Boolean(posts); + return hasMorePost; }; } From e9cbd59d25ee57f34e3649ec670b0449702d3f56 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 4 Jul 2018 10:41:47 -0400 Subject: [PATCH 13/20] Bump iOS build number to 122 (#1882) --- ios/Mattermost.xcodeproj/project.pbxproj | 4 ++-- ios/Mattermost/Info.plist | 2 +- ios/MattermostShare/Info.plist | 2 +- ios/MattermostTests/Info.plist | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 849780a47..23507ce2c 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -2532,7 +2532,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 121; + CURRENT_PROJECT_VERSION = 122; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2582,7 +2582,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 121; + CURRENT_PROJECT_VERSION = 122; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index cefafed16..10ca7c0c0 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 121 + 122 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index 14cd89a01..9ee30cd77 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -23,7 +23,7 @@ CFBundleShortVersionString 1.10.0 CFBundleVersion - 121 + 122 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index 328bb4339..26f4e17df 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 121 + 122 From ddbca980263a35743e72f92ddeb55ec88b51e13c Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 4 Jul 2018 10:50:39 -0400 Subject: [PATCH 14/20] Bump Android build number to 122 (#1883) --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 1918a1df4..8a2647fed 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -113,7 +113,7 @@ android { applicationId "com.mattermost.rnbeta" minSdkVersion 21 targetSdkVersion 23 - versionCode 121 + versionCode 122 versionName "1.10.0" ndk { abiFilters "armeabi-v7a", "x86" From 76a82aa627759801003bf8ce76260594399d3b0a Mon Sep 17 00:00:00 2001 From: Alan P Date: Wed, 4 Jul 2018 13:21:08 -0300 Subject: [PATCH 15/20] fix pt-BR availability (#1884) pt-BR i18n could not be set as there was no `case` matching it properly. --- app/i18n/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/i18n/index.js b/app/i18n/index.js index 618fd49fb..c9f91b3ef 100644 --- a/app/i18n/index.js +++ b/app/i18n/index.js @@ -49,7 +49,7 @@ function loadTranslation(locale) { TRANSLATIONS.pl = require('assets/i18n/pl.json'); localeData = require('react-intl/locale-data/pl'); break; - case 'pt-BT': + case 'pt-BR': TRANSLATIONS[locale] = require('assets/i18n/pt-BR.json'); localeData = require('react-intl/locale-data/pt'); break; From da8b5e92cce8114334a6eb3b41443734c36874c0 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Wed, 4 Jul 2018 13:15:03 -0400 Subject: [PATCH 16/20] MM-10012 Added Markdown rendering to expanded announcement banner (#1861) * MM-10012 Added Markdown rendering to expanded announcement banner * Updated snapshots * Re-added dismiss button to expanded announcement banner * Updated 'Don't show again' button style' --- .../announcement_banner.test.js.snap | 60 ++++++--- .../announcement_banner.js | 49 ++++---- .../announcement_banner.test.js | 18 +-- app/components/announcement_banner/index.js | 16 +-- app/components/channel_link/channel_link.js | 5 + app/components/markdown/markdown.js | 2 + app/components/remove_markdown.js | 75 +++++++++++ .../channel_post_list/channel_post_list.js | 2 +- .../expanded_announcement_banner.js | 116 ++++++++++++++++++ .../expanded_announcement_banner/index.js | 32 +++++ app/screens/index.js | 1 + assets/base/i18n/en.json | 1 - 12 files changed, 297 insertions(+), 80 deletions(-) create mode 100644 app/components/remove_markdown.js create mode 100644 app/screens/expanded_announcement_banner/expanded_announcement_banner.js create mode 100644 app/screens/expanded_announcement_banner/index.js diff --git a/app/components/announcement_banner/__snapshots__/announcement_banner.test.js.snap b/app/components/announcement_banner/__snapshots__/announcement_banner.test.js.snap index 305e02dca..9163f2a2e 100644 --- a/app/components/announcement_banner/__snapshots__/announcement_banner.test.js.snap +++ b/app/components/announcement_banner/__snapshots__/announcement_banner.test.js.snap @@ -5,12 +5,6 @@ ShallowWrapper { "length": 1, Symbol(enzyme.__root__): [Circular], Symbol(enzyme.__unrendered__): - Banner Text + - Banner Text + , , "ellipsizeMode": "tail", "numberOfLines": 1, "style": Array [ @@ -147,7 +147,17 @@ ShallowWrapper { ], }, "ref": null, - "rendered": "Banner Text", + "rendered": Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "value": "Banner Text", + }, + "ref": null, + "rendered": null, + "type": [Function], + }, "type": [Function], }, Object { @@ -204,7 +214,9 @@ ShallowWrapper { ] } > - Banner Text + - Banner Text + , , "ellipsizeMode": "tail", "numberOfLines": 1, "style": Array [ @@ -293,7 +309,17 @@ ShallowWrapper { ], }, "ref": null, - "rendered": "Banner Text", + "rendered": Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "value": "Banner Text", + }, + "ref": null, + "rendered": null, + "type": [Function], + }, "type": [Function], }, Object { @@ -331,12 +357,6 @@ ShallowWrapper { "length": 1, Symbol(enzyme.__root__): [Circular], Symbol(enzyme.__unrendered__): { - const {actions, bannerText} = this.props; - actions.dismissBanner(bannerText); - }; - handlePress = () => { - const {formatMessage} = this.context.intl; - const options = [{ - text: formatMessage({id: 'mobile.announcement_banner.ok', defaultMessage: 'OK'}), - }]; + const {navigator, theme} = this.props; - if (this.props.allowDismissal) { - options.push({ - text: formatMessage({id: 'mobile.announcement_banner.dismiss', defaultMessage: 'Dismiss'}), - onPress: this.handleDismiss, - }); - } - - Alert.alert( - formatMessage({id: 'mobile.announcement_banner.title', defaultMessage: 'Announcement'}), - this.props.bannerText, - options, - {cancelable: false} - ); + navigator.push({ + screen: 'ExpandedAnnouncementBanner', + title: this.context.intl.formatMessage({ + id: 'mobile.announcement_banner.title', + defaultMessage: 'Announcement', + }), + animated: true, + backButtonTitle: '', + navigatorStyle: { + navBarTextColor: theme.sidebarHeaderTextColor, + navBarBackgroundColor: theme.sidebarHeaderBg, + navBarButtonColor: theme.sidebarHeaderTextColor, + screenBackgroundColor: theme.centerChannelBg, + }, + }); }; toggleBanner = (show = true) => { @@ -120,7 +113,7 @@ export default class AnnouncementBanner extends PureComponent { numberOfLines={1} style={[style.bannerText, bannerTextStyle]} > - {bannerText} + { const baseProps = { - actions: { - dismissBanner: jest.fn(), - }, - allowDismissal: true, bannerColor: '#ddd', bannerDismissed: false, bannerEnabled: true, @@ -33,16 +29,4 @@ describe('AnnouncementBanner', () => { wrapper.setProps({bannerEnabled: false}); expect(wrapper).toMatchSnapshot(); }); - - test('should call actions.dismissBanner on handleDismiss', () => { - const actions = {dismissBanner: jest.fn()}; - const props = {...baseProps, actions}; - const wrapper = shallow( - - ); - - wrapper.instance().handleDismiss(); - expect(actions.dismissBanner).toHaveBeenCalledTimes(1); - expect(actions.dismissBanner).toHaveBeenCalledWith(props.bannerText); - }); -}); \ No newline at end of file +}); diff --git a/app/components/announcement_banner/index.js b/app/components/announcement_banner/index.js index 7b27f2ae7..f560c461d 100644 --- a/app/components/announcement_banner/index.js +++ b/app/components/announcement_banner/index.js @@ -1,12 +1,10 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; - -import {dismissBanner} from 'app/actions/views/announcement'; +import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import AnnouncementBanner from './announcement_banner'; @@ -16,21 +14,13 @@ function mapStateToProps(state) { const {announcement} = state.views; return { - allowDismissal: config.AllowBannerDismissal === 'true', bannerColor: config.BannerColor, bannerDismissed: config.BannerText === announcement, bannerEnabled: config.EnableBanner === 'true' && license.IsLicensed === 'true', bannerText: config.BannerText, bannerTextColor: config.BannerTextColor || '#000', + theme: getTheme(state), }; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - dismissBanner, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(AnnouncementBanner); +export default connect(mapStateToProps)(AnnouncementBanner); diff --git a/app/components/channel_link/channel_link.js b/app/components/channel_link/channel_link.js index 89f236591..f06dfc600 100644 --- a/app/components/channel_link/channel_link.js +++ b/app/components/channel_link/channel_link.js @@ -11,6 +11,7 @@ export default class ChannelLink extends React.PureComponent { static propTypes = { channelName: PropTypes.string.isRequired, linkStyle: CustomPropTypes.Style, + onChannelLinkPress: PropTypes.func, textStyle: CustomPropTypes.Style, channelsByName: PropTypes.object.isRequired, actions: PropTypes.shape({ @@ -57,6 +58,10 @@ export default class ChannelLink extends React.PureComponent { handlePress = () => { this.props.actions.setChannelDisplayName(this.state.channel.display_name); this.props.actions.handleSelectChannel(this.state.channel.id); + + if (this.props.onChannelLinkPress) { + this.props.onChannelLinkPress(this.state.channel); + } } render() { diff --git a/app/components/markdown/markdown.js b/app/components/markdown/markdown.js index db91aef76..204e6ab4f 100644 --- a/app/components/markdown/markdown.js +++ b/app/components/markdown/markdown.js @@ -39,6 +39,7 @@ export default class Markdown extends PureComponent { isEdited: PropTypes.bool, isSearchResult: PropTypes.bool, navigator: PropTypes.object.isRequired, + onChannelLinkPress: PropTypes.func, onLongPress: PropTypes.func, onPermalinkPress: PropTypes.func, onPostPress: PropTypes.func, @@ -194,6 +195,7 @@ export default class Markdown extends PureComponent { ); diff --git a/app/components/remove_markdown.js b/app/components/remove_markdown.js new file mode 100644 index 000000000..eb4d2562d --- /dev/null +++ b/app/components/remove_markdown.js @@ -0,0 +1,75 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Parser} from 'commonmark'; +import Renderer from 'commonmark-react-renderer'; +import PropTypes from 'prop-types'; +import React from 'react'; +import {Text} from 'react-native'; + +export default class RemoveMarkdown extends React.PureComponent { + static propTypes = { + value: PropTypes.string.isRequired, + }; + + constructor(props) { + super(props); + + this.parser = this.createParser(); + this.renderer = this.createRenderer(); + } + + createParser = () => { + return new Parser(); + }; + + createRenderer = () => { + return new Renderer({ + renderers: { + text: this.renderText, + + emph: Renderer.forwardChildren, + strong: Renderer.forwardChildren, + del: Renderer.forwardChildren, + code: Renderer.forwardChildren, + link: Renderer.forwardChildren, + atMention: Renderer.forwardChildren, + channelLink: Renderer.forwardChildren, + emoji: this.renderNull, + + paragraph: Renderer.forwardChildren, + heading: Renderer.forwardChildren, + codeBlock: this.renderNull, + blockQuote: this.renderNull, + + list: this.renderNull, + item: this.renderNull, + + hardBreak: this.renderNull, + thematicBreak: this.renderNull, + softBreak: this.renderNull, + + htmlBlock: this.renderNull, + htmlInline: this.renderNull, + + table: this.renderNull, + table_row: this.renderNull, + table_cell: this.renderNull, + }, + }); + }; + + renderText = ({literal}) => { + return {literal}; + }; + + renderNull = () => { + return null; + }; + + render() { + const ast = this.parser.parse(this.props.value); + + return {this.renderer.render(ast)}; + } +} diff --git a/app/screens/channel/channel_post_list/channel_post_list.js b/app/screens/channel/channel_post_list/channel_post_list.js index 360ee666b..a79f425c2 100644 --- a/app/screens/channel/channel_post_list/channel_post_list.js +++ b/app/screens/channel/channel_post_list/channel_post_list.js @@ -228,7 +228,7 @@ export default class ChannelPostList extends PureComponent { return ( {component} - + ); diff --git a/app/screens/expanded_announcement_banner/expanded_announcement_banner.js b/app/screens/expanded_announcement_banner/expanded_announcement_banner.js new file mode 100644 index 000000000..e47b3bcb6 --- /dev/null +++ b/app/screens/expanded_announcement_banner/expanded_announcement_banner.js @@ -0,0 +1,116 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import PropTypes from 'prop-types'; +import React from 'react'; +import {ScrollView, View} from 'react-native'; +import Button from 'react-native-button'; + +import FormattedText from 'app/components/formatted_text'; +import Markdown from 'app/components/markdown'; + +import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown'; +import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; + +export default class ExpandedAnnouncementBanner extends React.PureComponent { + static propTypes = { + actions: PropTypes.shape({ + dismissBanner: PropTypes.func.isRequired, + }).isRequired, + allowDismissal: PropTypes.bool.isRequired, + bannerText: PropTypes.string.isRequired, + navigator: PropTypes.object.isRequired, + theme: PropTypes.object.isRequired, + } + + close = () => { + this.props.navigator.pop(); + }; + + dismissBanner = () => { + this.props.actions.dismissBanner(this.props.bannerText); + + this.close(); + }; + + handleChannelLinkPress = () => { + this.close(); + }; + + render() { + const style = getStyleSheet(this.props.theme); + + let dismissButton = null; + if (this.props.allowDismissal) { + dismissButton = ( + + + + ); + } + + return ( + + + + + {dismissButton} + + ); + } +} + +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return { + container: { + flex: 1, + }, + scrollContainer: { + flex: 1, + }, + textContainer: { + padding: 15, + }, + baseTextStyle: { + color: theme.centerChannelColor, + fontSize: 15, + lineHeight: 20, + }, + dismissContainer: { + borderTopColor: changeOpacity(theme.centerChannelColor, 0.2), + borderTopWidth: 1, + padding: 10, + }, + dismissButton: { + alignSelf: 'stretch', + backgroundColor: theme.sidebarHeaderBg, + borderRadius: 3, + padding: 15, + }, + dismissButtonText: { + color: theme.sidebarHeaderTextColor, + fontSize: 15, + fontWeight: '600', + textAlign: 'center', + }, + }; +}); diff --git a/app/screens/expanded_announcement_banner/index.js b/app/screens/expanded_announcement_banner/index.js new file mode 100644 index 000000000..83837c9c3 --- /dev/null +++ b/app/screens/expanded_announcement_banner/index.js @@ -0,0 +1,32 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {bindActionCreators} from 'redux'; +import {connect} from 'react-redux'; + +import {getConfig} from 'mattermost-redux/selectors/entities/general'; +import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; + +import {dismissBanner} from 'app/actions/views/announcement'; + +import ExpandedAnnouncementBanner from './expanded_announcement_banner'; + +function mapStateToProps(state) { + const config = getConfig(state); + + return { + allowDismissal: config.AllowBannerDismissal === 'true', + bannerText: config.BannerText, + theme: getTheme(state), + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + dismissBanner, + }, dispatch), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(ExpandedAnnouncementBanner); diff --git a/app/screens/index.js b/app/screens/index.js index 37e1e6a89..eef346b2d 100644 --- a/app/screens/index.js +++ b/app/screens/index.js @@ -26,6 +26,7 @@ export function registerScreens(store, Provider) { Navigation.registerComponent('EditPost', () => wrapWithContextProvider(require('app/screens/edit_post').default), store, Provider); Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(require('app/screens/edit_profile').default), store, Provider); Navigation.registerComponent('Entry', () => Entry, store, Provider); + Navigation.registerComponent('ExpandedAnnouncementBanner', () => wrapWithContextProvider(require('app/screens/expanded_announcement_banner').default), store, Provider); Navigation.registerComponent('FlaggedPosts', () => wrapWithContextProvider(require('app/screens/flagged_posts').default), store, Provider); Navigation.registerComponent('ImagePreview', () => wrapWithContextProvider(require('app/screens/image_preview').default), store, Provider); Navigation.registerComponent('Login', () => wrapWithContextProvider(require('app/screens/login').default), store, Provider); diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 01476101e..481edc80f 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -2307,7 +2307,6 @@ "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.announcement_banner.dismiss": "Dismiss", - "mobile.announcement_banner.ok": "OK", "mobile.announcement_banner.title": "Announcement", "mobile.channel.markAsRead": "Mark As Read", "mobile.channel_drawer.search": "Jump to...", From 665bb862a85300f7dbf91c945e542935f5335134 Mon Sep 17 00:00:00 2001 From: Saturnino Abril Date: Thu, 5 Jul 2018 01:15:21 +0800 Subject: [PATCH 17/20] on search success and other user/s are selected already, remove current user first before profile match filtering (#1876) --- app/screens/more_dms/more_dms.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/screens/more_dms/more_dms.js b/app/screens/more_dms/more_dms.js index fda9798e3..b091b88fd 100644 --- a/app/screens/more_dms/more_dms.js +++ b/app/screens/more_dms/more_dms.js @@ -95,10 +95,17 @@ class MoreDirectMessages extends PureComponent { nextProps.getRequest.status === RequestStatus.SUCCESS) { const profiles = this.sliceProfiles(nextProps.profiles); this.setState({profiles, showNoResults: true}); - } else if (this.state.searching && - nextProps.searchRequest.status === RequestStatus.SUCCESS) { + } else if ( + this.state.searching && + nextProps.searchRequest.status === RequestStatus.SUCCESS + ) { + let profiles = nextProps.profiles; + if (this.state.selectedCount > 0) { + profiles = this.removeCurrentUserFromProfiles(profiles); + } + const exactMatches = []; - let results = filterProfilesMatchingTerm(nextProps.profiles, this.state.term).filter((p) => { + const results = filterProfilesMatchingTerm(profiles, this.state.term).filter((p) => { if (p.username === this.state.term || p.username.startsWith(this.state.term)) { exactMatches.push(p); return false; @@ -107,10 +114,6 @@ class MoreDirectMessages extends PureComponent { return true; }); - if (this.state.selectedCount > 0) { - results = this.removeCurrentUserFromProfiles(results); - } - this.setState({profiles: [...exactMatches, ...results], showNoResults: true}); } } From e4a0a4b4ec60375830096677c6163bb00ea11867 Mon Sep 17 00:00:00 2001 From: Saturnino Abril Date: Thu, 5 Jul 2018 01:17:59 +0800 Subject: [PATCH 18/20] [MM-10215] Fix saving of email notification props when email batching is enabled (#1654) * fix saving of email notification props when email batching is enabled * fix email notification settings for android * make screen for Android email notification * update per comment * leave as is "...email.everyXMinutes", and fix prop type error on emailInterval * add "mobile." prefix to newly introduced translation ID --- .../radio_button/radio_button_group.js | 42 ++- .../notification_settings.js | 291 ++-------------- ...cation_settings_email.android.test.js.snap | 128 +++++++ ...tification_settings_email.ios.test.js.snap | 71 ++++ .../notification_settings_email/index.js | 36 +- .../notification_settings_email.android.js | 321 ++++++++++++++++++ ...otification_settings_email.android.test.js | 102 ++++++ ....js => notification_settings_email.ios.js} | 141 ++------ .../notification_settings_email.ios.test.js | 89 +++++ .../notification_settings_email_base.js | 99 ++++++ .../notification_settings_mentions.android.js | 4 +- assets/base/i18n/en.json | 1 + 12 files changed, 909 insertions(+), 416 deletions(-) create mode 100644 app/screens/settings/notification_settings_email/__snapshots__/notification_settings_email.android.test.js.snap create mode 100644 app/screens/settings/notification_settings_email/__snapshots__/notification_settings_email.ios.test.js.snap create mode 100644 app/screens/settings/notification_settings_email/notification_settings_email.android.js create mode 100644 app/screens/settings/notification_settings_email/notification_settings_email.android.test.js rename app/screens/settings/notification_settings_email/{notification_settings_email.js => notification_settings_email.ios.js} (53%) create mode 100644 app/screens/settings/notification_settings_email/notification_settings_email.ios.test.js create mode 100644 app/screens/settings/notification_settings_email/notification_settings_email_base.js diff --git a/app/components/radio_button/radio_button_group.js b/app/components/radio_button/radio_button_group.js index 92728608b..b7dd3bda2 100644 --- a/app/components/radio_button/radio_button_group.js +++ b/app/components/radio_button/radio_button_group.js @@ -17,34 +17,30 @@ export default class RadioButtonGroup extends PureComponent { options: [] }; - state = {}; - constructor(props) { super(props); - this.selected = null; - if (this.props.options.length) { - this.props.options.forEach((option) => { - const { - value, - checked, - } = option; + this.state = { + selected: this.getSelectedValue(props.options), + }; + } - if (!this.state.selected && checked) { - this.selected = value; - } - }); + componentWillReceiveProps(nextProps) { + if (this.props.options !== nextProps.options) { + this.setState({selected: this.getSelectedValue(nextProps.options)}); + } + } + + getSelectedValue = (options = []) => { + let selected; + for (const option in options) { + if (option.checked) { + selected = option.value; + break; + } } - this.state = {selected: this.selected}; - } - - get value() { - return this.state.selected; - } - - set value(value) { - this.onChange(value); + return selected; } onChange = (value) => { @@ -81,7 +77,7 @@ export default class RadioButtonGroup extends PureComponent { label={label} disabled={disabled} onCheck={this.onChange} - checked={this.state.selected && value === this.state.selected} + checked={option.checked} /> ); }); diff --git a/app/screens/settings/notification_settings/notification_settings.js b/app/screens/settings/notification_settings/notification_settings.js index 4b3a5ad90..fe0c5c634 100644 --- a/app/screens/settings/notification_settings/notification_settings.js +++ b/app/screens/settings/notification_settings/notification_settings.js @@ -6,19 +6,14 @@ import PropTypes from 'prop-types'; import {injectIntl, intlShape} from 'react-intl'; import { Alert, - Modal, Platform, ScrollView, - TouchableOpacity, View, } from 'react-native'; import deepEqual from 'deep-equal'; -import {General, Preferences, RequestStatus} from 'mattermost-redux/constants'; -import {getPreferencesByCategory} from 'mattermost-redux/utils/preference_utils'; +import {General, RequestStatus} from 'mattermost-redux/constants'; -import FormattedText from 'app/components/formatted_text'; -import RadioButtonGroup from 'app/components/radio_button'; import StatusBar from 'app/components/status_bar'; import NotificationPreferences from 'app/notification_preferences'; import SettingsItem from 'app/screens/settings/settings_item'; @@ -31,10 +26,8 @@ class NotificationSettings extends PureComponent { actions: PropTypes.shape({ handleUpdateUserNotifyProps: PropTypes.func.isRequired, }), - config: PropTypes.object.isRequired, currentUser: PropTypes.object.isRequired, intl: intlShape.isRequired, - myPreferences: PropTypes.object.isRequired, navigator: PropTypes.object, theme: PropTypes.object.isRequired, updateMeRequest: PropTypes.object.isRequired, @@ -42,10 +35,6 @@ class NotificationSettings extends PureComponent { enableAutoResponder: PropTypes.bool.isRequired, }; - state = { - showEmailNotificationsModal: false, - }; - componentWillReceiveProps(nextProps) { if (this.props.theme !== nextProps.theme) { setNavigatorStyles(this.props.navigator, nextProps.theme); @@ -94,30 +83,25 @@ class NotificationSettings extends PureComponent { }; goToNotificationSettingsEmail = () => { - if (Platform.OS === 'ios') { - const {currentUser, intl, navigator, theme} = this.props; - navigator.push({ - backButtonTitle: '', - screen: 'NotificationSettingsEmail', - title: intl.formatMessage({ - id: 'mobile.notification_settings.email_title', - defaultMessage: 'Email Notifications', - }), - animated: true, - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - passProps: { - currentUser, - onBack: this.saveNotificationProps, - }, - }); - } else { - this.setState({showEmailNotificationsModal: true}); - } + const {currentUser, intl, navigator, theme} = this.props; + navigator.push({ + backButtonTitle: '', + screen: 'NotificationSettingsEmail', + title: intl.formatMessage({ + id: 'mobile.notification_settings.email_title', + defaultMessage: 'Email Notifications', + }), + animated: true, + navigatorStyle: { + navBarTextColor: theme.sidebarHeaderTextColor, + navBarBackgroundColor: theme.sidebarHeaderBg, + navBarButtonColor: theme.sidebarHeaderTextColor, + screenBackgroundColor: theme.centerChannelBg, + }, + passProps: { + currentUserId: currentUser.id, + }, + }); }; goToNotificationSettingsMentions = () => { @@ -168,34 +152,6 @@ class NotificationSettings extends PureComponent { }); }; - setEmailNotifications = (emailSetting) => { - this.setState({emailSetting}); - }; - - saveEmailNotifications = () => { - const {config, currentUser} = this.props; - const {emailSetting} = this.state; - - this.setState({showEmailNotificationsModal: false}); - - if (emailSetting) { - let email = emailSetting; - let interval; - - const emailBatchingEnabled = config.EnableEmailBatching === 'true'; - if (emailBatchingEnabled && emailSetting !== 'false') { - interval = emailSetting; - email = 'true'; - } - - this.saveNotificationProps({ - ...getNotificationProps(currentUser), - email, - interval, - }); - } - }; - saveNotificationProps = (notifyProps) => { const {currentUser} = this.props; const {user_id: userId} = notifyProps; @@ -204,10 +160,6 @@ class NotificationSettings extends PureComponent { user_id: userId, }; - if (notifyProps.interval) { - previousProps.interval = notifyProps.interval; - } - if (!deepEqual(previousProps, notifyProps)) { this.props.actions.handleUpdateUserNotifyProps(notifyProps); } @@ -247,154 +199,6 @@ class NotificationSettings extends PureComponent { } }; - renderEmailNotificationSettings = (style) => { - if (Platform.OS === 'ios') { - return null; - } - - const {config, currentUser, intl, myPreferences} = this.props; - const notifyProps = getNotificationProps(currentUser); - const sendEmailNotifications = config.SendEmailNotifications === 'true'; - const emailBatchingEnabled = sendEmailNotifications && config.EnableEmailBatching === 'true'; - - const never = notifyProps.email === 'false'; - let sendImmediatley = notifyProps.email === 'true'; - let sendImmediatleyValue = 'true'; - let fifteenMinutes; - let hourly; - let interval; - - if (emailBatchingEnabled) { - const emailPreferences = getPreferencesByCategory(myPreferences, Preferences.CATEGORY_NOTIFICATIONS); - if (emailPreferences.size) { - interval = emailPreferences.get(Preferences.EMAIL_INTERVAL).value; - } - } - - if (emailBatchingEnabled && notifyProps.email !== 'false') { - sendImmediatley = interval === Preferences.INTERVAL_IMMEDIATE.toString(); - fifteenMinutes = interval === Preferences.INTERVAL_FIFTEEN_MINUTES.toString(); - hourly = interval === Preferences.INTERVAL_HOUR.toString(); - sendImmediatleyValue = Preferences.INTERVAL_IMMEDIATE.toString(); - } - - let helpText; - if (sendEmailNotifications) { - helpText = ( - - ); - } - - const emailOptions = [{ - label: intl.formatMessage({ - id: 'user.settings.notifications.email.immediately', - defaultMessage: 'Immediately', - }), - value: sendImmediatleyValue, - checked: sendImmediatley, - }]; - - if (emailBatchingEnabled) { - emailOptions.push({ - label: intl.formatMessage({ - id: 'user.settings.notifications.email.everyXMinutes', - defaultMessage: 'Every {count, plural, one {minute} other {{count, number} minutes}}', - }, {count: Preferences.INTERVAL_FIFTEEN_MINUTES / 60}), - value: Preferences.INTERVAL_FIFTEEN_MINUTES.toString(), - checked: fifteenMinutes, - }, { - label: intl.formatMessage({ - id: 'user.settings.notifications.email.everyHour', - defaultMessage: 'Every hour', - }), - value: Preferences.INTERVAL_HOUR.toString(), - checked: hourly, - }); - } - - emailOptions.push({ - label: intl.formatMessage({ - id: 'user.settings.notifications.email.never', - defaultMessage: 'Never', - }), - value: 'false', - checked: never, - }); - - return ( - this.setState({showEmailNotificationsModal: false})} - > - - - - - - - {!sendEmailNotifications && - - } - {sendEmailNotifications && - - } - {helpText} - - - - - - - - {sendEmailNotifications && - - - - - - - } - - - - - - ); - }; - render() { const {theme, enableAutoResponder} = this.props; const style = getStyleSheet(theme); @@ -458,7 +262,6 @@ class NotificationSettings extends PureComponent { {autoResponder} - {this.renderEmailNotificationSettings(style)} ); } @@ -483,60 +286,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { height: 1, width: '100%', }, - modalOverlay: { - backgroundColor: changeOpacity('#000000', 0.6), - alignItems: 'center', - flex: 1, - }, - modal: { - backgroundColor: theme.centerChannelBg, - borderRadius: 4, - marginTop: 20, - width: '95%', - }, - modalBody: { - paddingHorizontal: 24, - }, - modalTitleContainer: { - marginBottom: 30, - marginTop: 20, - }, - modalTitle: { - color: theme.centerChannelColor, - fontSize: 19, - }, - modalOptionDisabled: { - color: changeOpacity(theme.centerChannelColor, 0.5), - fontSize: 17, - }, - modalHelpText: { - color: changeOpacity(theme.centerChannelColor, 0.5), - fontSize: 13, - marginTop: 20, - }, - modalFooter: { - alignItems: 'flex-end', - height: 58, - marginTop: 40, - width: '100%', - }, - modalFooterContainer: { - alignItems: 'center', - flex: 1, - flexDirection: 'row', - paddingRight: 24, - }, - modalFooterOptionContainer: { - alignItems: 'center', - height: 40, - justifyContent: 'center', - paddingHorizontal: 10, - paddingVertical: 5, - }, - modalFooterOption: { - color: theme.linkColor, - fontSize: 14, - }, }; }); diff --git a/app/screens/settings/notification_settings_email/__snapshots__/notification_settings_email.android.test.js.snap b/app/screens/settings/notification_settings_email/__snapshots__/notification_settings_email.android.test.js.snap new file mode 100644 index 000000000..5dd411882 --- /dev/null +++ b/app/screens/settings/notification_settings_email/__snapshots__/notification_settings_email.android.test.js.snap @@ -0,0 +1,128 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NotificationSettingsMobileAndroid should match snapshot 1`] = ` + + } + label={ + + } + theme={ + Object { + "centerChannelBg": "#aaa", + "centerChannelColor": "#aaa", + } + } +/> +`; + +exports[`NotificationSettingsMobileAndroid should match snapshot 2`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; diff --git a/app/screens/settings/notification_settings_email/__snapshots__/notification_settings_email.ios.test.js.snap b/app/screens/settings/notification_settings_email/__snapshots__/notification_settings_email.ios.test.js.snap new file mode 100644 index 000000000..2f1513e61 --- /dev/null +++ b/app/screens/settings/notification_settings_email/__snapshots__/notification_settings_email.ios.test.js.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NotificationSettingsEmailIos should match snapshot, renderEmailSection 1`] = ` +
+ + + } + selected={true} + theme={ + Object { + "centerChannelBg": "#aaa", + "centerChannelColor": "#aaa", + } + } + /> + + + } + selected={false} + theme={ + Object { + "centerChannelBg": "#aaa", + "centerChannelColor": "#aaa", + } + } + /> + +
+`; diff --git a/app/screens/settings/notification_settings_email/index.js b/app/screens/settings/notification_settings_email/index.js index 6d50d6103..9a6a58b42 100644 --- a/app/screens/settings/notification_settings_email/index.js +++ b/app/screens/settings/notification_settings_email/index.js @@ -1,19 +1,47 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; +import {Preferences} from 'mattermost-redux/constants'; + +import {savePreferences} from 'mattermost-redux/actions/preferences'; + import {getConfig} from 'mattermost-redux/selectors/entities/general'; -import {getMyPreferences, getTheme} from 'mattermost-redux/selectors/entities/preferences'; +import { + get as getPreference, + getTheme, +} from 'mattermost-redux/selectors/entities/preferences'; import NotificationSettingsEmail from './notification_settings_email'; function mapStateToProps(state) { + const config = getConfig(state); + const sendEmailNotifications = config.SendEmailNotifications === 'true'; + const enableEmailBatching = config.EnableEmailBatching === 'true'; + const emailInterval = getPreference( + state, + Preferences.CATEGORY_NOTIFICATIONS, + Preferences.EMAIL_INTERVAL, + Preferences.INTERVAL_NEVER + ).toString(); + return { - config: getConfig(state), - myPreferences: getMyPreferences(state), + enableEmailBatching, + emailInterval, + sendEmailNotifications, + siteName: config.siteName || '', theme: getTheme(state), }; } -export default connect(mapStateToProps)(NotificationSettingsEmail); +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + savePreferences, + }, dispatch), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(NotificationSettingsEmail); diff --git a/app/screens/settings/notification_settings_email/notification_settings_email.android.js b/app/screens/settings/notification_settings_email/notification_settings_email.android.js new file mode 100644 index 000000000..3c30d77a3 --- /dev/null +++ b/app/screens/settings/notification_settings_email/notification_settings_email.android.js @@ -0,0 +1,321 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {intlShape} from 'react-intl'; +import { + Modal, + ScrollView, + TouchableOpacity, + View, +} from 'react-native'; + +import {Preferences} from 'mattermost-redux/constants'; + +import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; + +import FormattedText from 'app/components/formatted_text'; +import RadioButtonGroup from 'app/components/radio_button'; +import SectionItem from 'app/screens/settings/section_item'; + +import NotificationSettingsEmailBase from './notification_settings_email_base'; + +class NotificationSettingsEmailAndroid extends NotificationSettingsEmailBase { + static contextTypes = { + intl: intlShape, + }; + + handleClose = () => { + this.setState({showEmailNotificationsModal: false}); + } + + handleSaveEmailNotification = () => { + this.saveEmailNotifyProps(); + this.handleClose(); + }; + + showEmailModal = () => { + this.setState({showEmailNotificationsModal: true}); + }; + + renderEmailSection() { + const { + sendEmailNotifications, + theme, + } = this.props; + const {interval} = this.state; + let i18nId; + let i18nMessage; + if (sendEmailNotifications) { + switch (interval) { + case Preferences.INTERVAL_IMMEDIATE.toString(): + i18nId = 'user.settings.notifications.email.immediately'; + i18nMessage = 'Immediately'; + break; + case Preferences.INTERVAL_HOUR.toString(): + i18nId = 'user.settings.notifications.email.everyHour'; + i18nMessage = 'Every hour'; + break; + case Preferences.INTERVAL_FIFTEEN_MINUTES.toString(): + i18nId = 'mobile.user.settings.notifications.email.fifteenMinutes'; + i18nMessage = 'Every 15 minutes'; + break; + case Preferences.INTERVAL_NEVER.toString(): + default: + i18nId = 'user.settings.notifications.email.never'; + i18nMessage = 'Never'; + break; + } + } else { + i18nId = 'user.settings.notifications.email.disabled'; + i18nMessage = 'Email notifications are not enabled'; + } + + return ( + + )} + label={( + + )} + action={this.showEmailModal} + actionType='default' + theme={theme} + /> + ); + } + + renderEmailNotificationsModal(style) { + const {intl} = this.context; + const { + enableEmailBatching, + sendEmailNotifications, + siteName, + } = this.props; + const {interval} = this.state; + + let helpText; + if (sendEmailNotifications) { + helpText = ( + + ); + } + + const emailOptions = [{ + label: intl.formatMessage({ + id: 'user.settings.notifications.email.immediately', + defaultMessage: 'Immediately', + }), + value: Preferences.INTERVAL_IMMEDIATE.toString(), + checked: interval === Preferences.INTERVAL_IMMEDIATE.toString(), + }]; + + if (enableEmailBatching) { + emailOptions.push({ + label: intl.formatMessage({ + id: 'mobile.user.settings.notifications.email.fifteenMinutes', + defaultMessage: 'Every 15 minutes', + }), + value: Preferences.INTERVAL_FIFTEEN_MINUTES.toString(), + checked: interval === Preferences.INTERVAL_FIFTEEN_MINUTES.toString(), + }, { + label: intl.formatMessage({ + id: 'user.settings.notifications.email.everyHour', + defaultMessage: 'Every hour', + }), + value: Preferences.INTERVAL_HOUR.toString(), + checked: interval === Preferences.INTERVAL_HOUR.toString(), + }); + } + + emailOptions.push({ + label: intl.formatMessage({ + id: 'user.settings.notifications.email.never', + defaultMessage: 'Never', + }), + value: Preferences.INTERVAL_NEVER.toString(), + checked: interval === Preferences.INTERVAL_NEVER.toString(), + }); + + return ( + + + + + + + + {!sendEmailNotifications && + + } + {sendEmailNotifications && + + } + {helpText} + + + + + + + + {sendEmailNotifications && + + + + + + + } + + + + + + ); + } + + render() { + const {theme} = this.props; + const style = getStyleSheet(theme); + + return ( + + + {this.renderEmailSection()} + + {this.renderEmailNotificationsModal(style)} + + + ); + } +} + +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return { + container: { + flex: 1, + backgroundColor: theme.centerChannelBg, + }, + separator: { + backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), + height: 1, + width: '100%', + }, + divider: { + backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), + height: 1, + width: '100%', + }, + scrollView: { + flex: 1, + backgroundColor: changeOpacity(theme.centerChannelColor, 0.06), + }, + scrollViewContent: { + paddingVertical: 0, + }, + modalOverlay: { + backgroundColor: changeOpacity('#000000', 0.6), + alignItems: 'center', + flex: 1, + }, + modal: { + backgroundColor: theme.centerChannelBg, + borderRadius: 4, + marginTop: 20, + width: '95%', + }, + modalBody: { + paddingHorizontal: 24, + }, + modalTitleContainer: { + marginBottom: 30, + marginTop: 20, + }, + modalTitle: { + color: theme.centerChannelColor, + fontSize: 19, + }, + modalOptionDisabled: { + color: changeOpacity(theme.centerChannelColor, 0.5), + fontSize: 17, + }, + modalHelpText: { + color: changeOpacity(theme.centerChannelColor, 0.5), + fontSize: 13, + marginTop: 20, + }, + modalFooter: { + alignItems: 'flex-end', + height: 58, + marginTop: 40, + width: '100%', + }, + modalFooterContainer: { + alignItems: 'center', + flex: 1, + flexDirection: 'row', + paddingRight: 24, + }, + modalFooterOptionContainer: { + alignItems: 'center', + height: 40, + justifyContent: 'center', + paddingHorizontal: 10, + paddingVertical: 5, + }, + modalFooterOption: { + color: theme.linkColor, + fontSize: 14, + }, + }; +}); + +export default NotificationSettingsEmailAndroid; diff --git a/app/screens/settings/notification_settings_email/notification_settings_email.android.test.js b/app/screens/settings/notification_settings_email/notification_settings_email.android.test.js new file mode 100644 index 000000000..062f7ac24 --- /dev/null +++ b/app/screens/settings/notification_settings_email/notification_settings_email.android.test.js @@ -0,0 +1,102 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {configure} from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +configure({adapter: new Adapter()}); + +import {shallowWithIntl} from 'test/intl-test-helper'; + +import NotificationSettingsMobileAndroid from './notification_settings_email.android.js'; + +describe('NotificationSettingsMobileAndroid', () => { + const baseProps = { + currentUserId: 'current_user_id', + emailInterval: '30', + enableEmailBatching: false, + navigator: {setOnNavigatorEvent: () => {}}, // eslint-disable-line no-empty-function + actions: { + savePreferences: () => {}, // eslint-disable-line no-empty-function + }, + sendEmailNotifications: true, + siteName: 'Mattermost', + theme: { + centerChannelBg: '#aaa', + centerChannelColor: '#aaa', + }, + }; + + test('should match snapshot', () => { + const wrapper = shallowWithIntl( + + ); + + const style = { + divider: {}, + modal: {}, + modalBody: {}, + modalTitleContainer: {}, + modalTitle: {}, + modalOptionDisabled: {}, + modalHelpText: {}, + modalFooter: {}, + modalFooterContainer: {}, + modalFooterOptionContainer: {}, + modalFooterOption: {}, + }; + + expect(wrapper.instance().renderEmailSection()).toMatchSnapshot(); + expect(wrapper.instance().renderEmailNotificationsModal(style)).toMatchSnapshot(); + }); + + test('should match state on setEmailNotifications', () => { + const wrapper = shallowWithIntl( + + ); + + wrapper.setState({email: 'false', interval: '0'}); + wrapper.instance().setEmailNotifications('30'); + expect(wrapper.state({email: 'true', interval: '30'})); + + wrapper.instance().setEmailNotifications('0'); + expect(wrapper.state({email: 'false', interval: '0'})); + + wrapper.instance().setEmailNotifications('3600'); + expect(wrapper.state({email: 'true', interval: '3600'})); + }); + + test('should match state on handleClose', () => { + const wrapper = shallowWithIntl( + + ); + + wrapper.setState({showEmailNotificationsModal: true}); + wrapper.instance().handleClose(); + expect(wrapper.state('showEmailNotificationsModal')).toEqual(false); + }); + + test('should saveEmailNotifyProps and handleClose on handleSaveEmailNotification', () => { + const wrapper = shallowWithIntl( + + ); + + const instance = wrapper.instance(); + instance.saveEmailNotifyProps = jest.fn(); + instance.handleClose = jest.fn(); + + instance.handleSaveEmailNotification(); + expect(instance.saveEmailNotifyProps).toHaveBeenCalledTimes(1); + expect(instance.handleClose).toHaveBeenCalledTimes(1); + }); + + test('should match state on showEmailModal', () => { + const wrapper = shallowWithIntl( + + ); + + wrapper.setState({showEmailNotificationsModal: false}); + wrapper.instance().showEmailModal(); + expect(wrapper.state('showEmailNotificationsModal')).toEqual(true); + }); +}); diff --git a/app/screens/settings/notification_settings_email/notification_settings_email.js b/app/screens/settings/notification_settings_email/notification_settings_email.ios.js similarity index 53% rename from app/screens/settings/notification_settings_email/notification_settings_email.js rename to app/screens/settings/notification_settings_email/notification_settings_email.ios.js index e29778335..dccb96804 100644 --- a/app/screens/settings/notification_settings_email/notification_settings_email.js +++ b/app/screens/settings/notification_settings_email/notification_settings_email.ios.js @@ -1,8 +1,7 @@ // 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 React from 'react'; import { ScrollView, @@ -10,120 +9,34 @@ import { } from 'react-native'; import {Preferences} from 'mattermost-redux/constants'; -import {getPreferencesByCategory} from 'mattermost-redux/utils/preference_utils'; import FormattedText from 'app/components/formatted_text'; import StatusBar from 'app/components/status_bar'; -import {getNotificationProps} from 'app/utils/notify_props'; -import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import Section from 'app/screens/settings/section'; import SectionItem from 'app/screens/settings/section_item'; -export default class NotificationSettingsEmail extends PureComponent { - static propTypes = { - config: PropTypes.object.isRequired, - currentUser: PropTypes.object.isRequired, - myPreferences: PropTypes.object.isRequired, - navigator: PropTypes.object, - onBack: PropTypes.func.isRequired, - theme: PropTypes.object.isRequired, - }; +import NotificationSettingsEmailBase from './notification_settings_email_base'; - constructor(props) { - super(props); - - const {currentUser} = props; - const notifyProps = getNotificationProps(currentUser); - - props.navigator.setOnNavigatorEvent(this.onNavigatorEvent); - this.state = this.setStateFromNotifyProps(notifyProps); - } - - componentWillReceiveProps(nextProps) { - if (this.props.theme !== nextProps.theme) { - setNavigatorStyles(this.props.navigator, nextProps.theme); - } - } - - onNavigatorEvent = (event) => { - if (event.type === 'ScreenChangedEvent') { - switch (event.id) { - case 'willDisappear': - this.saveUserNotifyProps(); - break; - } - } - }; - - setEmailNotifications = (value) => { - const {config} = this.props; - let email = value; - let interval; - - const emailBatchingEnabled = config.EnableEmailBatching === 'true'; - if (emailBatchingEnabled && value !== 'false') { - interval = value; - email = 'true'; - } - - this.setState({ - email, - interval, - }); - }; - - setStateFromNotifyProps = (notifyProps) => { - const {config, myPreferences} = this.props; - let interval; - if (config.SendEmailNotifications === 'true' && config.EnableEmailBatching === 'true') { - const emailPreferences = getPreferencesByCategory(myPreferences, Preferences.CATEGORY_NOTIFICATIONS); - if (emailPreferences.size) { - interval = emailPreferences.get(Preferences.EMAIL_INTERVAL).value; - } - } - - return { - ...notifyProps, - interval, - }; - }; - - saveUserNotifyProps = () => { - this.props.onBack({ - ...this.state, - user_id: this.props.currentUser.id, - }); - }; - - renderEmailSection = () => { - const {config, theme} = this.props; +class NotificationSettingsEmailIos extends NotificationSettingsEmailBase { + renderEmailSection() { + const { + enableEmailBatching, + sendEmailNotifications, + siteName, + theme, + } = this.props; + const {interval} = this.state; const style = getStyleSheet(theme); - const sendEmailNotifications = config.SendEmailNotifications === 'true'; - const emailBatchingEnabled = sendEmailNotifications && config.EnableEmailBatching === 'true'; - - let sendImmediatley = this.state.email === 'true'; - let sendImmediatleyValue = 'true'; - let fifteenMinutes; - let hourly; - const never = this.state.email === 'false'; - - if (emailBatchingEnabled && this.state.email !== 'false') { - sendImmediatley = this.state.interval === Preferences.INTERVAL_IMMEDIATE.toString(); - fifteenMinutes = this.state.interval === Preferences.INTERVAL_FIFTEEN_MINUTES.toString(); - hourly = this.state.interval === Preferences.INTERVAL_HOUR.toString(); - - sendImmediatleyValue = Preferences.INTERVAL_IMMEDIATE.toString(); - } - return (
@@ -138,25 +51,24 @@ export default class NotificationSettingsEmail extends PureComponent { )} action={this.setEmailNotifications} actionType='select' - actionValue={sendImmediatleyValue} - selected={sendImmediatley} + actionValue={Preferences.INTERVAL_IMMEDIATE.toString()} + selected={interval === Preferences.INTERVAL_IMMEDIATE.toString()} theme={theme} /> - {emailBatchingEnabled && + {enableEmailBatching && )} action={this.setEmailNotifications} actionType='select' actionValue={Preferences.INTERVAL_FIFTEEN_MINUTES.toString()} - selected={fifteenMinutes} + selected={interval === Preferences.INTERVAL_FIFTEEN_MINUTES.toString()} theme={theme} /> @@ -170,7 +82,7 @@ export default class NotificationSettingsEmail extends PureComponent { action={this.setEmailNotifications} actionType='select' actionValue={Preferences.INTERVAL_HOUR.toString()} - selected={hourly} + selected={interval === Preferences.INTERVAL_HOUR.toString()} theme={theme} /> @@ -185,8 +97,8 @@ export default class NotificationSettingsEmail extends PureComponent { )} action={this.setEmailNotifications} actionType='select' - actionValue='false' - selected={never} + actionValue={Preferences.INTERVAL_NEVER.toString()} + selected={interval === Preferences.INTERVAL_NEVER.toString()} theme={theme} /> @@ -200,7 +112,7 @@ export default class NotificationSettingsEmail extends PureComponent { }
); - }; + } render() { const {theme} = this.props; @@ -227,11 +139,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { flex: 1, backgroundColor: theme.centerChannelBg, }, - input: { - color: theme.centerChannelColor, - fontSize: 12, - height: 40, - }, separator: { backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), flex: 1, @@ -253,3 +160,5 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }, }; }); + +export default NotificationSettingsEmailIos; diff --git a/app/screens/settings/notification_settings_email/notification_settings_email.ios.test.js b/app/screens/settings/notification_settings_email/notification_settings_email.ios.test.js new file mode 100644 index 000000000..044bc5837 --- /dev/null +++ b/app/screens/settings/notification_settings_email/notification_settings_email.ios.test.js @@ -0,0 +1,89 @@ +// 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'; +configure({adapter: new Adapter()}); + +import NotificationSettingsEmailIos from './notification_settings_email.ios.js'; + +jest.mock('app/utils/theme', () => { + const original = require.requireActual('app/utils/theme'); + return { + ...original, + changeOpacity: jest.fn(), + }; +}); + +describe('NotificationSettingsEmailIos', () => { + const baseProps = { + currentUserId: 'current_user_id', + emailInterval: '30', + enableEmailBatching: false, + navigator: {setOnNavigatorEvent: () => {}}, // eslint-disable-line no-empty-function + actions: { + savePreferences: () => {}, // eslint-disable-line no-empty-function + }, + sendEmailNotifications: true, + siteName: 'Mattermost', + theme: { + centerChannelBg: '#aaa', + centerChannelColor: '#aaa', + }, + }; + + test('should match snapshot, renderEmailSection', () => { + const wrapper = shallow( + + ); + + expect(wrapper.instance().renderEmailSection()).toMatchSnapshot(); + }); + + test('should call saveEmailNotifyProps on onNavigatorEvent', () => { + const wrapper = shallow( + + ); + + const instance = wrapper.instance(); + instance.saveEmailNotifyProps = jest.fn(); + instance.onNavigatorEvent({type: 'ScreenChangedEvent', id: 'willDisappear'}); + + expect(instance.saveEmailNotifyProps).toHaveBeenCalledTimes(1); + }); + + test('should macth state on setEmailNotifications', () => { + const wrapper = shallow( + + ); + + wrapper.setState({email: 'false', interval: '0'}); + wrapper.instance().setEmailNotifications('30'); + expect(wrapper.state({email: 'true', interval: '30'})); + + wrapper.instance().setEmailNotifications('0'); + expect(wrapper.state({email: 'false', interval: '0'})); + + wrapper.instance().setEmailNotifications('3600'); + expect(wrapper.state({email: 'true', interval: '3600'})); + }); + + test('should call props.actions.savePreferences on saveUserNotifyProps', () => { + const props = {...baseProps, actions: {savePreferences: jest.fn()}}; + const wrapper = shallow( + + ); + + wrapper.setState({email: 'true', interval: '3600'}); + wrapper.instance().saveEmailNotifyProps(); + expect(props.actions.savePreferences).toHaveBeenCalledTimes(1); + expect(props.actions.savePreferences).toBeCalledWith( + 'current_user_id', + [ + {category: 'notifications', name: 'email', user_id: 'current_user_id', value: 'true'}, + {category: 'notifications', name: 'email_interval', user_id: 'current_user_id', value: '3600'}, + ] + ); + }); +}); diff --git a/app/screens/settings/notification_settings_email/notification_settings_email_base.js b/app/screens/settings/notification_settings_email/notification_settings_email_base.js new file mode 100644 index 000000000..7b9114080 --- /dev/null +++ b/app/screens/settings/notification_settings_email/notification_settings_email_base.js @@ -0,0 +1,99 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {PureComponent} from 'react'; +import PropTypes from 'prop-types'; + +import {Preferences} from 'mattermost-redux/constants'; +import {getEmailInterval} from 'mattermost-redux/utils/notify_props'; + +import {setNavigatorStyles} from 'app/utils/theme'; + +export default class NotificationSettingsEmailBase extends PureComponent { + static propTypes = { + actions: PropTypes.shape({ + savePreferences: PropTypes.func.isRequired, + }), + currentUserId: PropTypes.string.isRequired, + emailInterval: PropTypes.string.isRequired, + enableEmailBatching: PropTypes.bool.isRequired, + navigator: PropTypes.object, + sendEmailNotifications: PropTypes.bool.isRequired, + siteName: PropTypes.string, + theme: PropTypes.object.isRequired, + }; + + constructor(props) { + super(props); + + const { + emailInterval, + enableEmailBatching, + navigator, + sendEmailNotifications, + } = props; + + this.state = { + interval: getEmailInterval( + sendEmailNotifications, + enableEmailBatching, + parseInt(emailInterval, 10), + ).toString(), + showEmailNotificationsModal: false, + }; + + navigator.setOnNavigatorEvent(this.onNavigatorEvent); + } + + componentWillReceiveProps(nextProps) { + if (this.props.theme !== nextProps.theme) { + setNavigatorStyles(this.props.navigator, nextProps.theme); + } + + if ( + this.props.sendEmailNotifications !== nextProps.sendEmailNotifications || + this.props.enableEmailBatching !== nextProps.enableEmailBatching || + this.props.emailInterval !== nextProps.emailInterval + ) { + this.setState({ + interval: getEmailInterval( + nextProps.sendEmailNotifications, + nextProps.enableEmailBatching, + parseInt(nextProps.emailInterval, 10), + ).toString(), + }); + } + } + + onNavigatorEvent = (event) => { + if (event.type === 'ScreenChangedEvent') { + switch (event.id) { + case 'willDisappear': + this.saveEmailNotifyProps(); + break; + } + } + }; + + setEmailNotifications = (interval) => { + const {sendEmailNotifications} = this.props; + + let email = 'false'; + if (sendEmailNotifications && interval !== Preferences.INTERVAL_NEVER.toString()) { + email = 'true'; + } + + this.setState({ + email, + interval, + }); + }; + + saveEmailNotifyProps = () => { + const {currentUserId} = this.props; + const {email, interval} = this.state; + const emailNotify = {category: Preferences.CATEGORY_NOTIFICATIONS, user_id: currentUserId, name: 'email', value: email}; + const emailInterval = {category: Preferences.CATEGORY_NOTIFICATIONS, user_id: currentUserId, name: Preferences.EMAIL_INTERVAL, value: interval}; + this.props.actions.savePreferences(currentUserId, [emailNotify, emailInterval]); + }; +} diff --git a/app/screens/settings/notification_settings_mentions/notification_settings_mentions.android.js b/app/screens/settings/notification_settings_mentions/notification_settings_mentions.android.js index 20e32fb6a..d49ab0d09 100644 --- a/app/screens/settings/notification_settings_mentions/notification_settings_mentions.android.js +++ b/app/screens/settings/notification_settings_mentions/notification_settings_mentions.android.js @@ -20,7 +20,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import NotificationSettingsMentionsBase from './notification_settings_mention_base'; -class NotificationSettingsMentionsIos extends NotificationSettingsMentionsBase { +class NotificationSettingsMentionsAndroid extends NotificationSettingsMentionsBase { cancelMentionKeys = () => { this.setState({showKeywordsModal: false}); this.keywords = this.state.mention_keys; @@ -442,4 +442,4 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }; }); -export default injectIntl(NotificationSettingsMentionsIos); +export default injectIntl(NotificationSettingsMentionsAndroid); diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 481edc80f..03a5aceff 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -3318,6 +3318,7 @@ "user.settings.notifications.email.disabled_long": "Email notifications have not been enabled by your System Administrator.", "user.settings.notifications.email.everyHour": "Every hour", "user.settings.notifications.email.everyXMinutes": "Every {count, plural, one {minute} other {{count, number} minutes}}", + "mobile.user.settings.notifications.email.fifteenMinutes": "Every 15 minutes", "user.settings.notifications.email.immediately": "Immediately", "user.settings.notifications.email.never": "Never", "user.settings.notifications.email.send": "Send email notifications", From 6379cb9e4d12fcca55d5a9c66c115e2ff3a0ca99 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 4 Jul 2018 13:52:51 -0400 Subject: [PATCH 19/20] Bump iOS build number to 123 (#1885) --- ios/Mattermost.xcodeproj/project.pbxproj | 4 ++-- ios/Mattermost/Info.plist | 2 +- ios/MattermostShare/Info.plist | 2 +- ios/MattermostTests/Info.plist | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 23507ce2c..53bd33aaa 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -2532,7 +2532,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 122; + CURRENT_PROJECT_VERSION = 123; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2582,7 +2582,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 122; + CURRENT_PROJECT_VERSION = 123; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index 10ca7c0c0..555cc5abe 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 122 + 123 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index 9ee30cd77..b623f7428 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -23,7 +23,7 @@ CFBundleShortVersionString 1.10.0 CFBundleVersion - 122 + 123 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index 26f4e17df..1b0f40df8 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 122 + 123 From 6d8c59ab61d17b2b59774373cc4dd353fbd15fb5 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 4 Jul 2018 13:53:02 -0400 Subject: [PATCH 20/20] Bump Android build number to 123 (#1886) --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 8a2647fed..a2a0362ff 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -113,7 +113,7 @@ android { applicationId "com.mattermost.rnbeta" minSdkVersion 21 targetSdkVersion 23 - versionCode 122 + versionCode 123 versionName "1.10.0" ndk { abiFilters "armeabi-v7a", "x86"