diff --git a/android/app/src/main/java/com/mattermost/MainApplication.java b/android/app/src/main/java/com/mattermost/MainApplication.java index 7b06dbb5c..f3ffff7a5 100644 --- a/android/app/src/main/java/com/mattermost/MainApplication.java +++ b/android/app/src/main/java/com/mattermost/MainApplication.java @@ -9,7 +9,7 @@ import com.learnium.RNDeviceInfo.RNDeviceInfo; import com.psykar.cookiemanager.CookieManagerPackage; import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage; import com.oblador.vectoricons.VectorIconsPackage; -import com.horcrux.svg.RNSvgPackage; +import com.horcrux.svg.SvgPackage; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; @@ -41,7 +41,7 @@ public class MainApplication extends Application implements ReactApplication { new CookieManagerPackage(), new ReactNativePushNotificationPackage(), new VectorIconsPackage(), - new RNSvgPackage(), + new SvgPackage(), new LinearGradientPackage(), new PickerPackage(), new OrientationPackage() diff --git a/app/actions/navigation/index.js b/app/actions/navigation/index.js index e2255f461..da4c1bf3c 100644 --- a/app/actions/navigation/index.js +++ b/app/actions/navigation/index.js @@ -281,7 +281,6 @@ export function showOptionsModal(options) { export function showMoreChannelsModal() { return async (dispatch, getState) => { - closeDrawers()(dispatch, getState); dispatch({ type: NavigationTypes.NAVIGATION_MODAL, route: Routes.MoreChannels @@ -291,7 +290,6 @@ export function showMoreChannelsModal() { export function showDirectMessagesModal() { return async (dispatch, getState) => { - closeDrawers()(dispatch, getState); dispatch({ type: NavigationTypes.NAVIGATION_MODAL, route: Routes.MoreDirectMessages diff --git a/app/components/channel_drawer_list/channel_drawer_list.js b/app/components/channel_drawer_list/channel_drawer_list.js index 180233709..3539fb75c 100644 --- a/app/components/channel_drawer_list/channel_drawer_list.js +++ b/app/components/channel_drawer_list/channel_drawer_list.js @@ -37,6 +37,7 @@ class ChannelDrawerList extends Component { showDirectMessagesModal: PropTypes.func.isRequired, showMoreChannelsModal: PropTypes.func.isRequired }).isRequired, + canCreatePrivateChannels: PropTypes.bool.isRequired, channels: PropTypes.object.isRequired, channelMembers: PropTypes.object, currentTeam: PropTypes.object.isRequired, @@ -186,7 +187,7 @@ class ChannelDrawerList extends Component { return data; } - const {theme} = this.props; + const {canCreatePrivateChannels, theme} = this.props; const styles = getStyleSheet(theme); const { @@ -208,8 +209,12 @@ class ChannelDrawerList extends Component { ...publicChannels ); + let createPrivateChannel; + if (canCreatePrivateChannels) { + createPrivateChannel = this.createPrivateChannel; + } data.push( - this.renderTitle(styles, 'sidebar.pg', 'PRIVATE CHANNELS', this.createPrivateChannel, privateChannels.length > 0), + this.renderTitle(styles, 'sidebar.pg', 'PRIVATE CHANNELS', createPrivateChannel, privateChannels.length > 0), ...privateChannels ); diff --git a/app/components/channel_drawer_list/channel_drawer_list_container.js b/app/components/channel_drawer_list/channel_drawer_list_container.js index 9d21f29f7..2fc2114be 100644 --- a/app/components/channel_drawer_list/channel_drawer_list_container.js +++ b/app/components/channel_drawer_list/channel_drawer_list_container.js @@ -20,10 +20,19 @@ import { unmarkFavorite } from 'app/actions/views/channel'; +import {Constants} from 'mattermost-redux/constants'; +import {getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users'; +import {showCreateOption} from 'mattermost-redux/utils/channel_utils'; +import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils'; + import ChannelDrawerList from './channel_drawer_list'; function mapStateToProps(state, ownProps) { + const {config, license} = state.entities.general; + const roles = getCurrentUserRoles(state); + return { + canCreatePrivateChannels: showCreateOption(config, license, Constants.PRIVATE_CHANNEL, isAdmin(roles), isSystemAdmin(roles)), ...ownProps }; } diff --git a/app/components/custom_list/index.js b/app/components/custom_list/index.js index 7d2e1288a..6ec659bb3 100644 --- a/app/components/custom_list/index.js +++ b/app/components/custom_list/index.js @@ -23,7 +23,8 @@ export default class CustomList extends PureComponent { selectable: PropTypes.bool, onRowSelect: PropTypes.func, renderRow: PropTypes.func.isRequired, - createSections: PropTypes.func + createSections: PropTypes.func, + showNoResults: PropTypes.bool }; static defaultProps = { @@ -37,7 +38,8 @@ export default class CustomList extends PureComponent { loadingText: null, onRowSelect: () => true, createSections: () => true, - showSections: true + showSections: true, + showNoResults: true }; constructor(props) { @@ -48,7 +50,6 @@ export default class CustomList extends PureComponent { componentWillReceiveProps(nextProps) { const {data, showSections, searching} = nextProps; - this.showNoResults = true; if (searching || searching !== this.props.searching) { this.setState(this.buildDataSource(nextProps)); @@ -161,7 +162,7 @@ export default class CustomList extends PureComponent { noResults = this.props.data.length === 0; } - if (this.showNoResults && !this.props.loading && noResults) { + if (this.props.showNoResults && !this.props.loading && noResults) { return ( { + this.setState({canEdit: false}); + }; + handlePostDelete = () => { const {formatMessage} = this.props.intl; const {currentTeamId, post, actions} = this.props; @@ -74,6 +104,7 @@ class Post extends PureComponent { text: formatMessage({id: 'post_info.del', defaultMessage: 'Delete'}), style: 'destructive', onPress: () => { + this.editDisableAction.cancel(); actions.deletePost(currentTeamId, post); } }] @@ -183,7 +214,7 @@ class Post extends PureComponent { renderMessage = (style, messageStyle, replyBar = false) => { const {formatMessage} = this.props.intl; - const {currentUserId, isFlagged, post, roles, theme} = this.props; + const {isFlagged, post, theme} = this.props; const {flagPost, unflagPost} = this.props.actions; const actions = []; @@ -200,11 +231,11 @@ class Post extends PureComponent { }); } - if (post.user_id === currentUserId) { + if (this.state.canEdit) { actions.push({text: formatMessage({id: 'post_info.edit', defaultMessage: 'Edit'}), onPress: () => this.handlePostEdit()}); } - if (post.user_id === currentUserId || isAdmin(roles)) { + if (this.state.canDelete && post.state !== Constants.POST_DELETED) { actions.push({text: formatMessage({id: 'post_info.del', defaultMessage: 'Delete'}), onPress: () => this.handlePostDelete()}); } diff --git a/app/components/post/post_container.js b/app/components/post/post_container.js index 88faaf635..1ad1fdd52 100644 --- a/app/components/post/post_container.js +++ b/app/components/post/post_container.js @@ -22,16 +22,18 @@ function makeMapStateToProps() { const commentedOnUser = ownProps.commentedOnPost ? getUser(state, ownProps.commentedOnPost.user_id) : null; const user = getUser(state, ownProps.post.user_id); const myPreferences = getMyPreferences(state); + const {config, license} = state.entities.general; return { ...ownProps, - config: state.entities.general.config, + config, commentCount: getCommentCountForPost(state, ownProps), commentedOnDisplayName: displayUsername(commentedOnUser, myPreferences), currentTeamId: getCurrentTeamId(state), currentUserId: getCurrentUserId(state), displayName: displayUsername(user, myPreferences), isFlagged: isPostFlagged(ownProps.post.id, myPreferences), + license, roles: getCurrentUserRoles(state), theme: getTheme(state), user diff --git a/app/scenes/channel_info/channel_info.js b/app/scenes/channel_info/channel_info.js index 4ed5e6c89..26515d764 100644 --- a/app/scenes/channel_info/channel_info.js +++ b/app/scenes/channel_info/channel_info.js @@ -20,6 +20,7 @@ import ChannelInfoRow from './channel_info_row'; class ChannelInfo extends PureComponent { static propTypes = { intl: intlShape.isRequired, + canDeleteChannel: PropTypes.bool.isRequired, currentTeamId: PropTypes.string.isRequired, currentChannel: PropTypes.object.isRequired, currentChannelCreatorName: PropTypes.string, @@ -152,6 +153,7 @@ class ChannelInfo extends PureComponent { render() { const { + canDeleteChannel, currentChannel, currentChannelCreatorName, currentChannelMemberCount, @@ -245,7 +247,7 @@ class ChannelInfo extends PureComponent { theme={theme} /> - {this.renderLeaveOrDeleteChannelRow() && + {this.renderLeaveOrDeleteChannelRow() && canDeleteChannel && this.handleDeleteOrLeave('delete')} diff --git a/app/scenes/channel_info/channel_info_container.js b/app/scenes/channel_info/channel_info_container.js index 0e5fd5c9f..c75e0c32f 100644 --- a/app/scenes/channel_info/channel_info_container.js +++ b/app/scenes/channel_info/channel_info_container.js @@ -22,12 +22,14 @@ import { getChannelsByCategory, canManageChannelMembers } from 'mattermost-redux/selectors/entities/channels'; -import {getCurrentUserId, getUser, getStatusForUserId} from 'mattermost-redux/selectors/entities/users'; -import {getUserIdFromChannelName} from 'mattermost-redux/utils/channel_utils'; +import {getCurrentUserId, getUser, getStatusForUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users'; +import {getUserIdFromChannelName, showDeleteOption} from 'mattermost-redux/utils/channel_utils'; +import {isAdmin, isChannelAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils'; import ChannelInfo from './channel_info'; function mapStateToProps(state, ownProps) { + const {config, license} = state.entities.general; const currentChannel = getCurrentChannel(state); const currentChannelCreator = getUser(state, currentChannel.creator_id); const currentChannelCreatorName = currentChannelCreator && currentChannelCreator.username; @@ -37,6 +39,7 @@ function mapStateToProps(state, ownProps) { const isCurrent = currentChannel.id === state.entities.channels.currentChannelId; const isFavorite = favoriteChannels.indexOf(currentChannel.id) > -1; const leaveChannelRequest = state.requests.channels.leaveChannel; + const roles = getCurrentUserRoles(state); let status; if (currentChannel.type === Constants.DM_CHANNEL) { @@ -46,6 +49,7 @@ function mapStateToProps(state, ownProps) { return { ...ownProps, + canDeleteChannel: showDeleteOption(config, license, currentChannel, isAdmin(roles), isSystemAdmin(roles), isChannelAdmin(roles)), currentTeamId: state.entities.teams.currentTeamId, currentChannel, currentChannelCreatorName, diff --git a/app/scenes/more_channels/create_button.js b/app/scenes/more_channels/create_button.js index f8c701be6..59da6b84c 100644 --- a/app/scenes/more_channels/create_button.js +++ b/app/scenes/more_channels/create_button.js @@ -9,10 +9,18 @@ import { } from 'react-native'; import FormattedText from 'app/components/formatted_text'; - import {getTheme} from 'app/selectors/preferences'; +import {Constants} from 'mattermost-redux/constants'; +import {getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users'; +import {showCreateOption} from 'mattermost-redux/utils/channel_utils'; +import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils'; + function CreateButton(props) { + if (!props.canCreateChannels) { + return null; + } + return ( { + // set the timeout to 400 cause is the time that the modal takes to open + // Somehow interactionManager doesn't care + setTimeout(() => { this.props.actions.getMoreChannels(this.props.currentTeamId, 0); - }); + }, 400); } componentWillUnmount() { @@ -208,6 +212,7 @@ class MoreChannels extends PureComponent { id); await this.props.actions.handleSelectChannel(id); + this.props.actions.closeDrawers(); InteractionManager.runAfterInteractions(() => { this.props.actions.goBack(); }); @@ -218,13 +223,15 @@ class MoreChannels extends PureComponent { }; render() { - const {formatMessage} = this.props.intl; - const isLoading = this.props.requestStatus.status === RequestStatus.STARTED; - const style = getStyleFromTheme(this.props.theme); - const more = this.state.searching ? () => true : this.loadMoreChannels; + const {intl, requestStatus, theme} = this.props; + const {adding, channels, searching} = this.state; + const {formatMessage} = intl; + const isLoading = requestStatus.status === RequestStatus.STARTED || requestStatus.status === RequestStatus.NOT_STARTED; + const style = getStyleFromTheme(theme); + const more = searching ? () => true : this.loadMoreChannels; let content; - if (this.state.adding) { + if (adding) { content = ( @@ -250,9 +257,9 @@ class MoreChannels extends PureComponent { /> ); diff --git a/app/scenes/more_dms/index.js b/app/scenes/more_dms/index.js index 6534dbbba..5f431d5a3 100644 --- a/app/scenes/more_dms/index.js +++ b/app/scenes/more_dms/index.js @@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux'; import navigationSceneConnect from '../navigationSceneConnect'; -import {goBack} from 'app/actions/navigation'; +import {closeDrawers, goBack} from 'app/actions/navigation'; import {makeDirectChannel} from 'app/actions/views/more_dms'; import {getTheme} from 'app/selectors/preferences'; import {getProfiles, searchProfiles} from 'mattermost-redux/actions/users'; @@ -41,6 +41,7 @@ function mapStateToProps(state, ownProps) { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ + closeDrawers, goBack, makeDirectChannel, getProfiles, diff --git a/app/scenes/more_dms/more_dms.js b/app/scenes/more_dms/more_dms.js index d2a98adca..f825237ef 100644 --- a/app/scenes/more_dms/more_dms.js +++ b/app/scenes/more_dms/more_dms.js @@ -31,6 +31,7 @@ class MoreDirectMessages extends PureComponent { subscribeToHeaderEvent: React.PropTypes.func.isRequired, unsubscribeFromHeaderEvent: React.PropTypes.func.isRequired, actions: PropTypes.shape({ + closeDrawers: PropTypes.func.isRequired, goBack: PropTypes.func.isRequired, makeDirectChannel: PropTypes.func.isRequired, getProfiles: PropTypes.func.isRequired, @@ -61,11 +62,12 @@ class MoreDirectMessages extends PureComponent { this.searchTimeoutId = 0; this.state = { - profiles: [], + profiles: props.profiles, page: 0, adding: false, next: true, - searching: false + searching: false, + showNoResults: false }; } @@ -79,7 +81,7 @@ class MoreDirectMessages extends PureComponent { nextProps.requestStatus.status === RequestStatus.SUCCESS) { const {page} = this.state; const profiles = nextProps.profiles.splice(0, (page + 1) * Constants.PROFILE_CHUNK_SIZE); - this.setState({profiles}); + this.setState({profiles, showNoResults: true}); } else if (this.state.searching && nextProps.searchRequest.status === RequestStatus.SUCCESS) { const results = nextProps.profiles.filter((p) => { @@ -87,7 +89,7 @@ class MoreDirectMessages extends PureComponent { return p.username.toLowerCase().includes(term) || p.email.toLowerCase().includes(term) || p.first_name.toLowerCase().includes(term) || p.last_name.toLowerCase().includes(term); }); - this.setState({profiles: results}); + this.setState({profiles: results, showNoResults: true}); } } @@ -96,9 +98,11 @@ class MoreDirectMessages extends PureComponent { Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard); } - InteractionManager.runAfterInteractions(() => { + // set the timeout to 400 cause is the time that the modal takes to open + // Somehow interactionManager doesn't care + setTimeout(() => { this.props.actions.getProfiles(0); - }); + }, 400); } componentWillUnmount() { @@ -166,20 +170,24 @@ class MoreDirectMessages extends PureComponent { this.setState({adding: true}); this.searchBar.blur(); await this.props.actions.makeDirectChannel(id); + + this.props.actions.closeDrawers(); InteractionManager.runAfterInteractions(() => { this.props.actions.goBack(); }); }; render() { - const {formatMessage} = this.props.intl; - const isLoading = (this.props.requestStatus.status === RequestStatus.STARTED) || - (this.props.searchRequest.status === RequestStatus.STARTED); - const style = getStyleFromTheme(this.props.theme); + const {intl, preferences, requestStatus, searchRequest, theme} = this.props; + const {adding, profiles, searching, showNoResults} = this.state; + const {formatMessage} = intl; + const isLoading = (requestStatus.status === RequestStatus.STARTED) || (requestStatus.status === RequestStatus.NOT_STARTED) || + (searchRequest.status === RequestStatus.STARTED); + const style = getStyleFromTheme(theme); const more = this.state.searching ? () => true : this.loadMoreProfiles; let content; - if (this.state.adding) { + if (adding) { content = ( @@ -205,11 +213,11 @@ class MoreDirectMessages extends PureComponent { /> ); diff --git a/app/scenes/user_profile/user_profile.js b/app/scenes/user_profile/user_profile.js index 5120698f2..f5bfe950f 100644 --- a/app/scenes/user_profile/user_profile.js +++ b/app/scenes/user_profile/user_profile.js @@ -15,6 +15,95 @@ import {getFullName} from 'mattermost-redux/utils/user_utils'; import UserProfileRow from './user_profile_row'; +export default class UserProfile extends PureComponent { + static propTypes = { + actions: PropTypes.shape({ + handleSendMessage: PropTypes.func.isRequired + }).isRequired, + config: PropTypes.object.isRequired, + currentUserId: PropTypes.string.isRequired, + theme: PropTypes.object.isRequired, + user: PropTypes.object.isRequired + }; + + state = { + isFavorite: false + }; + + getDisplayName = () => { + const {theme, user} = this.props; + const style = createStyleSheet(theme); + + const displayName = getFullName(user); + + if (displayName) { + return {displayName}; + } + + return null; + }; + + buildDisplayBlock = (property) => { + const {theme, user} = this.props; + const style = createStyleSheet(theme); + + if (user.hasOwnProperty(property) && user[property].length > 0) { + return ( + + {property.toUpperCase()} + {user[property]} + + ); + } + + return null; + }; + + sendMessage = () => { + this.props.actions.handleSendMessage(this.props.user.id); + }; + + render() { + const {config, currentUserId, theme, user} = this.props; + const style = createStyleSheet(theme); + + return ( + + + + + {this.getDisplayName()} + {`@${user.username}`} + + + {this.buildDisplayBlock('username')} + {config.ShowEmailAddress === 'true' && this.buildDisplayBlock('email')} + {this.buildDisplayBlock('position')} + + {currentUserId !== user.id && + + } + + + ); + } +} + const createStyleSheet = makeStyleSheetFromTheme((theme) => { return StyleSheet.create({ container: { @@ -60,91 +149,3 @@ const createStyleSheet = makeStyleSheetFromTheme((theme) => { } }); }); - -export default class UserProfile extends PureComponent { - static propTypes = { - currentUserId: PropTypes.string.isRequired, - theme: PropTypes.object.isRequired, - user: PropTypes.object.isRequired, - actions: PropTypes.shape({ - handleSendMessage: PropTypes.func.isRequired - }).isRequired - }; - - state = { - isFavorite: false - } - - getDisplayName = () => { - const {theme, user} = this.props; - const style = createStyleSheet(theme); - - const displayName = getFullName(user); - - if (displayName) { - return {displayName}; - } - - return null; - } - - buildDisplayBlock = (property) => { - const {theme, user} = this.props; - const style = createStyleSheet(theme); - - if (user.hasOwnProperty(property) && user[property].length > 0) { - return ( - - {property.toUpperCase()} - {user[property]} - - ); - } - - return null; - } - - sendMessage = () => { - this.props.actions.handleSendMessage(this.props.user.id); - } - - render() { - const {theme, user} = this.props; - const style = createStyleSheet(theme); - - return ( - - - - - {this.getDisplayName()} - {`@${user.username}`} - - - {this.buildDisplayBlock('username')} - {this.buildDisplayBlock('email')} - {this.buildDisplayBlock('position')} - - {this.props.currentUserId !== this.props.user.id && - - } - - - ); - } -} diff --git a/app/scenes/user_profile/user_profile_container.js b/app/scenes/user_profile/user_profile_container.js index 903bd1de5..bde83361e 100644 --- a/app/scenes/user_profile/user_profile_container.js +++ b/app/scenes/user_profile/user_profile_container.js @@ -4,15 +4,19 @@ import {bindActionCreators} from 'redux'; import {handleSendMessage} from 'app/actions/views/user_profile'; +import navigationSceneConnect from 'app/scenes/navigationSceneConnect'; import {getTheme} from 'app/selectors/preferences'; +import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; + import UserProfile from './user_profile'; -import navigationSceneConnect from '../navigationSceneConnect'; - function mapStateToProps(state, ownProps) { + const {config} = state.entities.general; + return { - currentUserId: state.entities.users.currentUserId, + config, + currentUserId: getCurrentUserId(state), user: state.entities.users.profiles[ownProps.userId], theme: getTheme(state) };