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:
Chris Duarte 2017-08-23 11:29:09 -07:00 committed by enahum
parent b8d8be78d5
commit bffc75f8d1
17 changed files with 595 additions and 21 deletions

View file

@ -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.

View file

@ -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};
};
}

View file

@ -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}
/>
);
}

View file

@ -9,8 +9,9 @@ import Emoji from './emoji';
function mapStateToProps(state, ownProps) {
return {
...ownProps,
customEmojis: getCustomEmojisByName(state),
...ownProps
token: state.entities.general.credentials.token
};
}

View 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);

View 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);

View file

@ -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,

View file

@ -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}

View file

@ -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;

View file

@ -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');
};

View 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
}
});

View 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;

View file

@ -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);

View file

@ -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",

View file

@ -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";

View file

@ -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",

View file

@ -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"