RN-223 Emoji picker for adding new emoji reactions (#854)
* RN-223 Emoji picker for adding new emoji reactions * Remove redundant setServerVersion and fetch custom emojis on reset * Review feedback * Review feedback 2 * Review feedback 3 * Review feedback 4 * Change aliases to array and add header to image source * Remove aliases from custom emojis
This commit is contained in:
parent
b8d8be78d5
commit
bffc75f8d1
17 changed files with 595 additions and 21 deletions
32
NOTICE.txt
32
NOTICE.txt
|
|
@ -1247,3 +1247,35 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-fast-image
|
||||
|
||||
FastImage, performant React Native image component.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/DylanVann/react-native-fast-image
|
||||
|
||||
* LICENSE:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Dylan Vann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
|
|||
|
|
@ -11,20 +11,16 @@ import {
|
|||
import {handleTeamChange, selectFirstAvailableTeam} from 'app/actions/views/select_team';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {getClientConfig, getLicenseConfig, setServerVersion} from 'mattermost-redux/actions/general';
|
||||
import {getClientConfig, getLicenseConfig} from 'mattermost-redux/actions/general';
|
||||
import {markChannelAsRead, viewChannel} from 'mattermost-redux/actions/channels';
|
||||
|
||||
export function loadConfigAndLicense(serverVersion) {
|
||||
export function loadConfigAndLicense() {
|
||||
return async (dispatch, getState) => {
|
||||
const [config, license] = await Promise.all([
|
||||
getClientConfig()(dispatch, getState),
|
||||
getLicenseConfig()(dispatch, getState)
|
||||
]);
|
||||
|
||||
if (config && license) {
|
||||
setServerVersion(serverVersion)(dispatch, getState);
|
||||
}
|
||||
|
||||
return {config, license};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Image, Text} from 'react-native';
|
||||
import {Image, Platform, Text} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {EmojiIndicesByAlias, Emojis} from 'app/utils/emojis';
|
||||
|
|
@ -17,7 +18,8 @@ export default class Emoji extends React.PureComponent {
|
|||
literal: PropTypes.string,
|
||||
padding: PropTypes.number,
|
||||
size: PropTypes.number.isRequired,
|
||||
textStyle: CustomPropTypes.Style
|
||||
textStyle: CustomPropTypes.Style,
|
||||
token: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -33,7 +35,8 @@ export default class Emoji extends React.PureComponent {
|
|||
literal,
|
||||
padding,
|
||||
size,
|
||||
textStyle
|
||||
textStyle,
|
||||
token
|
||||
} = this.props;
|
||||
|
||||
let imageUrl;
|
||||
|
|
@ -49,10 +52,23 @@ export default class Emoji extends React.PureComponent {
|
|||
return <Text style={textStyle}>{literal}</Text>;
|
||||
}
|
||||
|
||||
let ImageComponent = FastImage;
|
||||
const source = {
|
||||
uri: imageUrl,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
};
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
ImageComponent = Image;
|
||||
}
|
||||
|
||||
return (
|
||||
<Image
|
||||
<ImageComponent
|
||||
style={{width: size, height: size, padding}}
|
||||
source={{uri: imageUrl}}
|
||||
source={source}
|
||||
onError={this.onError}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import Emoji from './emoji';
|
|||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
...ownProps,
|
||||
customEmojis: getCustomEmojisByName(state),
|
||||
...ownProps
|
||||
token: state.entities.general.credentials.token
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
278
app/components/emoji_picker/emoji_picker.js
Normal file
278
app/components/emoji_picker/emoji_picker.js
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {
|
||||
Dimensions,
|
||||
SectionList,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import Emoji from 'app/components/emoji';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import SearchBar from 'app/components/search_bar';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
|
||||
const {width: deviceWidth} = Dimensions.get('window');
|
||||
const EMOJI_SIZE = 30;
|
||||
const EMOJI_GUTTER = 7.5;
|
||||
const SECTION_MARGIN = 15;
|
||||
|
||||
class EmojiPicker extends PureComponent {
|
||||
static propTypes = {
|
||||
emojis: PropTypes.array.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
onEmojiPress: PropTypes.func,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onEmojiPress: emptyFunction
|
||||
};
|
||||
|
||||
leftButton = {
|
||||
id: 'close-edit-post'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
emojis: props.emojis,
|
||||
searchTerm: ''
|
||||
};
|
||||
}
|
||||
|
||||
changeSearchTerm = (text) => {
|
||||
this.setState({
|
||||
searchTerm: text
|
||||
});
|
||||
|
||||
clearTimeout(this.searchTermTimeout);
|
||||
const timeout = text ? 350 : 0;
|
||||
this.searchTermTimeout = setTimeout(() => {
|
||||
const emojis = this.searchEmojis(text);
|
||||
this.setState({
|
||||
emojis
|
||||
});
|
||||
}, timeout);
|
||||
};
|
||||
|
||||
cancelSearch = () => {
|
||||
this.setState({
|
||||
emojis: this.props.emojis,
|
||||
searchTerm: ''
|
||||
});
|
||||
}
|
||||
|
||||
filterEmojiAliases = (aliases, searchTerm) => {
|
||||
return aliases.findIndex((alias) => alias.includes(searchTerm)) !== -1;
|
||||
}
|
||||
|
||||
searchEmojis = (searchTerm) => {
|
||||
const {emojis} = this.props;
|
||||
const searchTermLowerCase = searchTerm.toLowerCase();
|
||||
|
||||
if (!searchTerm) {
|
||||
return emojis;
|
||||
}
|
||||
|
||||
const nextEmojis = [];
|
||||
emojis.forEach((section) => {
|
||||
const {data, ...otherProps} = section;
|
||||
const {key, items} = data[0];
|
||||
|
||||
const nextData = {
|
||||
key,
|
||||
items: items.filter((item) => {
|
||||
if (item.aliases) {
|
||||
return this.filterEmojiAliases(item.aliases, searchTermLowerCase);
|
||||
}
|
||||
|
||||
return item.name.includes(searchTermLowerCase);
|
||||
})
|
||||
};
|
||||
|
||||
if (nextData.items.length) {
|
||||
nextEmojis.push({
|
||||
...otherProps,
|
||||
data: [nextData]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return nextEmojis;
|
||||
}
|
||||
|
||||
renderSectionHeader = ({section}) => {
|
||||
const {theme} = this.props;
|
||||
const styles = getStyleSheetFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View key={section.title}>
|
||||
<FormattedText
|
||||
style={styles.sectionTitle}
|
||||
id={section.id}
|
||||
defaultMessage={section.defaultMessage}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderEmojis = (emojis, index) => {
|
||||
const {theme} = this.props;
|
||||
const styles = getStyleSheetFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View
|
||||
key={index}
|
||||
style={styles.columnStyle}
|
||||
>
|
||||
{emojis.map((emoji, emojiIndex) => {
|
||||
const style = [styles.emoji];
|
||||
if (emojiIndex === 0) {
|
||||
style.push(styles.emojiLeft);
|
||||
} else if (emojiIndex === emojis.length - 1) {
|
||||
style.push(styles.emojiRight);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={emoji.name}
|
||||
style={style}
|
||||
onPress={() => {
|
||||
this.props.onEmojiPress(emoji.name);
|
||||
}}
|
||||
>
|
||||
<Emoji
|
||||
emojiName={emoji.name}
|
||||
size={EMOJI_SIZE}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderItem = ({item}) => {
|
||||
const {theme} = this.props;
|
||||
const styles = getStyleSheetFromTheme(theme);
|
||||
|
||||
const numColumns = Number(((deviceWidth - (SECTION_MARGIN * 2)) / (EMOJI_SIZE + (EMOJI_GUTTER * 2))).toFixed(0));
|
||||
|
||||
const slices = item.items.reduce((slice, emoji, emojiIndex) => {
|
||||
if (emojiIndex % numColumns === 0 && emojiIndex !== 0) {
|
||||
slice.push([]);
|
||||
}
|
||||
|
||||
slice[slice.length - 1].push(emoji);
|
||||
|
||||
return slice;
|
||||
}, [[]]);
|
||||
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
{slices.map(this.renderEmojis)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {intl, theme} = this.props;
|
||||
const {emojis, searchTerm} = this.state;
|
||||
const {formatMessage} = intl;
|
||||
const styles = getStyleSheetFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View style={styles.wrapper}>
|
||||
<View style={styles.searchBar}>
|
||||
<SearchBar
|
||||
ref='search_bar'
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={{
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 13
|
||||
}}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.8)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.changeSearchTerm}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
value={searchTerm}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.container}>
|
||||
<SectionList
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={styles.listView}
|
||||
sections={emojis}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
renderItem={this.renderItem}
|
||||
removeClippedSubviews={true}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
columnStyle: {
|
||||
alignSelf: 'stretch',
|
||||
flexDirection: 'row',
|
||||
marginVertical: EMOJI_GUTTER,
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flex: 1
|
||||
},
|
||||
emoji: {
|
||||
width: EMOJI_SIZE,
|
||||
height: EMOJI_SIZE,
|
||||
marginHorizontal: EMOJI_GUTTER,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
emojiLeft: {
|
||||
marginLeft: 0
|
||||
},
|
||||
emojiRight: {
|
||||
marginRight: 0
|
||||
},
|
||||
listView: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
width: deviceWidth - (SECTION_MARGIN * 2)
|
||||
},
|
||||
searchBar: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
paddingVertical: 5
|
||||
},
|
||||
section: {
|
||||
alignItems: 'center'
|
||||
},
|
||||
sectionTitle: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
fontSize: 15,
|
||||
fontWeight: '700',
|
||||
paddingVertical: 5
|
||||
},
|
||||
wrapper: {
|
||||
flex: 1
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export default injectIntl(EmojiPicker);
|
||||
108
app/components/emoji_picker/index.js
Normal file
108
app/components/emoji_picker/index.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
|
||||
|
||||
import {getTheme} from 'app/selectors/preferences';
|
||||
import {CategoryNames, Emojis, EmojiIndicesByCategory} from 'app/utils/emojis';
|
||||
|
||||
import EmojiPicker from './emoji_picker';
|
||||
|
||||
const categoryToI18n = {
|
||||
activity: {
|
||||
id: 'mobile.emoji_picker.activity',
|
||||
defaultMessage: 'ACTIVITY'
|
||||
},
|
||||
custom: {
|
||||
id: 'mobile.emoji_picker.custom',
|
||||
defaultMessage: 'CUSTOM'
|
||||
},
|
||||
flags: {
|
||||
id: 'mobile.emoji_picker.flags',
|
||||
defaultMessage: 'FLAGS'
|
||||
},
|
||||
foods: {
|
||||
id: 'mobile.emoji_picker.foods',
|
||||
defaultMessage: 'FOODS'
|
||||
},
|
||||
nature: {
|
||||
id: 'mobile.emoji_picker.nature',
|
||||
defaultMessage: 'NATURE'
|
||||
},
|
||||
objects: {
|
||||
id: 'mobile.emoji_picker.objects',
|
||||
defaultMessage: 'OBJECTS'
|
||||
},
|
||||
people: {
|
||||
id: 'mobile.emoji_picker.people',
|
||||
defaultMessage: 'PEOPLE'
|
||||
},
|
||||
places: {
|
||||
id: 'mobile.emoji_picker.places',
|
||||
defaultMessage: 'PLACES'
|
||||
},
|
||||
symbols: {
|
||||
id: 'mobile.emoji_picker.symbols',
|
||||
defaultMessage: 'SYMBOLS'
|
||||
}
|
||||
};
|
||||
|
||||
function fillEmoji(indice) {
|
||||
const emoji = Emojis[indice];
|
||||
return {
|
||||
name: emoji.aliases[0],
|
||||
aliases: emoji.aliases
|
||||
};
|
||||
}
|
||||
|
||||
const getEmojisBySection = createSelector(
|
||||
getCustomEmojisByName,
|
||||
(customEmojis) => {
|
||||
const emoticons = CategoryNames.filter((name) => name !== 'custom').map((category) => {
|
||||
const section = {
|
||||
...categoryToI18n[category],
|
||||
key: category,
|
||||
data: [{
|
||||
key: `${category}-emojis`,
|
||||
items: EmojiIndicesByCategory.get(category).map(fillEmoji)
|
||||
}]
|
||||
};
|
||||
|
||||
return section;
|
||||
});
|
||||
|
||||
const customEmojiData = {
|
||||
key: 'custom-emojis',
|
||||
title: 'CUSTOM',
|
||||
items: []
|
||||
};
|
||||
|
||||
for (const [key] of customEmojis) {
|
||||
customEmojiData.items.push({
|
||||
name: key
|
||||
});
|
||||
}
|
||||
|
||||
emoticons.push({
|
||||
...categoryToI18n.custom,
|
||||
key: 'custom',
|
||||
data: [customEmojiData]
|
||||
});
|
||||
|
||||
return emoticons;
|
||||
}
|
||||
);
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const emojis = getEmojisBySection(state);
|
||||
|
||||
return {
|
||||
emojis,
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(EmojiPicker);
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {createPost, deletePost, removePost} from 'mattermost-redux/actions/posts';
|
||||
import {addReaction, createPost, deletePost, removePost} from 'mattermost-redux/actions/posts';
|
||||
import {getPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
|
|
@ -38,6 +38,7 @@ function makeMapStateToProps() {
|
|||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
addReaction,
|
||||
createPost,
|
||||
deletePost,
|
||||
removePost,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
|||
class Post extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
addReaction: PropTypes.func.isRequired,
|
||||
createPost: PropTypes.func.isRequired,
|
||||
deletePost: PropTypes.func.isRequired,
|
||||
removePost: PropTypes.func.isRequired,
|
||||
|
|
@ -146,6 +147,35 @@ class Post extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
handleAddReactionToPost = (emoji) => {
|
||||
const {post} = this.props;
|
||||
this.props.actions.addReaction(post.id, emoji);
|
||||
}
|
||||
|
||||
handleAddReaction = () => {
|
||||
const {intl, navigator, post, theme} = this.props;
|
||||
|
||||
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).
|
||||
then((source) => {
|
||||
navigator.showModal({
|
||||
screen: 'AddReaction',
|
||||
title: intl.formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'}),
|
||||
animated: true,
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
passProps: {
|
||||
post,
|
||||
closeButton: source,
|
||||
onEmojiPress: this.handleAddReactionToPost
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleFailedPostPress = () => {
|
||||
const options = {
|
||||
title: {
|
||||
|
|
@ -304,6 +334,7 @@ class Post extends PureComponent {
|
|||
canEdit={this.state.canEdit}
|
||||
isSearchResult={isSearchResult}
|
||||
navigator={this.props.navigator}
|
||||
onAddReaction={this.handleAddReaction}
|
||||
onFailedPostPress={this.handleFailedPostPress}
|
||||
onPostDelete={this.handlePostDelete}
|
||||
onPostEdit={this.handlePostEdit}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class PostBody extends PureComponent {
|
|||
isSystemMessage: PropTypes.bool,
|
||||
message: PropTypes.string,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
onAddReaction: PropTypes.func,
|
||||
onFailedPostPress: PropTypes.func,
|
||||
onPostDelete: PropTypes.func,
|
||||
onPostEdit: PropTypes.func,
|
||||
|
|
@ -55,6 +56,7 @@ class PostBody extends PureComponent {
|
|||
|
||||
static defaultProps = {
|
||||
fileIds: [],
|
||||
onAddReaction: emptyFunction,
|
||||
onFailedPostPress: emptyFunction,
|
||||
onPostDelete: emptyFunction,
|
||||
onPostEdit: emptyFunction,
|
||||
|
|
@ -192,6 +194,11 @@ class PostBody extends PureComponent {
|
|||
if (canDelete && !hasBeenDeleted) {
|
||||
actions.push({text: formatMessage({id: 'post_info.del', defaultMessage: 'Delete'}), onPress: onPostDelete});
|
||||
}
|
||||
|
||||
actions.push({
|
||||
text: formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'}),
|
||||
onPress: this.props.onAddReaction
|
||||
});
|
||||
}
|
||||
|
||||
let messageComponent;
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ export default class Mattermost {
|
|||
);
|
||||
} else {
|
||||
setServerVersion(serverVersion)(dispatch, getState);
|
||||
const data = await loadConfigAndLicense(serverVersion)(dispatch, getState);
|
||||
const data = await loadConfigAndLicense()(dispatch, getState);
|
||||
this.configureAnalytics(data.config);
|
||||
}
|
||||
}
|
||||
|
|
@ -418,8 +418,11 @@ export default class Mattermost {
|
|||
setServerVersion('')(dispatch, getState);
|
||||
};
|
||||
|
||||
restartApp = () => {
|
||||
restartApp = async () => {
|
||||
Navigation.dismissModal({animationType: 'none'});
|
||||
|
||||
const {dispatch, getState} = store;
|
||||
await loadConfigAndLicense()(dispatch, getState);
|
||||
this.startApp('fade');
|
||||
};
|
||||
|
||||
|
|
|
|||
72
app/screens/add_reaction/add_reaction.js
Normal file
72
app/screens/add_reaction/add_reaction.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
StyleSheet,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import EmojiPicker from 'app/components/emoji_picker';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
|
||||
export default class AddReaction extends PureComponent {
|
||||
static propTypes = {
|
||||
closeButton: PropTypes.object,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
onEmojiPress: PropTypes.func
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onEmojiPress: emptyFunction
|
||||
};
|
||||
|
||||
leftButton = {
|
||||
id: 'close-edit-post'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
|
||||
props.navigator.setButtons({
|
||||
leftButtons: [{...this.leftButton, icon: props.closeButton}]
|
||||
});
|
||||
}
|
||||
|
||||
close = () => {
|
||||
this.props.navigator.dismissModal({
|
||||
animationType: 'slide-down'
|
||||
});
|
||||
};
|
||||
|
||||
onNavigatorEvent = (event) => {
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
switch (event.id) {
|
||||
case 'close-edit-post':
|
||||
this.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleEmojiPress = (emoji) => {
|
||||
this.props.onEmojiPress(emoji);
|
||||
this.close();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<EmojiPicker onEmojiPress={this.handleEmojiPress}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
}
|
||||
});
|
||||
6
app/screens/add_reaction/index.js
Normal file
6
app/screens/add_reaction/index.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import AddReaction from './add_reaction';
|
||||
|
||||
export default AddReaction;
|
||||
|
|
@ -7,6 +7,7 @@ import {Navigation} from 'react-native-navigation';
|
|||
import About from 'app/screens/about';
|
||||
import AccountSettings from 'app/screens/account_settings';
|
||||
import AccountNotifications from 'app/screens/account_notifications';
|
||||
import AddReaction from 'app/screens/add_reaction';
|
||||
import AdvancedSettings from 'app/screens/advanced_settings';
|
||||
import Channel from 'app/screens/channel';
|
||||
import ChannelAddMembers from 'app/screens/channel_add_members';
|
||||
|
|
@ -53,6 +54,7 @@ export function registerScreens(store, Provider) {
|
|||
Navigation.registerComponent('About', () => wrapWithContextProvider(About), store, Provider);
|
||||
Navigation.registerComponent('AccountSettings', () => wrapWithContextProvider(AccountSettings), store, Provider);
|
||||
Navigation.registerComponent('AccountNotifications', () => wrapWithContextProvider(AccountNotifications), store, Provider);
|
||||
Navigation.registerComponent('AddReaction', () => wrapWithContextProvider(AddReaction), store, Provider);
|
||||
Navigation.registerComponent('AdvancedSettings', () => wrapWithContextProvider(AdvancedSettings), store, Provider);
|
||||
Navigation.registerComponent('Channel', () => wrapWithContextProvider(Channel), store, Provider);
|
||||
Navigation.registerComponent('ChannelAddMembers', () => wrapWithContextProvider(ChannelAddMembers), store, Provider);
|
||||
|
|
|
|||
|
|
@ -1832,6 +1832,15 @@
|
|||
"mobile.custom_list.no_results": "No Results",
|
||||
"mobile.drawer.teamsTitle": "Teams",
|
||||
"mobile.edit_post.title": "Editing Message",
|
||||
"mobile.emoji_picker.activity": "ACTIVITY",
|
||||
"mobile.emoji_picker.custom": "CUSTOM",
|
||||
"mobile.emoji_picker.flags": "FLAGS",
|
||||
"mobile.emoji_picker.foods": "FOODS",
|
||||
"mobile.emoji_picker.nature": "NATURE",
|
||||
"mobile.emoji_picker.objects": "OBJECTS",
|
||||
"mobile.emoji_picker.people": "PEOPLE",
|
||||
"mobile.emoji_picker.places": "PLACES",
|
||||
"mobile.emoji_picker.symbols": "SYMBOLS",
|
||||
"mobile.error_handler.button": "Relaunch",
|
||||
"mobile.error_handler.description": "\nClick relaunch to open the app again. After restart, you can report the problem from the settings menu.\n",
|
||||
"mobile.error_handler.title": "Unexpected error occurred",
|
||||
|
|
@ -1864,12 +1873,14 @@
|
|||
"mobile.offlineIndicator.offline": "No internet connection",
|
||||
"mobile.open_dm.error": "We couldn't open a direct message with {displayName}. Please check your connection and try again.",
|
||||
"mobile.open_gm.error": "We couldn't open a group message with those users. Please check your connection and try again.",
|
||||
|
||||
"mobile.post.cancel": "Cancel",
|
||||
"mobile.post.delete_question": "Are you sure you want to delete this post?",
|
||||
"mobile.post.delete_title": "Delete Post",
|
||||
"mobile.post.failed_delete": "Delete Message",
|
||||
"mobile.post.failed_retry": "Try Again",
|
||||
"mobile.post.failed_title": "Unable to send your message",
|
||||
"mobile.post_info.add_reaction": "Add Reaction",
|
||||
"mobile.post.retry": "Refresh",
|
||||
"mobile.request.invalid_response": "Received invalid response from the server.",
|
||||
"mobile.routes.channelInfo": "Info",
|
||||
|
|
@ -2010,6 +2021,7 @@
|
|||
"post_info.edit": "Edit",
|
||||
"post_info.message.visible": "(Only visible to you)",
|
||||
"post_info.message.visible.compact": " (Only visible to you)",
|
||||
"post_info.mobile.add_reaction": "Add Reaction",
|
||||
"post_info.mobile.flag": "Flag",
|
||||
"post_info.mobile.unflag": "Unflag",
|
||||
"post_info.permalink": "Permalink",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
|
||||
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
|
||||
|
|
@ -62,6 +61,7 @@
|
|||
F006936FC2884C24A1321FC0 /* MaterialIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 356C9186FA374641A00EB2EA /* MaterialIcons.ttf */; };
|
||||
F083DB472349411A8E6E7AAD /* OpenSans-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BE17F630DB5D41FD93F32D22 /* OpenSans-LightItalic.ttf */; };
|
||||
FDEF24E9D9AB47728E11033B /* libRNPasscodeStatus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17A75F5A3D0D4A4A995DCD76 /* libRNPasscodeStatus.a */; };
|
||||
1C1F002AC5DE465FAE9E0D74 /* libFastImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C0C75A424714EAC8E876A8F /* libFastImage.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
|
@ -445,6 +445,8 @@
|
|||
EE671DF7637347CD8C069819 /* libRNDeviceInfo.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNDeviceInfo.a; sourceTree = "<group>"; };
|
||||
F1F071EE85494E269A50AE88 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; };
|
||||
FBBEC29EE2D3418D9AC33BD5 /* OpenSans-ExtraBoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-ExtraBoldItalic.ttf"; path = "../assets/fonts/OpenSans-ExtraBoldItalic.ttf"; sourceTree = "<group>"; };
|
||||
50ECB1D221E44F51B5690DF2 /* FastImage.xcodeproj */ = {isa = PBXFileReference; name = "FastImage.xcodeproj"; path = "../node_modules/react-native-fast-image/ios/FastImage.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
|
||||
0C0C75A424714EAC8E876A8F /* libFastImage.a */ = {isa = PBXFileReference; name = "libFastImage.a"; path = "libFastImage.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -484,6 +486,7 @@
|
|||
615D2E7805814A3A9B9C69EC /* libRNLocalAuth.a in Frameworks */,
|
||||
FDEF24E9D9AB47728E11033B /* libRNPasscodeStatus.a in Frameworks */,
|
||||
38FB8B0A3AAD4F839EF6EF0B /* libJailMonkey.a in Frameworks */,
|
||||
1C1F002AC5DE465FAE9E0D74 /* libFastImage.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -790,6 +793,7 @@
|
|||
D0281D64B98143668D6AD42B /* RNLocalAuth.xcodeproj */,
|
||||
EBA6063A99C141098D40C67A /* RNPasscodeStatus.xcodeproj */,
|
||||
27A6EA89298440439DA9F98D /* JailMonkey.xcodeproj */,
|
||||
50ECB1D221E44F51B5690DF2 /* FastImage.xcodeproj */,
|
||||
);
|
||||
name = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1405,8 +1409,6 @@
|
|||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
@ -1425,8 +1427,6 @@
|
|||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
@ -1456,6 +1456,7 @@
|
|||
"$(SRCROOT)/../node_modules/react-native-passcode-status/ios",
|
||||
"$(SRCROOT)/../node_modules/jail-monkey/JailMonkey",
|
||||
"$(SRCROOT)/../node_modules/react-native/Libraries/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-fast-image/ios/FastImage/**",
|
||||
);
|
||||
INFOPLIST_FILE = Mattermost/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
|
|
@ -1494,6 +1495,7 @@
|
|||
"$(SRCROOT)/../node_modules/react-native-passcode-status/ios",
|
||||
"$(SRCROOT)/../node_modules/jail-monkey/JailMonkey",
|
||||
"$(SRCROOT)/../node_modules/react-native/Libraries/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-fast-image/ios/FastImage/**",
|
||||
);
|
||||
INFOPLIST_FILE = Mattermost/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
"react-native-device-info": "0.11.0",
|
||||
"react-native-drawer": "2.3.0",
|
||||
"react-native-exception-handler": "1.1.0",
|
||||
"react-native-fast-image": "1.0.0",
|
||||
"react-native-image-picker": "jp928/react-native-image-picker#6ee35b69f3dbd6c7c66f580fd4d9eabf398703d4",
|
||||
"react-native-keyboard-aware-scroll-view": "0.2.8",
|
||||
"react-native-linear-gradient": "2.0.0",
|
||||
|
|
|
|||
|
|
@ -4288,7 +4288,7 @@ promise@^7.1.1:
|
|||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
prop-types@15.5.10, prop-types@^15.0.0, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@~15.5.7:
|
||||
prop-types@15.5.10, prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@~15.5.7:
|
||||
version "15.5.10"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
|
||||
dependencies:
|
||||
|
|
@ -4495,6 +4495,12 @@ react-native-exception-handler@1.1.0:
|
|||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-exception-handler/-/react-native-exception-handler-1.1.0.tgz#320b7fc9c104e11d66173a2b0daeac97831a1a37"
|
||||
|
||||
react-native-fast-image@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-fast-image/-/react-native-fast-image-1.0.0.tgz#a88337efa18fd34d530bab9ee05680374f3e2a2d"
|
||||
dependencies:
|
||||
prop-types "^15.5.10"
|
||||
|
||||
react-native-image-picker@jp928/react-native-image-picker#6ee35b69f3dbd6c7c66f580fd4d9eabf398703d4:
|
||||
version "0.26.2"
|
||||
resolved "https://codeload.github.com/jp928/react-native-image-picker/tar.gz/6ee35b69f3dbd6c7c66f580fd4d9eabf398703d4"
|
||||
|
|
|
|||
Loading…
Reference in a new issue