PLT-5493 Add a profile scene that shows user information (#287)
* PLT-5493 React Native: Add a profile scene that shows user information * Review feedback
This commit is contained in:
parent
567310b9d6
commit
b09dbbb79c
16 changed files with 514 additions and 43 deletions
|
|
@ -102,6 +102,20 @@ export function goToThread(channelId, rootId) {
|
|||
};
|
||||
}
|
||||
|
||||
export function goToUserProfile(userId) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: {
|
||||
...Routes.UserProfile,
|
||||
props: {
|
||||
userId
|
||||
}
|
||||
}
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function openChannelDrawer() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
|
|
|
|||
12
app/actions/views/user_profile.js
Normal file
12
app/actions/views/user_profile.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {makeDirectChannel} from 'app/actions/views/more_dms';
|
||||
import {NavigationTypes} from 'app/constants';
|
||||
|
||||
export function handleSendMessage(otherUserId) {
|
||||
return async (dispatch, getState) => {
|
||||
await makeDirectChannel(otherUserId)(dispatch, getState);
|
||||
dispatch({type: NavigationTypes.NAVIGATION_POP_TO_INDEX, index: 0}, getState);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import {
|
||||
Image,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
|
|
@ -89,19 +90,22 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
});
|
||||
});
|
||||
|
||||
export default class Post extends React.Component {
|
||||
export default class Post extends Component {
|
||||
static propTypes = {
|
||||
style: View.propTypes.style,
|
||||
post: React.PropTypes.object.isRequired,
|
||||
user: React.PropTypes.object,
|
||||
displayName: React.PropTypes.string,
|
||||
renderReplies: React.PropTypes.bool,
|
||||
isFirstReply: React.PropTypes.bool,
|
||||
isLastReply: React.PropTypes.bool,
|
||||
commentedOnPost: React.PropTypes.object,
|
||||
commentedOnDisplayName: React.PropTypes.string,
|
||||
theme: React.PropTypes.object.isRequired,
|
||||
onPress: React.PropTypes.func
|
||||
post: PropTypes.object.isRequired,
|
||||
user: PropTypes.object,
|
||||
displayName: PropTypes.string,
|
||||
renderReplies: PropTypes.bool,
|
||||
isFirstReply: PropTypes.bool,
|
||||
isLastReply: PropTypes.bool,
|
||||
commentedOnPost: PropTypes.object,
|
||||
commentedOnDisplayName: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onPress: PropTypes.func,
|
||||
actions: PropTypes.shape({
|
||||
goToUserProfile: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
handlePress = () => {
|
||||
|
|
@ -177,6 +181,10 @@ export default class Post extends React.Component {
|
|||
return attachments;
|
||||
}
|
||||
|
||||
viewUserProfile = () => {
|
||||
this.props.actions.goToUserProfile(this.props.user.id);
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
const PROFILE_PICTURE_SIZE = 32;
|
||||
|
|
@ -199,6 +207,7 @@ export default class Post extends React.Component {
|
|||
<FormattedText
|
||||
id='post_info.system'
|
||||
defaultMessage='System'
|
||||
style={style.displayName}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
@ -217,23 +226,36 @@ export default class Post extends React.Component {
|
|||
</View>
|
||||
);
|
||||
|
||||
displayName = this.props.post.props.override_username;
|
||||
displayName = (
|
||||
<Text style={style.displayName}>
|
||||
{this.props.post.props.override_username}
|
||||
</Text>
|
||||
);
|
||||
messageStyle = [style.message, style.webhookMessage];
|
||||
} else {
|
||||
profilePicture = (
|
||||
<ProfilePicture
|
||||
user={this.props.user}
|
||||
size={PROFILE_PICTURE_SIZE}
|
||||
/>
|
||||
<TouchableOpacity onPress={this.viewUserProfile}>
|
||||
<ProfilePicture
|
||||
user={this.props.user}
|
||||
size={PROFILE_PICTURE_SIZE}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
if (this.props.displayName) {
|
||||
displayName = this.props.displayName;
|
||||
displayName = (
|
||||
<TouchableOpacity onPress={this.viewUserProfile}>
|
||||
<Text style={style.displayName}>
|
||||
{this.props.displayName}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
} else {
|
||||
displayName = (
|
||||
<FormattedText
|
||||
id='channel_loader.someone'
|
||||
defaultMessage='Someone'
|
||||
style={style.displayName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -250,9 +272,7 @@ export default class Post extends React.Component {
|
|||
</View>
|
||||
<View style={style.rightColumn}>
|
||||
<View style={style.postInfoContainer}>
|
||||
<Text style={style.displayName}>
|
||||
{displayName}
|
||||
</Text>
|
||||
{displayName}
|
||||
<Text style={style.time}>
|
||||
<FormattedTime value={this.props.post.create_at}/>
|
||||
</Text>
|
||||
|
|
@ -279,9 +299,7 @@ export default class Post extends React.Component {
|
|||
{this.renderReplyBar(style)}
|
||||
<View style={style.rightColumn}>
|
||||
<View style={style.postInfoContainer}>
|
||||
<Text style={style.displayName}>
|
||||
{displayName}
|
||||
</Text>
|
||||
{displayName}
|
||||
<Text style={style.time}>
|
||||
<FormattedTime value={this.props.post.create_at}/>
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {getMyPreferences, getTheme} from 'service/selectors/entities/preferences';
|
||||
import {goToUserProfile} from 'app/actions/navigation';
|
||||
import {getUser} from 'service/selectors/entities/users';
|
||||
import {displayUsername} from 'service/utils/user_utils';
|
||||
|
||||
|
|
@ -24,5 +26,12 @@ function mapStateToProps(state, ownProps) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Post);
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToUserProfile
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Post);
|
||||
|
|
|
|||
|
|
@ -13,24 +13,21 @@ import Client from 'service/client';
|
|||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
statusContainer: {
|
||||
width: 14,
|
||||
height: 14,
|
||||
statusWrapper: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
overflow: 'hidden',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 8,
|
||||
borderWidth: 1.5,
|
||||
borderColor: theme.centerChannelBg
|
||||
},
|
||||
statusContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden'
|
||||
},
|
||||
status: {
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.centerChannelBg
|
||||
},
|
||||
online: {
|
||||
|
|
@ -54,6 +51,9 @@ const statusToIcon = {
|
|||
export default class ProfilePicture extends React.PureComponent {
|
||||
static propTypes = {
|
||||
size: React.PropTypes.number,
|
||||
statusBorderWidth: React.PropTypes.number,
|
||||
statusSize: React.PropTypes.number,
|
||||
statusIconSize: React.PropTypes.number,
|
||||
user: React.PropTypes.object,
|
||||
status: React.PropTypes.string,
|
||||
theme: React.PropTypes.object.isRequired,
|
||||
|
|
@ -63,7 +63,10 @@ export default class ProfilePicture extends React.PureComponent {
|
|||
};
|
||||
|
||||
static defaultProps = {
|
||||
size: 128
|
||||
size: 128,
|
||||
statusBorderWidth: 2,
|
||||
statusSize: 14,
|
||||
statusIconSize: 8
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -86,7 +89,7 @@ export default class ProfilePicture extends React.PureComponent {
|
|||
<Icon
|
||||
style={style.status}
|
||||
name={statusToIcon[this.props.status]}
|
||||
size={8}
|
||||
size={this.props.statusIconSize}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -99,8 +102,32 @@ export default class ProfilePicture extends React.PureComponent {
|
|||
defaultSource={placeholder}
|
||||
/>
|
||||
{this.props.status &&
|
||||
<View style={[style.statusContainer, style[this.props.status]]}>
|
||||
{statusIcon}
|
||||
<View
|
||||
style={[
|
||||
style.statusWrapper,
|
||||
{
|
||||
width: this.props.statusSize,
|
||||
height: this.props.statusSize,
|
||||
borderWidth: this.props.statusBorderWidth,
|
||||
borderRadius: this.props.statusSize / 2
|
||||
},
|
||||
(this.props.status === 'offline' && style.offline)
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
style.statusContainer,
|
||||
{
|
||||
width: this.props.statusSize - this.props.statusBorderWidth,
|
||||
height: this.props.statusSize - this.props.statusBorderWidth,
|
||||
borderRadius: (this.props.statusSize - this.props.statusBorderWidth) / 2,
|
||||
padding: this.props.statusBorderWidth
|
||||
},
|
||||
style[this.props.status
|
||||
]]}
|
||||
>
|
||||
{statusIcon}
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import keyMirror from 'service/utils/key_mirror';
|
|||
const NavigationTypes = keyMirror({
|
||||
NAVIGATION_PUSH: null,
|
||||
NAVIGATION_POP: null,
|
||||
NAVIGATION_POP_TO_INDEX: null,
|
||||
NAVIGATION_OPEN_LEFT_DRAWER: null,
|
||||
NAVIGATION_OPEN_RIGHT_DRAWER: null,
|
||||
NAVIGATION_CLOSE_DRAWERS: null,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ import {
|
|||
Search,
|
||||
SelectServer,
|
||||
SelectTeam,
|
||||
Thread
|
||||
Thread,
|
||||
UserProfile
|
||||
} from 'app/scenes';
|
||||
|
||||
import keyMirror from 'service/utils/key_mirror';
|
||||
|
|
@ -136,6 +137,14 @@ export const Routes = {
|
|||
navigationProps: {
|
||||
title: {id: 'mobile.routes.thread', defaultMessage: 'Thread'}
|
||||
}
|
||||
},
|
||||
UserProfile: {
|
||||
key: 'UserProfile',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: UserProfile,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,41 @@ export default function(state = initialState, action) {
|
|||
|
||||
return NavigationExperimental.StateUtils.pop(state);
|
||||
|
||||
case NavigationTypes.NAVIGATION_POP_TO_INDEX: {
|
||||
let newState = {...state};
|
||||
|
||||
if (!newState.isModal && (newState.leftDrawerOpen || newState.rightDrawerOpen)) {
|
||||
newState = {
|
||||
...newState,
|
||||
leftDrawerOpen: false,
|
||||
rightDrawerOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
if (newState.isModal) {
|
||||
newState = {
|
||||
...newState,
|
||||
modal: {
|
||||
index: 0,
|
||||
routes: []
|
||||
},
|
||||
isModal: false
|
||||
};
|
||||
}
|
||||
|
||||
if (action.index === newState.index || action.index > newState.routes.length - 1) {
|
||||
return newState;
|
||||
}
|
||||
|
||||
const nextRoutes = newState.routes.slice(0, action.index + 1);
|
||||
|
||||
return {
|
||||
...newState,
|
||||
index: action.index,
|
||||
routes: nextRoutes
|
||||
};
|
||||
}
|
||||
|
||||
case NavigationTypes.NAVIGATION_OPEN_LEFT_DRAWER:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import Search from './search/search_container.js';
|
|||
import SelectServer from './select_server/select_server_container.js';
|
||||
import SelectTeam from './select_team/select_team_container.js';
|
||||
import Thread from './thread';
|
||||
import UserProfile from './user_profile';
|
||||
|
||||
module.exports = {
|
||||
ChannelView: Channel, // Special case the name for this one to avoid ambiguity
|
||||
|
|
@ -36,5 +37,6 @@ module.exports = {
|
|||
Search,
|
||||
SelectServer,
|
||||
SelectTeam,
|
||||
Thread
|
||||
Thread,
|
||||
UserProfile
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ const defaults = {
|
|||
renderBackButton: (props, emitter, theme) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{flexDirection: 'row', ...Platform.select({ios: {marginTop: 10}, android: {marginTop: 15}}), marginLeft: 15, alignItems: 'center'}}
|
||||
style={{flexDirection: 'row', flex: 1, paddingHorizontal: 15, alignItems: 'center'}}
|
||||
onPress={props.onNavigateBack}
|
||||
>
|
||||
<Icon
|
||||
name='angle-left'
|
||||
size={25}
|
||||
size={18}
|
||||
color={theme.sidebarHeaderTextColor}
|
||||
/>
|
||||
<FormattedText
|
||||
|
|
|
|||
6
app/scenes/user_profile/index.js
Normal file
6
app/scenes/user_profile/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import UserProfileContainer from './user_profile_container';
|
||||
|
||||
export default UserProfileContainer;
|
||||
150
app/scenes/user_profile/user_profile.js
Normal file
150
app/scenes/user_profile/user_profile.js
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {getFullName} from 'service/utils/user_utils';
|
||||
|
||||
import UserProfileRow from './user_profile_row';
|
||||
|
||||
const createStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
content: {
|
||||
marginBottom: 25,
|
||||
marginHorizontal: 15
|
||||
},
|
||||
displayName: {
|
||||
marginTop: 15,
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 17,
|
||||
fontWeight: '600'
|
||||
},
|
||||
header: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
marginTop: 25,
|
||||
marginBottom: 10
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
scrollViewContent: {
|
||||
flex: 1
|
||||
},
|
||||
text: {
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
top: {
|
||||
padding: 25,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
username: {
|
||||
marginTop: 15,
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 15
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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 <Text style={style.displayName}>{displayName}</Text>;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
buildDisplayBlock = (property) => {
|
||||
const {theme, user} = this.props;
|
||||
const style = createStyleSheet(theme);
|
||||
|
||||
if (user.hasOwnProperty(property) && user[property].length > 0) {
|
||||
return (
|
||||
<View>
|
||||
<Text style={style.header}>{property.toUpperCase()}</Text>
|
||||
<Text style={style.text}>{user[property]}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
sendMessage = () => {
|
||||
this.props.actions.handleSendMessage(this.props.user.id);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {theme, user} = this.props;
|
||||
const style = createStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<ScrollView
|
||||
style={style.scrollView}
|
||||
contentContainerStyle={style.scrollViewContent}
|
||||
>
|
||||
<View style={style.top}>
|
||||
<ProfilePicture
|
||||
user={user}
|
||||
size={150}
|
||||
statusBorderWidth={6}
|
||||
statusSize={40}
|
||||
statusIconSize={18}
|
||||
/>
|
||||
{this.getDisplayName()}
|
||||
<Text style={style.username}>{`@${user.username}`}</Text>
|
||||
</View>
|
||||
<View style={style.content}>
|
||||
{this.buildDisplayBlock('username')}
|
||||
{this.buildDisplayBlock('email')}
|
||||
{this.buildDisplayBlock('position')}
|
||||
</View>
|
||||
{this.props.currentUserId !== this.props.user.id &&
|
||||
<UserProfileRow
|
||||
action={this.sendMessage}
|
||||
defaultMessage='Send Message'
|
||||
icon='paper-plane-o'
|
||||
textId='mobile.routes.user_profile.send_message'
|
||||
theme={theme}
|
||||
/>
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
29
app/scenes/user_profile/user_profile_container.js
Normal file
29
app/scenes/user_profile/user_profile_container.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {handleSendMessage} from 'app/actions/views/user_profile';
|
||||
import {getTheme} from 'service/selectors/entities/preferences';
|
||||
|
||||
import UserProfile from './user_profile';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
currentUserId: state.entities.users.currentId,
|
||||
user: state.entities.users.profiles[ownProps.userId],
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
handleSendMessage
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(UserProfile);
|
||||
130
app/scenes/user_profile/user_profile_row.js
Normal file
130
app/scenes/user_profile/user_profile_row.js
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes} from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
Switch,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
const createStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelBg, 0.7),
|
||||
paddingHorizontal: 15,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.3),
|
||||
borderBottomColor: changeOpacity(theme.centerChannelColor, 0.3)
|
||||
},
|
||||
detail: {
|
||||
marginHorizontal: 15,
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
fontSize: 15
|
||||
},
|
||||
label: {
|
||||
flex: 1,
|
||||
marginLeft: 15,
|
||||
fontSize: 15,
|
||||
paddingVertical: 15,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
leftIcon: {
|
||||
width: 17,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
rightIcon: {
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.7
|
||||
},
|
||||
wrapper: {
|
||||
backgroundColor: '#ddd'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function createTouchableComponent(children, action) {
|
||||
return (
|
||||
<TouchableHighlight onPress={action}>
|
||||
{children}
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
|
||||
function userProfileRow(props) {
|
||||
const {action, defaultMessage, detail, icon, textId, togglable, theme, shouldRender = true} = props;
|
||||
|
||||
if (!shouldRender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = createStyleSheet(theme);
|
||||
|
||||
const RowComponent = (
|
||||
<View style={style.wrapper}>
|
||||
<View style={style.container}>
|
||||
<Icon
|
||||
name={icon}
|
||||
size={15}
|
||||
style={style.leftIcon}
|
||||
/>
|
||||
<FormattedText
|
||||
style={[style.label]}
|
||||
id={textId}
|
||||
defaultMessage={defaultMessage}
|
||||
/>
|
||||
<Text style={style.detail}>{detail}</Text>
|
||||
{togglable ?
|
||||
<Switch
|
||||
onValueChange={action}
|
||||
value={detail}
|
||||
/> :
|
||||
<Icon
|
||||
name='chevron-right'
|
||||
size={15}
|
||||
style={style.rightIcon}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
if (togglable) {
|
||||
return RowComponent;
|
||||
}
|
||||
|
||||
return createTouchableComponent(RowComponent, action);
|
||||
}
|
||||
|
||||
userProfileRow.propTypes = {
|
||||
action: PropTypes.func.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
detail: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
PropTypes.bool
|
||||
]),
|
||||
icon: PropTypes.string.isRequired,
|
||||
iconColor: PropTypes.string,
|
||||
textId: PropTypes.string.isRequired,
|
||||
togglable: PropTypes.bool,
|
||||
textColor: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
userProfileRow.defaultProps = {
|
||||
iconColor: 'rgba(0, 0, 0, 0.7)',
|
||||
textColor: '#000',
|
||||
togglable: false
|
||||
};
|
||||
|
||||
export default userProfileRow;
|
||||
|
|
@ -1519,6 +1519,8 @@
|
|||
"mobile.routes.channel_members.action_message": "You must select at least one member to {term} {prep} the channel.",
|
||||
"mobile.routes.channel_members.action_message_confirm": "Are you sure you want to {term} the selected members {prep} the channel?",
|
||||
"mobile.routes.thread": "Thread",
|
||||
"mobile.routes.user_profile": "Profile",
|
||||
"mobile.routes.user_profile.send_message": "Send Message",
|
||||
"more_channels.close": "Close",
|
||||
"more_channels.create": "Create New Channel",
|
||||
"more_channels.createClick": "Click 'Create New Channel' to make a new one",
|
||||
|
|
|
|||
|
|
@ -154,6 +154,33 @@ describe('Reducers.Navigation', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('NAVIGATION_POP_TO_INDEX', () => {
|
||||
let state = initialState();
|
||||
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: {key: 'fake'}
|
||||
});
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: {key: 'fake2'}
|
||||
});
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: {key: 'fake3'}
|
||||
});
|
||||
|
||||
it('Reset to first index', () => {
|
||||
state = reduceAndFreeze(state, {
|
||||
type: NavigationTypes.NAVIGATION_POP_TO_INDEX,
|
||||
index: 0
|
||||
});
|
||||
|
||||
assert.equal(state.index, 0);
|
||||
assert.deepEqual(state.routes, [Routes.Root, {key: 'fake'}]);
|
||||
});
|
||||
});
|
||||
|
||||
it('NAVIGATION_OPEN_LEFT_DRAWER', () => {
|
||||
let state = initialState();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue