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 9163f2a2e..f40c2d932 100644 --- a/app/components/announcement_banner/__snapshots__/announcement_banner.test.js.snap +++ b/app/components/announcement_banner/__snapshots__/announcement_banner.test.js.snap @@ -10,6 +10,8 @@ ShallowWrapper { bannerEnabled={true} bannerText="Banner Text" bannerTextColor="#fff" + navigator={Object {}} + theme={Object {}} />, Symbol(enzyme.__renderer__): Object { "batchedUpdates": [Function], @@ -362,6 +364,8 @@ ShallowWrapper { bannerEnabled={false} bannerText="Banner Text" bannerTextColor="#fff" + navigator={Object {}} + theme={Object {}} />, Symbol(enzyme.__renderer__): Object { "batchedUpdates": [Function], diff --git a/app/components/announcement_banner/announcement_banner.test.js b/app/components/announcement_banner/announcement_banner.test.js index db0e313e8..1dfcc393a 100644 --- a/app/components/announcement_banner/announcement_banner.test.js +++ b/app/components/announcement_banner/announcement_banner.test.js @@ -17,6 +17,8 @@ describe('AnnouncementBanner', () => { bannerEnabled: true, bannerText: 'Banner Text', bannerTextColor: '#fff', + navigator: {}, + theme: {}, }; test('should match snapshot', () => { diff --git a/app/components/channel_icon.js b/app/components/channel_icon.js index e814eba39..59c1a10e3 100644 --- a/app/components/channel_icon.js +++ b/app/components/channel_icon.js @@ -25,6 +25,7 @@ export default class ChannelIcon extends React.PureComponent { teammateDeletedAt: PropTypes.number, theme: PropTypes.object.isRequired, type: PropTypes.string.isRequired, + isArchived: PropTypes.bool.isRequired, }; static defaultProps = { @@ -45,6 +46,7 @@ export default class ChannelIcon extends React.PureComponent { teammateDeletedAt, theme, type, + isArchived, } = this.props; const style = getStyleSheet(theme); @@ -76,8 +78,14 @@ export default class ChannelIcon extends React.PureComponent { } let icon; - - if (type === General.OPEN_CHANNEL) { + if (isArchived) { + icon = ( + + ); + } else if (type === General.OPEN_CHANNEL) { icon = ( { + const {defaultChannel, channelId, onCloseChannel} = this.props; + const {setChannelDisplayName, setChannelLoading} = this.props.actions; + setChannelLoading(true); + setChannelDisplayName(defaultChannel.display_name); + EventEmitter.emit('switch_channel', defaultChannel, channelId); + if (onCloseChannel) { + onCloseChannel(); + } + }; + + archivedView = (theme, style) => { + return ( + + + ); + }; + render() { const {intl} = this.context; const { @@ -463,6 +503,7 @@ export default class PostTextbox extends PureComponent { navigator, rootId, theme, + channelIsArchived, } = this.props; const style = getStyleSheet(theme); @@ -528,7 +569,7 @@ export default class PostTextbox extends PureComponent { value={this.state.value} rootId={rootId} /> - + {!channelIsArchived && {!channelIsReadOnly && attachmentButton} {this.renderSendButton()} - + } + {channelIsArchived && this.archivedView(theme, style)} ); } @@ -624,5 +666,31 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { alignItems: 'center', justifyContent: 'center', }, + archivedWrapper: { + paddingLeft: 20, + paddingRight: 20, + paddingTop: 10, + paddingBottom: 10, + borderTopWidth: 1, + borderTopColor: changeOpacity(theme.centerChannelColor, 0.20), + }, + archivedText: { + textAlign: 'center', + color: theme.centerChannelColor, + }, + closeButton: { + backgroundColor: theme.buttonBg, + alignItems: 'center', + paddingTop: 5, + paddingBottom: 5, + borderRadius: 4, + marginTop: 10, + height: 40, + }, + closeButtonText: { + marginTop: 7, + color: 'white', + fontWeight: 'bold', + }, }; }); diff --git a/app/components/reactions/index.js b/app/components/reactions/index.js index 01defaa1b..4ec98224d 100644 --- a/app/components/reactions/index.js +++ b/app/components/reactions/index.js @@ -23,10 +23,14 @@ function makeMapStateToProps() { const post = getPost(state, ownProps.postId); const channel = getChannel(state, post.channel_id) || {}; const teamId = channel.team_id; + const channelIsArchived = channel.delete_at !== 0; let canAddReaction = true; let canRemoveReaction = true; - if (hasNewPermissions(state)) { + if (channelIsArchived) { + canAddReaction = false; + canRemoveReaction = false; + } else if (hasNewPermissions(state)) { canAddReaction = haveIChannelPermission(state, { team: teamId, channel: post.channel_id, diff --git a/app/components/reactions/reactions.js b/app/components/reactions/reactions.js index bf844da4c..d2d5c9296 100644 --- a/app/components/reactions/reactions.js +++ b/app/components/reactions/reactions.js @@ -68,25 +68,28 @@ export default class Reactions extends PureComponent { }; render() { - const {position, reactions} = this.props; + const {position, reactions, canAddReaction} = this.props; const styles = getStyleSheet(this.props.theme); if (!reactions.size) { return null; } - const addMoreReactions = ( - - - - ); + let addMoreReactions = null; + if (canAddReaction) { + addMoreReactions = ( + + + + ); + } const reactionElements = []; switch (position) { diff --git a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap index b48b1eeb9..0fcd3bb59 100644 --- a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap +++ b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap @@ -9,6 +9,7 @@ ShallowWrapper { currentChannelId="current_channel_id" displayName="display_name" fake={false} + isArchived={false} isChannelMuted={false} isMyUser={true} isUnread={true} @@ -81,6 +82,7 @@ ShallowWrapper { ); diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js index ac661e812..3ce307c00 100644 --- a/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js +++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.test.js @@ -34,6 +34,7 @@ describe('ChannelItem', () => { sidebarTextHoverBg: '#aaa', }, unreadMsgs: 1, + isArchived: false, }; test('should match snapshot', () => { diff --git a/app/components/sidebars/main/channels_list/channel_item/index.js b/app/components/sidebars/main/channels_list/channel_item/index.js index f70703d23..2e0e1435d 100644 --- a/app/components/sidebars/main/channels_list/channel_item/index.js +++ b/app/components/sidebars/main/channels_list/channel_item/index.js @@ -72,6 +72,7 @@ function makeMapStateToProps() { theme: getTheme(state), type: channel.type, unreadMsgs, + isArchived: channel.delete_at !== 0, }; }; } diff --git a/app/screens/channel/channel_nav_bar/channel_title/channel_title.js b/app/screens/channel/channel_nav_bar/channel_title/channel_title.js index d4fee0e86..f50f7f861 100644 --- a/app/screens/channel/channel_nav_bar/channel_title/channel_title.js +++ b/app/screens/channel/channel_nav_bar/channel_title/channel_title.js @@ -20,6 +20,7 @@ export default class ChannelTitle extends PureComponent { isChannelMuted: PropTypes.bool, onPress: PropTypes.func, theme: PropTypes.object, + isArchived: PropTypes.bool, }; static defaultProps = { @@ -28,6 +29,19 @@ export default class ChannelTitle extends PureComponent { theme: {}, }; + archiveIcon(style) { + let content = null; + if (this.props.isArchived) { + content = ( + + ); + } + return content; + } + render() { const {currentChannelName, displayName, isChannelMuted, onPress, theme} = this.props; const channelName = displayName || currentChannelName; @@ -60,6 +74,7 @@ export default class ChannelTitle extends PureComponent { onPress={onPress} > + {this.archiveIcon(style)} { opacity: 0.6, marginLeft: 0, }, + archiveIcon: { + fontSize: 17, + color: theme.sidebarHeaderTextColor, + paddingRight: 7, + }, }; }); diff --git a/app/screens/channel/channel_nav_bar/channel_title/index.js b/app/screens/channel/channel_nav_bar/channel_title/index.js index d63620da4..cdf290a20 100644 --- a/app/screens/channel/channel_nav_bar/channel_title/index.js +++ b/app/screens/channel/channel_nav_bar/channel_title/index.js @@ -15,6 +15,7 @@ function mapStateToProps(state) { return { currentChannelName: currentChannel ? currentChannel.display_name : '', + isArchived: currentChannel ? currentChannel.delete_at !== 0 : false, displayName: state.views.channel.displayName, isChannelMuted: isChannelMuted(myChannelMember), theme: getTheme(state), diff --git a/app/screens/channel_info/channel_info.js b/app/screens/channel_info/channel_info.js index 25dd6a4bf..7a0797cfe 100644 --- a/app/screens/channel_info/channel_info.js +++ b/app/screens/channel_info/channel_info.js @@ -328,20 +328,105 @@ export default class ChannelInfo extends PureComponent { return isDirectMessage || isGroupMessage; }; + actionsRows = (style, channelIsArchived) => { + const { + currentChannelMemberCount, + canManageUsers, + canEditChannel, + theme, + } = this.props; + + if (channelIsArchived) { + return null; + } + + return ( + + + + { + + /** + true} + defaultMessage='Notification Preferences' + icon='bell-o' + textId='channel_header.notificationPreferences' + theme={theme} + /> + + **/ + } + {this.renderViewOrManageMembersRow() && + + + + + } + {canManageUsers && + + + + + } + {canEditChannel && ( + + + + + )} + + ); + }; + render() { const { canDeleteChannel, currentChannel, currentChannelCreatorName, currentChannelMemberCount, - canManageUsers, - canEditChannel, navigator, status, theme, } = this.props; const style = getStyleSheet(theme); + const channelIsArchived = currentChannel.delete_at !== 0; let i18nId; let defaultMessage; @@ -363,93 +448,24 @@ export default class ChannelInfo extends PureComponent { style={style.scrollView} > {currentChannel.hasOwnProperty('id') && - + } - - - - { - - /** - true} - defaultMessage='Notification Preferences' - icon='bell-o' - textId='channel_header.notificationPreferences' - theme={theme} - /> - - **/ - } - {this.renderViewOrManageMembersRow() && - - - - - } - {canManageUsers && - - - - - } - {canEditChannel && ( - - - - - )} + {this.actionsRows(style, channelIsArchived)} {this.renderLeaveOrDeleteChannelRow() && - } - {this.renderLeaveOrDeleteChannelRow() && canDeleteChannel && + {this.renderLeaveOrDeleteChannelRow() && canDeleteChannel && !channelIsArchived && } {this.renderCloseDirect() && - - - + + + } diff --git a/app/screens/permalink/index.js b/app/screens/permalink/index.js index 7480e13da..fd48f4c32 100644 --- a/app/screens/permalink/index.js +++ b/app/screens/permalink/index.js @@ -44,6 +44,7 @@ function makeMapStateToProps() { return { channelId: channel ? channel.id : '', + channelIsArchived: channel ? channel.delete_at !== 0 : false, channelName: channel ? channel.display_name : '', channelTeamId: channel ? channel.team_id : '', currentTeamId: getCurrentTeamId(state), diff --git a/app/screens/permalink/permalink.js b/app/screens/permalink/permalink.js index 7c7d29a3b..a66969581 100644 --- a/app/screens/permalink/permalink.js +++ b/app/screens/permalink/permalink.js @@ -12,6 +12,7 @@ import { import {intlShape} from 'react-intl'; import * as Animatable from 'react-native-animatable'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; +import AwesomeIcon from 'react-native-vector-icons/FontAwesome'; import {General} from 'mattermost-redux/constants'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; @@ -59,6 +60,7 @@ export default class Permalink extends PureComponent { setChannelLoading: PropTypes.func.isRequired, }).isRequired, channelId: PropTypes.string, + channelIsArchived: PropTypes.bool, channelName: PropTypes.string, channelTeamId: PropTypes.string, currentTeamId: PropTypes.string.isRequired, @@ -301,6 +303,20 @@ export default class Permalink extends PureComponent { } }; + archivedIcon = (style) => { + let ico = null; + if (this.props.channelIsArchived) { + ico = ( + + {' '} + ); + } + return ico; + }; + render() { const { currentUserId, @@ -386,6 +402,7 @@ export default class Permalink extends PureComponent { numberOfLines={1} style={style.title} > + {this.archivedIcon(style)} {title} @@ -492,5 +509,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { color: changeOpacity(theme.centerChannelColor, 0.4), fontSize: 15, }, + archiveIcon: { + color: theme.centerChannelColor, + fontSize: 16, + paddingRight: 20, + }, }; }); diff --git a/app/screens/search/index.js b/app/screens/search/index.js index 19c7e51a4..46e4727b9 100644 --- a/app/screens/search/index.js +++ b/app/screens/search/index.js @@ -6,7 +6,7 @@ import {connect} from 'react-redux'; import {selectFocusedPostId, selectPost} from 'mattermost-redux/actions/posts'; import {clearSearch, removeSearchTerms, searchPosts} from 'mattermost-redux/actions/search'; -import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'; +import {getCurrentChannelId, filterPostIds} from 'mattermost-redux/selectors/entities/channels'; import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; @@ -19,9 +19,11 @@ import Search from './search'; function makeMapStateToProps() { const preparePostIds = makePreparePostIdsForSearchPosts(); + const filterPostIdsByDeletedChannels = filterPostIds((c) => c && c.delete_at !== 0); return (state) => { const postIds = preparePostIds(state, state.entities.search.results); + const currentTeamId = getCurrentTeamId(state); const currentChannelId = getCurrentChannelId(state); const {recent} = state.entities.search; @@ -32,6 +34,7 @@ function makeMapStateToProps() { currentChannelId, isLandscape: isLandscape(state), postIds, + archivedPostIds: filterPostIdsByDeletedChannels(state, postIds), recent: recent[currentTeamId], searchingStatus: searchRequest.status, theme: getTheme(state), diff --git a/app/screens/search/search.js b/app/screens/search/search.js index 7e17ba525..c398e120a 100644 --- a/app/screens/search/search.js +++ b/app/screens/search/search.js @@ -56,10 +56,10 @@ export default class Search extends PureComponent { selectPost: PropTypes.func.isRequired, }).isRequired, currentTeamId: PropTypes.string.isRequired, - currentChannelId: PropTypes.string.isRequired, isLandscape: PropTypes.bool.isRequired, navigator: PropTypes.object, postIds: PropTypes.array, + archivedPostIds: PropTypes.arrayOf(PropTypes.string), recent: PropTypes.array.isRequired, searchingStatus: PropTypes.string, theme: PropTypes.object.isRequired, @@ -68,6 +68,7 @@ export default class Search extends PureComponent { static defaultProps = { postIds: [], recent: [], + archivedPostIds: [], }; static contextTypes = { @@ -294,6 +295,30 @@ export default class Search extends PureComponent { ); }; + archivedIndicator = (postID, style) => { + const channelIsArchived = this.props.archivedPostIds.includes(postID); + let archivedIndicator = null; + if (channelIsArchived) { + archivedIndicator = ( + + + + {' '} + + + + ); + } + return archivedIndicator; + }; + renderPost = ({item, index}) => { const {postIds, theme} = this.props; const {managedConfig} = this.state; @@ -325,6 +350,7 @@ export default class Search extends PureComponent { return ( + {this.archivedIndicator(postIds[index], style)} { @@ -749,6 +775,16 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { searching: { marginTop: 65, }, + archivedIndicator: { + alignItems: 'flex-end', + width: 150, + alignSelf: 'flex-end', + marginTop: -17, + marginRight: 10, + }, + archivedText: { + color: changeOpacity(theme.centerChannelColor, 0.4), + }, }; }); diff --git a/app/screens/thread/index.js b/app/screens/thread/index.js index 2a6820be1..099c7b32a 100644 --- a/app/screens/thread/index.js +++ b/app/screens/thread/index.js @@ -27,6 +27,7 @@ function makeMapStateToProps() { rootId: ownProps.rootId, postIds: getPostIdsForThread(state, ownProps.rootId), theme: getTheme(state), + channelIsArchived: channel ? channel.delete_at !== 0 : false, }; }; } diff --git a/app/screens/thread/thread.js b/app/screens/thread/thread.js index d1e9ad988..163af06db 100644 --- a/app/screens/thread/thread.js +++ b/app/screens/thread/thread.js @@ -30,6 +30,7 @@ class Thread extends PureComponent { rootId: PropTypes.string.isRequired, theme: PropTypes.object.isRequired, postIds: PropTypes.array.isRequired, + channelIsArchived: PropTypes.bool, }; state = {}; @@ -95,6 +96,23 @@ class Thread extends PureComponent { return null; } + onCloseChannel = () => { + this.props.navigator.resetTo({ + screen: 'Channel', + title: '', + animated: false, + backButtonTitle: '', + navigatorStyle: { + animated: true, + animationType: 'fade', + navBarHidden: true, + statusBarHidden: false, + statusBarHideWithNavBar: false, + screenBackgroundColor: 'transparent', + }, + }); + } + render() { const { channelId, @@ -103,6 +121,7 @@ class Thread extends PureComponent { postIds, rootId, theme, + channelIsArchived, } = this.props; const style = getStyle(theme); @@ -127,9 +146,11 @@ class Thread extends PureComponent { /> {this.hasRootPost() && } diff --git a/package-lock.json b/package-lock.json index d4211ff02..202e0fbb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10108,8 +10108,8 @@ } }, "mattermost-redux": { - "version": "github:mattermost/mattermost-redux#707dc6926ae3eecb4c7b4ae08dca9be17b933568", - "from": "github:mattermost/mattermost-redux#707dc6926ae3eecb4c7b4ae08dca9be17b933568", + "version": "github:mattermost/mattermost-redux#12353bc88f20dde53b6191747790c6a82b8dd527", + "from": "github:mattermost/mattermost-redux#12353bc88f20dde53b6191747790c6a82b8dd527", "requires": { "deep-equal": "1.0.1", "eslint-plugin-header": "1.2.0", diff --git a/package.json b/package.json index 4c82e5c66..2e4ba775a 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "intl": "1.2.5", "jail-monkey": "1.0.0", "jsc-android": "216113.0.3", - "mattermost-redux": "github:mattermost/mattermost-redux#707dc6926ae3eecb4c7b4ae08dca9be17b933568", + "mattermost-redux": "github:mattermost/mattermost-redux#12353bc88f20dde53b6191747790c6a82b8dd527", "mime-db": "1.33.0", "prop-types": "15.6.1", "react": "16.3.2",