@plusscommunities/pluss-core-app 1.0.7 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/assets/icons/reactions/heart.png +0 -0
  2. package/assets/icons/reactions/party.png +0 -0
  3. package/assets/icons/reactions/sad.png +0 -0
  4. package/assets/icons/reactions/smile.png +0 -0
  5. package/package.json +6 -2
  6. package/src/actions/FollowerActions.js +40 -0
  7. package/src/actions/ResidentActions.js +29 -0
  8. package/src/actions/UserActions.js +221 -0
  9. package/src/actions/index.js +3 -0
  10. package/src/actions/types.js +8 -0
  11. package/src/apis/analyticsActions.js +20 -0
  12. package/src/apis/contactActions.js +23 -0
  13. package/src/apis/eventActions.js +147 -0
  14. package/src/apis/followerActions.js +32 -0
  15. package/src/apis/index.js +5 -0
  16. package/src/apis/reactionActions.js +23 -23
  17. package/src/apis/userActions.js +98 -0
  18. package/src/colours.js +6 -6
  19. package/src/components/BackButton.js +57 -0
  20. package/src/components/CategoryTabs.js +182 -0
  21. package/src/components/DropDownItem.js +73 -0
  22. package/src/components/DropDownMenu.js +37 -0
  23. package/src/components/FontScaleButton.js +38 -0
  24. package/src/components/FontScalePopup.js +73 -0
  25. package/src/components/FormattedText.js +131 -0
  26. package/src/components/ImageUploader.js +9 -20
  27. package/src/components/Input.js +187 -0
  28. package/src/components/PlussChat.js +885 -0
  29. package/src/components/PlussChatMessage.js +208 -0
  30. package/src/components/PlussChatTime.js +63 -0
  31. package/src/components/PositionedImage.js +260 -0
  32. package/src/components/Reaction.js +111 -0
  33. package/src/components/Reactions.js +75 -0
  34. package/src/components/StickyFooter.js +34 -0
  35. package/src/components/TextStyle.js +6 -1
  36. package/src/components/Toggle.js +84 -0
  37. package/src/components/TouchableSearchBar.js +62 -0
  38. package/src/components/UserListPopup.js +140 -0
  39. package/src/components/UserListing.js +262 -0
  40. package/src/components/WarningPopup.js +79 -0
  41. package/src/components/index.js +18 -0
  42. package/src/config.js +9 -1
  43. package/src/constants.js +24 -0
  44. package/src/helper.js +199 -29
  45. package/src/index.js +3 -1
  46. package/src/session.js +12 -12
  47. package/src/styles.js +33 -0
@@ -0,0 +1,208 @@
1
+ /* eslint no-use-before-define: ["error", { "variables": false }], react-native/no-inline-styles: 0 */
2
+
3
+ // NOTE:
4
+ // This is a modified version of react-native-gifted-chat Message component
5
+
6
+ // Original source:
7
+ // https://github.com/FaridSafi/react-native-gifted-chat/blob/master/src/Message.js
8
+
9
+ import PropTypes from 'prop-types';
10
+ import React from 'react';
11
+ import { View, ViewPropTypes, StyleSheet, Text } from 'react-native';
12
+ import _ from 'lodash';
13
+
14
+ import { Avatar, Bubble, SystemMessage, Day, utils } from 'react-native-gifted-chat';
15
+ import { TEXT_LIGHT } from '../colours';
16
+ import { getFirstName } from '../helper';
17
+
18
+ const isSameUser = utils.isSameUser;
19
+ const isSameDay = utils.isSameDay;
20
+
21
+ export default class PlussChatMessage extends React.Component {
22
+ getInnerComponentProps() {
23
+ const { containerStyle, ...props } = this.props;
24
+ return {
25
+ ...props,
26
+ isSameUser,
27
+ isSameDay,
28
+ };
29
+ }
30
+
31
+ isCurrentUser() {
32
+ if (this.props.user._id === this.props.currentMessage.user._id) {
33
+ return true;
34
+ }
35
+ return false;
36
+ }
37
+
38
+ renderDay() {
39
+ if (this.props.currentMessage.createdAt) {
40
+ const dayProps = this.getInnerComponentProps();
41
+ if (this.props.renderDay) {
42
+ return this.props.renderDay(dayProps);
43
+ }
44
+ return <Day {...dayProps} />;
45
+ }
46
+ return null;
47
+ }
48
+
49
+ renderBubble() {
50
+ const bubbleProps = this.getInnerComponentProps();
51
+ if (this.props.renderBubble) {
52
+ return this.props.renderBubble(bubbleProps);
53
+ }
54
+ return <Bubble {...bubbleProps} />;
55
+ }
56
+
57
+ renderSystemMessage() {
58
+ const systemMessageProps = this.getInnerComponentProps();
59
+ if (this.props.renderSystemMessage) {
60
+ return this.props.renderSystemMessage(systemMessageProps);
61
+ }
62
+ return <SystemMessage {...systemMessageProps} />;
63
+ }
64
+
65
+ renderAvatar() {
66
+ if (this.isCurrentUser() && !this.props.showUserAvatar) {
67
+ return null;
68
+ }
69
+ const avatarProps = this.getInnerComponentProps();
70
+ const { currentMessage } = avatarProps;
71
+ if (currentMessage.user.avatar === null || _.isEmpty(currentMessage.user.avatar)) {
72
+ return null;
73
+ }
74
+ return (
75
+ <Avatar
76
+ {...avatarProps}
77
+ imageStyle={{
78
+ left: {
79
+ width: 30,
80
+ height: 30,
81
+ borderRadius: 15,
82
+ },
83
+ right: {
84
+ width: 30,
85
+ height: 30,
86
+ borderRadius: 15,
87
+ },
88
+ }}
89
+ containerStyle={{
90
+ left: {
91
+ marginRight: 10,
92
+ },
93
+ right: {
94
+ marginLeft: 10,
95
+ },
96
+ }}
97
+ />
98
+ );
99
+ }
100
+
101
+ renderName() {
102
+ const nameProps = this.getInnerComponentProps();
103
+ const { currentMessage } = nameProps;
104
+ if (currentMessage.user.name === null) {
105
+ return null;
106
+ }
107
+ return <Text style={[styles[this.props.position].name]}>{this.isCurrentUser() ? 'Me' : getFirstName(currentMessage.user.name)}</Text>;
108
+ }
109
+
110
+ render() {
111
+ const sameUserAsLast = isSameUser(this.props.currentMessage, this.props.previousMessage);
112
+ const sameUser = isSameUser(this.props.currentMessage, this.props.nextMessage);
113
+ return (
114
+ <View>
115
+ {this.renderDay()}
116
+ {this.props.currentMessage.system ? (
117
+ this.renderSystemMessage()
118
+ ) : (
119
+ <View>
120
+ {sameUserAsLast ? null : this.renderName()}
121
+ <View
122
+ style={[
123
+ styles[this.props.position].container,
124
+ { marginBottom: sameUser ? 2 : 10 },
125
+ !this.props.inverted && { marginBottom: 2 },
126
+ this.props.containerStyle[this.props.position],
127
+ ]}
128
+ >
129
+ {this.props.position === 'left' ? this.renderAvatar() : null}
130
+ {this.renderBubble()}
131
+ {this.props.position === 'right' ? this.renderAvatar() : null}
132
+ </View>
133
+ </View>
134
+ )}
135
+ </View>
136
+ );
137
+ }
138
+ }
139
+
140
+ const styles = {
141
+ left: StyleSheet.create({
142
+ container: {
143
+ flexDirection: 'row',
144
+ alignItems: 'flex-end',
145
+ justifyContent: 'flex-start',
146
+ marginLeft: 10,
147
+ marginRight: 0,
148
+ },
149
+ name: {
150
+ fontFamily: 'sf-regular',
151
+ color: TEXT_LIGHT,
152
+ fontSize: 14,
153
+ textAlign: 'left',
154
+ marginLeft: 50,
155
+ marginBottom: 5,
156
+ },
157
+ }),
158
+ right: StyleSheet.create({
159
+ container: {
160
+ flexDirection: 'row',
161
+ alignItems: 'flex-end',
162
+ justifyContent: 'flex-end',
163
+ marginLeft: 0,
164
+ marginRight: 10,
165
+ },
166
+ name: {
167
+ fontFamily: 'sf-regular',
168
+ color: TEXT_LIGHT,
169
+ fontSize: 14,
170
+ textAlign: 'right',
171
+ marginRight: 10,
172
+ marginBottom: 5,
173
+ },
174
+ }),
175
+ };
176
+
177
+ PlussChatMessage.defaultProps = {
178
+ renderAvatar: undefined,
179
+ renderBubble: null,
180
+ renderDay: null,
181
+ renderSystemMessage: null,
182
+ position: 'left',
183
+ currentMessage: {},
184
+ nextMessage: {},
185
+ previousMessage: {},
186
+ user: {},
187
+ containerStyle: {},
188
+ showUserAvatar: true,
189
+ inverted: true,
190
+ };
191
+
192
+ PlussChatMessage.propTypes = {
193
+ renderAvatar: PropTypes.func,
194
+ showUserAvatar: PropTypes.bool,
195
+ renderBubble: PropTypes.func,
196
+ renderDay: PropTypes.func,
197
+ renderSystemMessage: PropTypes.func,
198
+ position: PropTypes.oneOf(['left', 'right']),
199
+ currentMessage: PropTypes.object,
200
+ nextMessage: PropTypes.object,
201
+ previousMessage: PropTypes.object,
202
+ user: PropTypes.object,
203
+ inverted: PropTypes.bool,
204
+ containerStyle: PropTypes.shape({
205
+ left: ViewPropTypes.style,
206
+ right: ViewPropTypes.style,
207
+ }),
208
+ };
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { StyleSheet, Text, View } from 'react-native';
3
+
4
+ import moment from 'moment';
5
+
6
+ export default class PlussChatTime extends React.Component {
7
+ shouldRenderDay(curTime, prevTime) {
8
+ return !curTime.isSame(prevTime, 'd');
9
+ }
10
+
11
+ shouldRenderTime(curTime, prevTime) {
12
+ return curTime.diff(prevTime, 'm') >= 10;
13
+ }
14
+
15
+ render() {
16
+ const curMesTime = moment(this.props.currentMessage.createdAt).local();
17
+ const prevTime = moment(this.props.previousMessage.createdAt).local();
18
+ const now = moment().local();
19
+
20
+ if (this.shouldRenderDay(curMesTime, prevTime) || this.shouldRenderTime(curMesTime, prevTime)) {
21
+ let timestamp = '';
22
+ if (!now.isSame(curMesTime, 'd')) {
23
+ timestamp += curMesTime.format('ddd, D MMM');
24
+ if (!now.isSame(curMesTime, 'y')) {
25
+ timestamp += ` ${curMesTime.format('YYYY')}`;
26
+ }
27
+ timestamp += ' • ';
28
+ }
29
+ timestamp += curMesTime.format('h:mma');
30
+ return (
31
+ <View style={[styles.container, this.props.containerStyle]}>
32
+ <View style={[styles.wrapper, this.props.wrapperStyle]}>
33
+ <Text style={[styles.text, this.props.textStyle]}>{timestamp}</Text>
34
+ </View>
35
+ </View>
36
+ );
37
+ }
38
+ return null;
39
+ }
40
+ }
41
+
42
+ const styles = StyleSheet.create({
43
+ container: {
44
+ alignItems: 'center',
45
+ justifyContent: 'center',
46
+ marginTop: 5,
47
+ marginBottom: 10,
48
+ },
49
+ wrapper: {
50
+ // backgroundColor: '#ccc',
51
+ // borderRadius: 10,
52
+ // paddingLeft: 10,
53
+ // paddingRight: 10,
54
+ // paddingTop: 5,
55
+ // paddingBottom: 5,
56
+ },
57
+ text: {
58
+ backgroundColor: 'transparent',
59
+ color: '#b2b2b2',
60
+ fontSize: 12,
61
+ fontWeight: '600',
62
+ },
63
+ });
@@ -0,0 +1,260 @@
1
+ import React, { Component } from 'react';
2
+ import { View, Animated, PanResponder, Image, StyleSheet } from 'react-native';
3
+ import { Icon } from 'react-native-elements';
4
+ import { BOXGREY, TEXT_LIGHT } from '../colours';
5
+
6
+ class PositionedImage extends Component {
7
+ constructor(props) {
8
+ super(props);
9
+
10
+ this.lastPos = { x: 0, y: 0 };
11
+ this.imagePosition = new Animated.ValueXY(this.lastPos);
12
+ this.imagePosition.addListener(value => {
13
+ this.lastPos = value;
14
+ });
15
+ const imagePanResponder = PanResponder.create({
16
+ onStartShouldSetPanResponder: () => {
17
+ this.startPos = this.lastPos;
18
+ if (this.props.onPositionEnd) {
19
+ if (this.props.onPositionStart) this.props.onPositionStart();
20
+ this.setState({ panning: true });
21
+ return true;
22
+ }
23
+ return false;
24
+ },
25
+ onPanResponderMove: (event, gesture) => {
26
+ const newPos = this.state.isVertical ? { x: 0, y: this.startPos.y + gesture.dy } : { x: this.startPos.x + gesture.dx, y: 0 };
27
+ this.imagePosition.setValue(newPos);
28
+ },
29
+ onPanResponderTerminationRequest: (event, gesture) => true,
30
+ onPanResponderRelease: (event, gesture) => {
31
+ let endPos = { x: 0, y: 0 };
32
+ if (this.state.isVertical) {
33
+ const endY = this.startPos.y + gesture.dy;
34
+ const limitY = this.props.height - this.state.imageHeight;
35
+ if (endY > 0) {
36
+ // console.log('top reached', endY);
37
+ endPos = { x: 0, y: 0 };
38
+ } else if (endY < limitY) {
39
+ // console.log('bottom reached', endY, limitY);
40
+ endPos = { x: 0, y: limitY };
41
+ } else {
42
+ endPos = { x: 0, y: endY };
43
+ }
44
+ } else {
45
+ const endX = this.startPos.x + gesture.dx;
46
+ const limitX = this.props.width - this.state.imageWidth;
47
+ if (endX > 0) {
48
+ // console.log('left reached', endX);
49
+ endPos = { x: 0, y: 0 };
50
+ } else if (endX < limitX) {
51
+ // console.log('right reached', endX, limitX);
52
+ endPos = { x: limitX, y: 0 };
53
+ } else {
54
+ endPos = { x: endX, y: 0 };
55
+ }
56
+ }
57
+ Animated.spring(this.imagePosition, { toValue: endPos, useNativeDriver: false }).start();
58
+
59
+ if (this.props.onPositionEnd)
60
+ this.props.onPositionEnd({
61
+ x: endPos.x / this.state.imageWidth,
62
+ y: endPos.y / this.state.imageHeight,
63
+ });
64
+
65
+ this.setState({ panning: false });
66
+ },
67
+ onPanResponderTerminate: (event, gesture) => {
68
+ this.setState({ panning: false });
69
+ },
70
+ });
71
+
72
+ this.state = {
73
+ imageWidth: 0,
74
+ imageHeight: 0,
75
+ imagePanResponder,
76
+ isVertical: true,
77
+ showGuide: false,
78
+ animatedOpacity: new Animated.Value(1),
79
+ panning: false,
80
+ };
81
+ }
82
+
83
+ componentDidMount() {
84
+ this.calculateDimensions();
85
+ }
86
+
87
+ componentDidUpdate(prevProps) {
88
+ if (prevProps.source.uri !== this.props.source.uri) {
89
+ // console.log('componentDidUpdate', prevProps.source.uri, this.props.source.uri);
90
+ this.calculateDimensions();
91
+ }
92
+ }
93
+
94
+ calculateDimensions = () => {
95
+ Image.getSize(this.props.source.uri, (imageWidth, imageHeight) => {
96
+ // Default vertical positioning
97
+ const newWidth = this.props.width;
98
+ const newHeight = imageHeight * (this.props.width / imageWidth);
99
+
100
+ if (newHeight < this.props.height) {
101
+ // If vertical positioning isn't possible, enable horizontal positioning
102
+ this.setState(
103
+ {
104
+ imageWidth: imageWidth * (this.props.height / imageHeight),
105
+ imageHeight: this.props.height,
106
+ isVertical: false,
107
+ showGuide: this.props.onPositionEnd ? true : false,
108
+ },
109
+ this.onDimensionAvailable,
110
+ );
111
+ } else {
112
+ this.setState(
113
+ {
114
+ imageWidth: newWidth,
115
+ imageHeight: newHeight,
116
+ isVertical: true,
117
+ showGuide: this.props.onPositionEnd ? true : false,
118
+ },
119
+ this.onDimensionAvailable,
120
+ );
121
+ }
122
+ });
123
+ };
124
+
125
+ onDimensionAvailable = () => {
126
+ const { offset } = this.props;
127
+ const { imageWidth, imageHeight, isVertical } = this.state;
128
+
129
+ if (offset) {
130
+ // Apply percentage offset
131
+ const actual = {
132
+ x: offset.x * imageWidth,
133
+ y: offset.y * imageHeight,
134
+ };
135
+ this.imagePosition.setValue(actual);
136
+ } else {
137
+ // Centered by default
138
+ this.imagePosition.setValue({
139
+ x: isVertical ? 0 : -0.5 * imageWidth + 0.5 * this.props.width,
140
+ y: isVertical ? -0.5 * imageHeight + 0.5 * this.props.height : 0,
141
+ });
142
+ }
143
+
144
+ if (this.props.onPositionEnd) {
145
+ setTimeout(() => {
146
+ Animated.timing(this.state.animatedOpacity, {
147
+ toValue: 0,
148
+ duration: 900,
149
+ useNativeDriver: false,
150
+ }).start(() => {
151
+ this.setState({ showGuide: false });
152
+ });
153
+ }, 1000);
154
+ }
155
+ };
156
+
157
+ renderGrid() {
158
+ if (!this.state.panning) return null;
159
+ return (
160
+ <View style={styles.gridContainer}>
161
+ <View style={styles.gridHorizontal} />
162
+ <View style={styles.gridVertical} />
163
+ </View>
164
+ );
165
+ }
166
+
167
+ renderGuide() {
168
+ if (!this.state.showGuide) return null;
169
+ const { animatedOpacity, isVertical } = this.state;
170
+ return (
171
+ <Animated.View style={[styles.guideContainer, { opacity: animatedOpacity }]}>
172
+ <View style={[styles.guideIconContainer, { flexDirection: 'column' }]}>
173
+ <Icon name="arrow-up" type="font-awesome" iconStyle={[styles.guideIcon, !isVertical && styles.guideIconDisabled]} />
174
+ <Icon name="arrow-down" type="font-awesome" iconStyle={[styles.guideIcon, !isVertical && styles.guideIconDisabled]} />
175
+ </View>
176
+ <View style={[styles.guideIconContainer, { flexDirection: 'row' }]}>
177
+ <Icon name="arrow-left" type="font-awesome" iconStyle={[styles.guideIcon, isVertical && styles.guideIconDisabled]} />
178
+ <Icon name="arrow-right" type="font-awesome" iconStyle={[styles.guideIcon, isVertical && styles.guideIconDisabled]} />
179
+ </View>
180
+ </Animated.View>
181
+ );
182
+ }
183
+
184
+ render() {
185
+ const { imageWidth, imageHeight, imagePanResponder } = this.state;
186
+
187
+ return (
188
+ <View style={[styles.container, { width: this.props.width, height: this.props.height }, this.props.style]}>
189
+ <Animated.Image
190
+ source={this.props.source}
191
+ style={[{ width: imageWidth, height: imageHeight }, this.imagePosition.getLayout()]}
192
+ {...imagePanResponder.panHandlers}
193
+ />
194
+ {this.renderGrid()}
195
+ {this.renderGuide()}
196
+ </View>
197
+ );
198
+ }
199
+ }
200
+
201
+ const styles = StyleSheet.create({
202
+ container: {
203
+ overflow: 'hidden',
204
+ backgroundColor: BOXGREY,
205
+ },
206
+ guideContainer: {
207
+ position: 'absolute',
208
+ top: 0,
209
+ right: 0,
210
+ bottom: 0,
211
+ left: 0,
212
+ alignItems: 'center',
213
+ justifyContent: 'center',
214
+ backgroundColor: '#0006',
215
+ },
216
+ guideIconContainer: {
217
+ position: 'absolute',
218
+ top: 0,
219
+ right: 0,
220
+ bottom: 0,
221
+ left: 0,
222
+ alignItems: 'center',
223
+ justifyContent: 'center',
224
+ },
225
+ guideIcon: {
226
+ color: '#fff',
227
+ fontSize: 50,
228
+ margin: 15,
229
+ },
230
+ guideIconDisabled: {
231
+ color: TEXT_LIGHT,
232
+ },
233
+ gridContainer: {
234
+ position: 'absolute',
235
+ top: 0,
236
+ right: 0,
237
+ bottom: 0,
238
+ left: 0,
239
+ },
240
+ gridHorizontal: {
241
+ position: 'absolute',
242
+ top: 0,
243
+ right: 0,
244
+ left: 0,
245
+ height: '50%',
246
+ borderBottomWidth: 1,
247
+ borderBottomColor: BOXGREY,
248
+ },
249
+ gridVertical: {
250
+ position: 'absolute',
251
+ top: 0,
252
+ right: 0,
253
+ bottom: 0,
254
+ width: '50%',
255
+ borderLeftWidth: 1,
256
+ borderLeftColor: BOXGREY,
257
+ },
258
+ });
259
+
260
+ export default PositionedImage;
@@ -0,0 +1,111 @@
1
+ import React, { Component } from 'react';
2
+ import { View, Text, Image, TouchableOpacity } from 'react-native';
3
+ import { connect } from 'react-redux';
4
+ import _ from 'lodash';
5
+ import { getReactions, getShadowStyle } from '../helper';
6
+ import { BG_GREY, TEXT_DARK, getMainBrandingColourFromState } from '../colours';
7
+ import { reactionActions } from '../apis';
8
+
9
+ class Reaction extends Component {
10
+ onPressReaction = () => {
11
+ if (!this.props.reactions[this.props.userId]) {
12
+ reactionActions.add(this.props.entityId, this.props.entityType, this.props.reaction.key, this.props.site);
13
+ this.props.onLike();
14
+ } else {
15
+ reactionActions.remove(this.props.entityId, this.props.entityType, this.props.reaction.key);
16
+ this.props.onUnlike();
17
+ }
18
+ };
19
+
20
+ getStyleType() {
21
+ if (this.props.reactions[this.props.userId]) {
22
+ return 'ThisHL';
23
+ }
24
+ if (this.props.anyHighlighted) {
25
+ return 'OtherHL';
26
+ }
27
+ return 'Default';
28
+ }
29
+
30
+ getTopReaction() {
31
+ return _.maxBy(Object.keys(this.props.reactions), key => {
32
+ return Object.keys(this.props.reactions[key]).length;
33
+ });
34
+ }
35
+
36
+ getIcon(topReaction) {
37
+ return _.find(getReactions(), r => {
38
+ return r.key === topReaction;
39
+ }).icon;
40
+ }
41
+
42
+ render() {
43
+ if (this.props.topReactionOnly) {
44
+ const topReaction = this.getTopReaction();
45
+ if (!topReaction) {
46
+ return null;
47
+ }
48
+ return (
49
+ <View style={[styles.container, styles.containerThisHL, this.props.style]}>
50
+ <Image style={[styles.image, this.props.imageStyle]} source={this.getIcon(topReaction)} />
51
+ <Text style={[styles.count, this.props.countStyle]}>{Object.keys(this.props.reactions[topReaction]).length}</Text>
52
+ </View>
53
+ );
54
+ }
55
+ return (
56
+ <TouchableOpacity style={styles.touchable} onPress={this.onPressReaction} disabled={this.props.user.type === 'KIOSK'}>
57
+ <View style={[styles.container, styles[`container${this.getStyleType()}`], this.props.style]}>
58
+ <Image style={[styles.image, this.props.imageStyle]} source={this.props.reaction.icon} />
59
+ <Text style={[styles.count, this.getStyleType() === 'ThisHL' && { color: this.props.colourBrandingMain }, this.props.countStyle]}>
60
+ {Object.keys(this.props.reactions).length}
61
+ </Text>
62
+ </View>
63
+ </TouchableOpacity>
64
+ );
65
+ }
66
+ }
67
+
68
+ const styles = {
69
+ touchable: {
70
+ marginHorizontal: 4,
71
+ width: 60,
72
+ height: 30,
73
+ },
74
+ container: {
75
+ width: 60,
76
+ height: 30,
77
+ borderRadius: 15,
78
+ backgroundColor: BG_GREY,
79
+ flexDirection: 'row-reverse',
80
+ alignItems: 'center',
81
+ paddingHorizontal: 8,
82
+ },
83
+ containerThisHL: {
84
+ ...getShadowStyle(),
85
+ },
86
+ containerOtherHL: {
87
+ opacity: 0.5,
88
+ },
89
+ image: {
90
+ width: 20,
91
+ height: 20,
92
+ resizeMode: 'contain',
93
+ },
94
+ count: {
95
+ fontFamily: 'sf-medium',
96
+ color: TEXT_DARK,
97
+ flex: 1,
98
+ textAlign: 'center',
99
+ fontSize: 16,
100
+ },
101
+ };
102
+
103
+ const mapStateToProps = state => {
104
+ return {
105
+ colourBrandingMain: getMainBrandingColourFromState(state),
106
+ user: state.user,
107
+ };
108
+ };
109
+
110
+ const reaction = connect(mapStateToProps, {})(Reaction);
111
+ export { reaction as Reaction };
@@ -0,0 +1,75 @@
1
+ import React, { Component } from 'react';
2
+ import { View } from 'react-native';
3
+ import { getReactions, getUserPreview } from '../helper';
4
+ import { Reaction } from './Reaction';
5
+
6
+ class Reactions extends Component {
7
+ state = {
8
+ reactions: getReactions(),
9
+ };
10
+
11
+ getReaction(key) {
12
+ if (!this.props.entity) {
13
+ return {};
14
+ }
15
+ if (!this.props.entity.Reactions) {
16
+ this.props.entity.Reactions = {};
17
+ }
18
+ return this.props.entity.Reactions[key] || {};
19
+ }
20
+
21
+ onLike(key) {
22
+ if (!this.props.entity.Reactions) {
23
+ this.props.entity.Reactions = {};
24
+ }
25
+ if (!this.props.entity.Reactions[key]) {
26
+ this.props.entity.Reactions[key] = {};
27
+ }
28
+ this.props.entity.Reactions[key][this.props.user.uid] = getUserPreview(this.props.user);
29
+ this.props.onUpdateReactions();
30
+ }
31
+
32
+ onUnlike(key) {
33
+ if (!this.props.entity.Reactions) {
34
+ return;
35
+ }
36
+ if (!this.props.entity.Reactions[key]) {
37
+ return;
38
+ }
39
+ delete this.props.entity.Reactions[key][this.props.user.uid];
40
+ this.props.onUpdateReactions();
41
+ }
42
+
43
+ render() {
44
+ return (
45
+ <View style={[styles.container, this.props.style]}>
46
+ {this.state.reactions.map((r, i) => {
47
+ return (
48
+ <Reaction
49
+ key={i}
50
+ reaction={r}
51
+ entityType={this.props.entityType}
52
+ entityId={this.props.entityId}
53
+ reactions={this.getReaction(r.key)}
54
+ anyHighlighted={this.props.anyHighlighted}
55
+ userId={this.props.user.uid}
56
+ onLike={this.onLike.bind(this, r.key)}
57
+ onUnlike={this.onUnlike.bind(this, r.key)}
58
+ site={this.props.site}
59
+ />
60
+ );
61
+ })}
62
+ </View>
63
+ );
64
+ }
65
+ }
66
+
67
+ const styles = {
68
+ container: {
69
+ flexDirection: 'row',
70
+ justifyContent: 'center',
71
+ width: '100%',
72
+ },
73
+ };
74
+
75
+ export { Reactions };