Improve in-app notifications design (#561)

This commit is contained in:
enahum 2017-05-22 21:38:45 -04:00 committed by GitHub
parent 26c365a00b
commit 516d184916
9 changed files with 230 additions and 39 deletions

View file

@ -37,7 +37,7 @@ export default class Root extends PureComponent {
navigator.showInAppNotification({
screen: 'Notification',
position: 'top',
autoDismissTimerSec: 15,
autoDismissTimerSec: 5,
dismissWithSwipe: true,
passProps: {
notification

View file

@ -128,6 +128,11 @@ export default class Mattermost {
notification = {
data: {
channel_id: deviceNotification.channel_id,
channel_name: deviceNotification.channel_name,
sender_id: deviceNotification.sender_id,
override_username: deviceNotification.override_username,
override_icon_url: deviceNotification.override_icon_url,
from_webhook: deviceNotification.from_webhook,
team_id: deviceNotification.team_id
},
message

View file

@ -61,7 +61,7 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('MoreChannels', () => wrapWithContextProvider(MoreChannels), store, Provider);
Navigation.registerComponent('MoreDirectMessages', () => wrapWithContextProvider(MoreDirectMessages), store, Provider);
Navigation.registerComponent('OptionsModal', () => wrapWithContextProvider(OptionsModal), store, Provider);
Navigation.registerComponent('Notification', () => Notification, store, Provider);
Navigation.registerComponent('Notification', () => wrapWithContextProvider(Notification), store, Provider);
Navigation.registerComponent('Root', () => Root, store, Provider);
Navigation.registerComponent('SelectServer', () => wrapWithContextProvider(SelectServer), store, Provider);
Navigation.registerComponent('SelectTeam', () => wrapWithContextProvider(SelectTeam), store, Provider);

View file

@ -7,11 +7,31 @@ import {connect} from 'react-redux';
import {goToNotification} from 'app/actions/views/root';
import {getTheme} from 'app/selectors/preferences';
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
import {getMyPreferences} from 'mattermost-redux/selectors/entities/preferences';
import {getUser} from 'mattermost-redux/selectors/entities/users';
import Notification from './notification';
function mapStateToProps(state, ownProps) {
const {data} = ownProps.notification;
let user;
if (data.sender_id) {
user = getUser(state, data.sender_id);
}
let channel;
if (data.channel_id) {
channel = getChannel(state, data.channel_id);
}
return {
...ownProps,
config: state.entities.general.config,
channel,
user,
myPreferences: getMyPreferences(state),
theme: getTheme(state)
};
}

View file

@ -13,18 +13,30 @@ import {
View
} from 'react-native';
import FormattedText from 'app/components/formatted_text';
import ProfilePicture from 'app/components/profile_picture';
import {changeOpacity} from 'app/utils/theme';
import icon from 'assets/images/icon.png';
import {isDirectChannel} from 'mattermost-redux/utils/channel_utils';
import {displayUsername} from 'mattermost-redux/utils/user_utils';
import logo from 'assets/images/icon.png';
import webhookIcon from 'assets/images/icons/webhook.jpg';
const IMAGE_SIZE = 33;
export default class Notification extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
goToNotification: PropTypes.func.isRequired
}).isRequired,
channel: PropTypes.object,
config: PropTypes.object,
notification: PropTypes.object.isRequired,
myPreferences: PropTypes.object.isRequired,
navigator: PropTypes.object,
theme: PropTypes.object.isRequired
theme: PropTypes.object.isRequired,
user: PropTypes.object
};
notificationTapped = () => {
@ -46,57 +58,209 @@ export default class Notification extends PureComponent {
}
};
render() {
return (
<View style={style.container}>
<TouchableOpacity
style={{flex: 1, flexDirection: 'row'}}
onPress={this.notificationTapped}
>
<View>
<Image
source={icon}
style={style.icon}
/>
</View>
<View style={{flex: 1, flexDirection: 'row', alignSelf: 'stretch', justifyContent: 'flex-start', marginLeft: 10}} >
<Text
numberOfLines={2}
style={style.message}
>
{this.props.notification.message}
</Text>
</View>
</TouchableOpacity>
</View>
getNotificationIcon = () => {
const {config, notification, user} = this.props;
const {data} = notification;
let icon = (
<Image
source={logo}
style={style.icon}
/>
);
if (data.from_webhook && config.EnablePostIconOverride === 'true') {
const wsIcon = data.override_icon_url ? {uri: data.override_icon_url} : webhookIcon;
icon = (
<Image
source={wsIcon}
style={style.icon}
/>
);
} else if (user) {
icon = (
<ProfilePicture
user={user}
size={IMAGE_SIZE}
/>
);
}
return icon;
};
getNotificationTitle = (titleText) => {
const {channel} = this.props;
let title = (
<Text
numberOfLines={1}
ellipsizeMode='tail'
style={style.title}
>
{titleText}
</Text>
);
const userName = this.getNotificationUserName();
if (userName && channel) {
let channelName;
let inText;
if (!isDirectChannel(channel)) {
inText = (
<FormattedText
id='mobile.notification.in'
defaultMessage=' in '
style={style.message}
/>
);
channelName = (
<Text
numberOfLines={1}
ellipsizeMode='tail'
style={[style.title, style.channelName]}
>
{channel.display_name}
</Text>
);
}
title = (
<View style={{flex: 1, flexDirection: 'row'}}>
{userName}
{inText}
{channelName}
</View>
);
}
return title;
};
getNotificationUserName = () => {
const {config, notification, myPreferences, user} = this.props;
const {data} = notification;
let userName;
if (data.override_username && config.EnablePostUsernameOverride === 'true') {
userName = (
<Text style={style.title}>
{data.override_username}
</Text>
);
} else if (user) {
userName = (
<Text style={style.title}>
{displayUsername(user, myPreferences)}
</Text>
);
}
return userName;
};
render() {
const {message} = this.props.notification;
if (message) {
const msg = message.split(':');
const titleText = msg.shift();
const messageText = msg.join('').trim();
const title = this.getNotificationTitle(titleText);
const icon = this.getNotificationIcon();
return (
<View style={style.container}>
<TouchableOpacity
style={{flex: 1, flexDirection: 'row'}}
onPress={this.notificationTapped}
>
<View style={style.iconContainer}>
{icon}
</View>
<View style={style.textContainer}>
{title}
<View style={{flex: 1}}>
<Text
numberOfLines={1}
ellipsizeMode='tail'
style={style.message}
>
{messageText}
</Text>
</View>
</View>
</TouchableOpacity>
</View>
);
}
return null;
}
}
const style = StyleSheet.create({
container: {
alignItems: 'center',
alignItems: 'flex-start',
backgroundColor: changeOpacity('#000', 0.9),
flexDirection: 'row',
justifyContent: 'flex-start',
padding: 10,
paddingHorizontal: 10,
width: Dimensions.get('window').width,
...Platform.select({
android: {
height: 60
height: 68
},
ios: {
height: 80
height: 88
}
})
},
iconContainer: {
...Platform.select({
android: {
paddingTop: 17
},
ios: {
paddingTop: 37
}
})
},
icon: {
borderRadius: 15,
height: 30,
width: 30
borderRadius: (IMAGE_SIZE / 2),
height: IMAGE_SIZE,
width: IMAGE_SIZE
},
textContainer: {
flex: 1,
flexDirection: 'column',
alignSelf: 'stretch',
alignItems: 'flex-start',
marginLeft: 10,
...Platform.select({
android: {
marginTop: 17,
height: 50
},
ios: {
paddingTop: 37
}
})
},
title: {
color: '#FFFFFF',
fontSize: 14,
fontWeight: '600'
},
channelName: {
alignSelf: 'stretch',
alignItems: 'flex-start',
flex: 1
},
message: {
color: 'white',
fontSize: 13
color: '#FFFFFF',
fontSize: 14
}
});

View file

@ -99,7 +99,7 @@ export default class SelectTeam extends PureComponent {
/>
<FormattedText
style={GlobalStyles.subheader}
id='signup_team.choose'
id='mobile.select_team.choose'
defaultMessage='Your teams:'
/>
{content}

View file

@ -156,7 +156,7 @@ class Settings extends PureComponent {
{showTeamSelection &&
<SettingsItem
defaultMessage='Team Selection'
i18nId='sidebar_right_menu.switch_team'
i18nId='mobile.settings.team_selection'
iconName='ios-people'
iconType='ion'
onPress={() => this.handlePress(this.goToSelectTeam)}

View file

@ -1712,6 +1712,7 @@
"mobile.loading_members": "Loading Members...",
"mobile.loading_posts": "Loading Messages...",
"mobile.login_options.choose_title": "Choose your login method",
"mobile.notification.in": " in ",
"mobile.offlineIndicator.connected": "Connected",
"mobile.offlineIndicator.connecting": "Connecting...",
"mobile.offlineIndicator.offline": "No internet connection",
@ -1743,6 +1744,7 @@
"mobile.routes.thread_dm": "Direct Message Thread",
"mobile.routes.user_profile": "Profile",
"mobile.routes.user_profile.send_message": "Send Message",
"mobile.select_team.choose": "Your teams:",
"mobile.server_ping_failed": "Cannot connect to the server. Please check your server URL and internet connection.",
"mobile.server_url.invalid_format": "URL must start with http:// or https://",
"mobile.session_expired": "Session Expired: Please log in to continue receiving notifications.",

View file

@ -3526,7 +3526,7 @@ makeerror@1.0.x:
mattermost-redux@mattermost/mattermost-redux#master:
version "0.0.1"
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/dad1a74263edb36271bf422cef977a06ac56815c"
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/6990daa5f010539075fe3657d379a6cd131b971a"
dependencies:
deep-equal "1.0.1"
harmony-reflect "1.5.1"