[ICU-579] Display Announcement banner (#1428)

* Announcement banner

* Feedback review
This commit is contained in:
enahum 2018-02-13 12:23:11 -03:00 committed by Harrison Healey
parent 6bc1f4a2db
commit 27c764d288
10 changed files with 219 additions and 2 deletions

View file

@ -0,0 +1,11 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {ViewTypes} from 'app/constants';
export function dismissBanner(text) {
return {
type: ViewTypes.ANNOUNCEMENT_BANNER,
data: text
};
}

View file

@ -0,0 +1,144 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Alert,
Animated,
StyleSheet,
Text,
TouchableOpacity
} from 'react-native';
import {intlShape} from 'react-intl';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
const {View: AnimatedView} = Animated;
export default class AnnouncementBanner extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
dismissBanner: PropTypes.func.isRequired
}).isRequired,
allowDismissal: PropTypes.bool,
bannerColor: PropTypes.string,
bannerDismissed: PropTypes.bool,
bannerEnabled: PropTypes.bool,
bannerText: PropTypes.string,
bannerTextColor: PropTypes.string
};
static contextTypes = {
intl: intlShape
};
state = {
bannerHeight: new Animated.Value(0)
};
componentWillMount() {
const {bannerDismissed, bannerEnabled, bannerText} = this.props;
const showBanner = bannerEnabled && !bannerDismissed && Boolean(bannerText);
this.toggleBanner(showBanner);
}
componentWillReceiveProps(nextProps) {
if (this.props.bannerText !== nextProps.bannerText ||
this.props.bannerEnabled !== nextProps.bannerEnabled ||
this.props.bannerDismissed !== nextProps.bannerDismissed) {
const showBanner = nextProps.bannerEnabled && !nextProps.bannerDismissed && Boolean(nextProps.bannerText);
this.toggleBanner(showBanner);
}
}
handleDismiss = () => {
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'})
}];
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}
);
};
toggleBanner = (show = true) => {
const value = show ? 38 : 0;
Animated.timing(this.state.bannerHeight, {
toValue: value,
duration: 350
}).start();
};
render() {
const {bannerHeight} = this.state;
const bannerStyle = {
backgroundColor: this.props.bannerColor,
height: bannerHeight
};
const bannerTextStyle = {
color: this.props.bannerTextColor
};
return (
<AnimatedView
style={[style.bannerContainer, bannerStyle]}
>
<TouchableOpacity
onPress={this.handlePress}
style={style.wrapper}
>
<Text
ellipsizeMode='tail'
numberOfLines={1}
style={[style.bannerText, bannerTextStyle]}
>
{this.props.bannerText}
</Text>
<MaterialIcons
color={this.props.bannerTextColor}
name='info'
size={16}
/>
</TouchableOpacity>
</AnimatedView>
);
}
}
const style = StyleSheet.create({
bannerContainer: {
paddingHorizontal: 10,
position: 'absolute',
top: 0,
overflow: 'hidden',
width: '100%'
},
wrapper: {
alignItems: 'center',
flex: 1,
flexDirection: 'row'
},
bannerText: {
flex: 1,
fontSize: 14,
marginRight: 5
}
});

View file

@ -0,0 +1,36 @@
// Copyright (c) 2018-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 AnnouncementBanner from './announcement_banner';
function mapStateToProps(state) {
const config = getConfig(state);
const license = getLicense(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
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
dismissBanner
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AnnouncementBanner);

View file

@ -61,7 +61,8 @@ const ViewTypes = keyMirror({
SET_LAST_UPGRADE_CHECK: null,
ADD_RECENT_EMOJI: null,
EXTENSION_SELECTED_TEAM_ID: null
EXTENSION_SELECTED_TEAM_ID: null,
ANNOUNCEMENT_BANNER: null
});
export default {

View file

@ -0,0 +1,17 @@
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {UserTypes} from 'mattermost-redux/action_types';
import {ViewTypes} from 'app/constants';
export default function banner(state = '', action) {
switch (action.type) {
case ViewTypes.ANNOUNCEMENT_BANNER:
return action.data;
case UserTypes.LOGOUT_SUCCESS:
return '';
default:
return state;
}
}

View file

@ -3,6 +3,7 @@
import {combineReducers} from 'redux';
import announcement from './announcement';
import channel from './channel';
import clientUpgrade from './client_upgrade';
import extension from './extension';
@ -17,6 +18,7 @@ import team from './team';
import thread from './thread';
export default combineReducers({
announcement,
channel,
clientUpgrade,
extension,

View file

@ -9,6 +9,7 @@ import {
View
} from 'react-native';
import AnnouncementBanner from 'app/components/announcement_banner';
import PostList from 'app/components/post_list';
import PostListRetry from 'app/components/post_list_retry';
import RetryBarIndicator from 'app/components/retry_bar_indicator';
@ -159,6 +160,7 @@ export default class ChannelPostList extends PureComponent {
return (
<View style={style.container}>
{component}
<AnnouncementBanner/>
<RetryBarIndicator/>
</View>
);

View file

@ -46,7 +46,7 @@ const setTransforms = [
export default function configureAppStore(initialState) {
const viewsBlackListFilter = createBlacklistFilter(
'views',
['extension', 'login', 'root']
['announcement', 'extension', 'login', 'root']
);
const typingBlackListFilter = createBlacklistFilter(

View file

@ -319,6 +319,7 @@ function cleanupState(action, keepCurrent = false) {
users: payload.entities.users
},
views: {
announcement: payload.views.announcement,
...resetPayload.views,
channel: {
...resetPayload.views.channel,

View file

@ -1940,6 +1940,9 @@
"mobile.android.photos_permission_denied_title": "Photo library access is required",
"mobile.android.videos_permission_denied_description": "To upload videos from your library, please change your permission settings.",
"mobile.android.videos_permission_denied_title": "Video library access is required",
"mobile.announcement_banner.title": "Announcement",
"mobile.announcement_banner.ok": "OK",
"mobile.announcement_banner.dismiss": "Dismiss",
"mobile.channel.markAsRead": "Mark As Read",
"mobile.channel_drawer.search": "Jump to...",
"mobile.channel_info.alertMessageDeleteChannel": "Are you sure you want to delete the {term} {name}?",