@plusscommunities/pluss-core-app 1.2.4 → 1.4.2-beta.0

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.
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@plusscommunities/pluss-core-app",
3
- "version": "1.2.4",
3
+ "version": "1.4.2-beta.0",
4
4
  "description": "Core extension package for Pluss Communities platform",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
7
+ "prepatch": "npm version prepatch --preid=beta",
7
8
  "patch": "npm version patch",
9
+ "preupload": "npm publish --access public --tag beta && rm -rf node_modules",
10
+ "preupload:p": "npm run prepatch && npm run preupload",
8
11
  "upload": "npm publish --access public && rm -rf node_modules",
9
12
  "upload:p": "npm run patch && npm run upload"
10
13
  },
@@ -12,31 +15,33 @@
12
15
  "license": "ISC",
13
16
  "dependencies": {
14
17
  "@expo/vector-icons": "^12.0.0",
15
- "aws-amplify": "^3.4.0",
16
- "aws-amplify-react-native": "^4.3.3",
17
- "expo-av": "~9.1.2",
18
- "expo-constants": "~10.1.3",
19
- "expo-file-system": "~11.0.2",
20
- "expo-image-manipulator": "~9.1.0",
21
- "expo-image-picker": "~10.1.4",
22
- "expo-linear-gradient": "~9.1.0",
23
- "expo-media-library": "~12.0.2",
24
- "expo-permissions": "~12.0.1",
25
- "expo-screen-orientation": "~3.1.0",
26
- "expo-sharing": "~9.1.2",
18
+ "@react-native-async-storage/async-storage": "~1.15.0",
19
+ "@react-native-community/netinfo": "7.1.3",
20
+ "@react-native-picker/picker": "2.2.1",
21
+ "aws-amplify": "^4.3.11",
22
+ "aws-amplify-react-native": "^6.0.2",
23
+ "expo-av": "~10.2.0",
24
+ "expo-constants": "~13.0.0",
25
+ "expo-file-system": "~13.1.0",
26
+ "expo-image-manipulator": "~10.2.0",
27
+ "expo-image-picker": "~12.0.1",
28
+ "expo-linear-gradient": "~11.0.0",
29
+ "expo-media-library": "~14.0.0",
30
+ "expo-screen-orientation": "~4.1.1",
31
+ "expo-sharing": "~10.1.0",
27
32
  "expo-video-player": "^1.6.0",
28
33
  "js-cookie": "^2.2.1",
29
34
  "lodash": "^4.17.4",
30
35
  "mime-types": "^2.1.24",
31
36
  "moment": "^2.18.1",
32
- "react": "16.13.1",
33
- "react-native": "https://github.com/expo/react-native/archive/sdk-41.0.0.tar.gz",
37
+ "react": "17.0.1",
38
+ "react-native": "0.64.3",
34
39
  "react-native-auto-height-image": "3.1.3",
35
40
  "react-native-elements": "^0.17.0",
36
41
  "react-native-image-zoom-viewer": "^3.0.1",
37
42
  "react-native-iphone-x-helper": "^1.3.1",
38
43
  "react-native-vimeo-iframe": "^1.0.4",
39
- "react-native-webview": "11.2.3",
44
+ "react-native-webview": "11.15.0",
40
45
  "react-native-youtube-iframe": "^2.2.1",
41
46
  "react-redux": "^5.0.5"
42
47
  },
package/src/apis/index.js CHANGED
@@ -4,4 +4,5 @@ export * from './followerActions';
4
4
  export * from './contactActions';
5
5
  export * from './eventActions';
6
6
  export * from './analyticsActions';
7
+ export * from './notificationActions';
7
8
  export { default as userActions } from './userActions';
@@ -0,0 +1,47 @@
1
+ import { getUrl } from '../helper';
2
+ import { authedFunction } from '../session';
3
+
4
+ export const notificationActions = {
5
+ getAppNotificationSetting: () => {
6
+ return authedFunction({
7
+ method: 'GET',
8
+ url: getUrl('notifications', 'getState/get', {
9
+ type: 'app',
10
+ }),
11
+ });
12
+ },
13
+ muteApp: () => {
14
+ return authedFunction({
15
+ method: 'POST',
16
+ url: getUrl('notifications', 'updateState/mute'),
17
+ data: { type: 'app' },
18
+ });
19
+ },
20
+ unmuteApp: () => {
21
+ return authedFunction({
22
+ method: 'POST',
23
+ url: getUrl('notifications', 'updateState/unmute'),
24
+ data: { type: 'app' },
25
+ });
26
+ },
27
+ getEntityNotificationSetting: (type, id) => {
28
+ return authedFunction({
29
+ method: 'GET',
30
+ url: getUrl('notifications', 'getState/get', { type, id }),
31
+ });
32
+ },
33
+ muteEntity: (type, id) => {
34
+ return authedFunction({
35
+ method: 'POST',
36
+ url: getUrl('notifications', 'updateState/mute'),
37
+ data: { type, id },
38
+ });
39
+ },
40
+ unmuteEntity: (type, id) => {
41
+ return authedFunction({
42
+ method: 'POST',
43
+ url: getUrl('notifications', 'updateState/unmute'),
44
+ data: { type, id },
45
+ });
46
+ },
47
+ };
@@ -28,7 +28,7 @@ class CommentReply extends Component {
28
28
  componentDidMount() {
29
29
  if (this.props.commentSection) {
30
30
  this.setState({
31
- commentsLoading: this.props.commentSection.getWrappedInstance().isLoading(),
31
+ commentsLoading: this.props.commentSection.isLoading(),
32
32
  });
33
33
  }
34
34
  }
@@ -93,13 +93,13 @@ class CommentReply extends Component {
93
93
  });
94
94
  Keyboard.dismiss();
95
95
  if (this.props.commentSection) {
96
- this.props.commentSection.getWrappedInstance().startedAddingComment();
96
+ this.props.commentSection.startedAddingComment();
97
97
  }
98
98
  reactionActions
99
99
  .addComment(this.props.entityId, this.props.entityType, this.props.entityName, this.props.site, text, image)
100
100
  .then(res => {
101
101
  if (this.props.commentSection) {
102
- this.props.commentSection.getWrappedInstance().commentAdded(res.data);
102
+ this.props.commentSection.commentAdded(res.data);
103
103
  }
104
104
  this.setState({
105
105
  addingComment: false,
@@ -125,7 +125,7 @@ class CommentReply extends Component {
125
125
  if (this.state.uploadingCommentImage || !_.isEmpty(this.state.commentImageInput)) {
126
126
  return;
127
127
  }
128
- this.commentImageUploader.getWrappedInstance().showUploadMenu();
128
+ this.commentImageUploader.showUploadMenu();
129
129
  }
130
130
 
131
131
  renderImageAttachment() {
@@ -329,5 +329,5 @@ const mapStateToProps = state => {
329
329
  return { user };
330
330
  };
331
331
 
332
- const commentReply = connect(mapStateToProps, {}, null, { withRef: true })(CommentReply);
332
+ const commentReply = connect(mapStateToProps, {}, null, { forwardRef: true })(CommentReply);
333
333
  export { commentReply as CommentReply };
@@ -1,16 +1,17 @@
1
1
  import React, { Component } from 'react';
2
- import { View, Text, TouchableOpacity, Image } from 'react-native';
2
+ import { View, Text, TouchableOpacity, Image, StyleSheet } from 'react-native';
3
3
  import _ from 'lodash';
4
4
  import moment from 'moment';
5
5
  import { connect } from 'react-redux';
6
6
  import { Icon } from 'react-native-elements';
7
- import { getPluralS, getThumb300, get1400 } from '../helper';
7
+ import { getPluralS, getThumb300, get1400, getSiteSettingFromState } from '../helper';
8
8
  import { getMainBrandingColourFromState, TEXT_DARKEST, BG_GREY, TEXT_LIGHT, LINEGREY } from '../colours';
9
- import { Spinner } from './Spinner';
10
- import { reactionActions } from '../apis';
9
+ import { reactionActions, notificationActions } from '../apis';
11
10
  import { ConfirmPopup } from './ConfirmPopup';
12
11
  import { ProfilePic } from './ProfilePic';
13
12
  import { ImagePopup } from './ImagePopup';
13
+ import { InlineButton } from './InlineButton';
14
+ import { Spinner } from './Spinner';
14
15
 
15
16
  class CommentSection extends Component {
16
17
  constructor(props) {
@@ -23,10 +24,15 @@ class CommentSection extends Component {
23
24
  commentToReport: null,
24
25
  commentReportedStatus: null,
25
26
  reportLoading: false,
27
+ processing: false,
28
+ muteExpiry: null,
29
+ muteLoaded: false,
26
30
  };
27
31
  }
28
32
 
29
33
  componentDidMount() {
34
+ this.getNotificationSate();
35
+
30
36
  if (!_.includes(this.props.user.hidden, 'viewComment')) {
31
37
  this.getComments();
32
38
  }
@@ -94,9 +100,67 @@ class CommentSection extends Component {
94
100
  }
95
101
 
96
102
  onGoToAdd() {
97
- this.props.commentReply.getWrappedInstance().focusInput();
103
+ this.props.commentReply.focusInput();
98
104
  }
99
105
 
106
+ onMute = () => {
107
+ const { entityType, entityId } = this.props;
108
+ this.setState({ processing: true }, async () => {
109
+ try {
110
+ const { data } = await notificationActions.muteEntity(entityType, entityId);
111
+ // console.log('onMute', data);
112
+ const muteExpiry = moment(data.Expiry);
113
+ this.setState({ muteExpiry, processing: false });
114
+ } catch (error) {
115
+ console.error('onMute', error);
116
+ this.setState({ processing: false });
117
+ }
118
+ });
119
+ };
120
+
121
+ onUnmute = () => {
122
+ const { entityType, entityId } = this.props;
123
+ this.setState({ processing: true }, async () => {
124
+ try {
125
+ const { data } = await notificationActions.unmuteEntity(entityType, entityId);
126
+ // console.log('onUnmute', data);
127
+ this.setState({ muteExpiry: null, processing: false });
128
+ } catch (error) {
129
+ console.error('onUnmute', error);
130
+ this.setState({ processing: false });
131
+ }
132
+ });
133
+ };
134
+
135
+ isMuted = () => {
136
+ const { muteExpiry } = this.state;
137
+ return muteExpiry && moment() <= muteExpiry;
138
+ };
139
+
140
+ getMuteRemaining = () => {
141
+ const { muteExpiry } = this.state;
142
+ const actual = muteExpiry ? moment.duration(muteExpiry.diff(moment())).asHours() : 0;
143
+ return Math.abs(Math.ceil(actual));
144
+ };
145
+
146
+ getNotificationSate = () => {
147
+ const { processing } = this.state;
148
+ const { entityType, entityId, notificationsForComments } = this.props;
149
+ if (processing || !notificationsForComments) return;
150
+
151
+ this.setState({ processing: true }, async () => {
152
+ try {
153
+ const { data } = await notificationActions.getEntityNotificationSetting(entityType, entityId);
154
+ // console.log('getNotificationSate', data);
155
+ const muteExpiry = data ? moment(data.Expiry) : null;
156
+ this.setState({ muteExpiry, muteLoaded: true, processing: false });
157
+ } catch (error) {
158
+ console.error('getNotificationSate', error);
159
+ this.setState({ processing: false });
160
+ }
161
+ });
162
+ };
163
+
100
164
  getAdjustedSize(size) {
101
165
  if (this.props.scaleFont) {
102
166
  return size + this.props.user.fontScale;
@@ -111,7 +175,7 @@ class CommentSection extends Component {
111
175
  commentsLoading: true,
112
176
  });
113
177
  if (this.props.commentReply) {
114
- this.props.commentReply.getWrappedInstance().loadingStarted();
178
+ this.props.commentReply.loadingStarted();
115
179
  }
116
180
  this.loadComments();
117
181
  }
@@ -145,7 +209,7 @@ class CommentSection extends Component {
145
209
  },
146
210
  );
147
211
  if (this.props.commentReply) {
148
- this.props.commentReply.getWrappedInstance().loadingCompleted();
212
+ this.props.commentReply.loadingCompleted();
149
213
  }
150
214
  if (this.props.live) {
151
215
  this.loadTimer = setTimeout(() => {
@@ -288,6 +352,47 @@ class CommentSection extends Component {
288
352
  );
289
353
  }
290
354
 
355
+ renderMute() {
356
+ const { notificationsForComments } = this.props;
357
+ const { muteLoaded, muteExpiry, processing } = this.state;
358
+ if (!notificationsForComments || !muteLoaded) return null;
359
+
360
+ if (processing)
361
+ return (
362
+ <View style={styles.muteSpinnerContainer}>
363
+ <Spinner size={'small'} color={this.props.colourBrandingMain} />
364
+ </View>
365
+ );
366
+
367
+ const isMuted = this.isMuted();
368
+ const hours = this.getMuteRemaining();
369
+ const mutedFor = `Muted for ${hours} hour${getPluralS(hours)}`;
370
+ // console.log(muteExpiry.format('DD MMM YYYY hh:mm a'));
371
+
372
+ return (
373
+ <View style={styles.muteContainer}>
374
+ {isMuted && muteExpiry ? <Text style={[{ color: this.props.colourBrandingMain }, styles.mutedForText]}>{mutedFor}</Text> : null}
375
+ <InlineButton
376
+ onPress={isMuted ? this.onUnmute : this.onMute}
377
+ color="#fff"
378
+ style={[{ borderColor: this.props.colourBrandingMain }, styles.muteButton]}
379
+ disabled={processing}
380
+ disabledOpacity
381
+ noText
382
+ >
383
+ <View style={styles.muteButtonInner}>
384
+ <Icon
385
+ name={isMuted ? 'bell-o' : 'bell-slash-o'}
386
+ type={'font-awesome'}
387
+ iconStyle={[{ color: this.props.colourBrandingMain }, styles.muteButtonIcon]}
388
+ />
389
+ <Text style={[{ color: this.props.colourBrandingMain }, styles.muteButtonText]}>{isMuted ? 'Unmute' : 'Mute'}</Text>
390
+ </View>
391
+ </InlineButton>
392
+ </View>
393
+ );
394
+ }
395
+
291
396
  renderComments() {
292
397
  if (this.state.commentsLoading) {
293
398
  return (
@@ -317,11 +422,14 @@ class CommentSection extends Component {
317
422
  <View style={styles.commentSection}>
318
423
  {!this.isEmpty() && (
319
424
  <View style={styles.commentSectionTitleRow}>
320
- {!this.props.hideAddComment && this.state.comments.length > 2 && (
321
- <TouchableOpacity onPress={this.onGoToAdd.bind(this)}>
322
- <Text style={[styles.goToText, { color: this.props.colourBrandingMain }]}>Add a comment</Text>
323
- </TouchableOpacity>
324
- )}
425
+ <View style={{ alignItems: 'flex-end' }}>
426
+ {this.renderMute()}
427
+ {!this.props.hideAddComment && this.state.comments.length > 2 && (
428
+ <TouchableOpacity onPress={this.onGoToAdd.bind(this)}>
429
+ <Text style={[styles.goToText, { color: this.props.colourBrandingMain }]}>Add a comment</Text>
430
+ </TouchableOpacity>
431
+ )}
432
+ </View>
325
433
  <Text style={[styles.commentCount, { fontSize: this.getAdjustedSize(15) }]}>
326
434
  {this.state.comments.length}
327
435
  {` comment${getPluralS(this.state.comments.length)}`}
@@ -374,22 +482,22 @@ class CommentSection extends Component {
374
482
  }
375
483
  }
376
484
 
377
- const styles = {
485
+ const styles = StyleSheet.create({
378
486
  commentSectionOuter: {
379
487
  flex: 1,
380
488
  paddingHorizontal: 16,
381
489
  },
382
490
  commentSection: {
383
491
  flex: 1,
384
- paddingVertical: 16,
492
+ paddingVertical: 12,
385
493
  borderTopColor: LINEGREY,
386
494
  borderTopWidth: 1,
387
- borderTopStyle: 'solid',
388
495
  },
389
496
  commentCount: {
390
497
  fontFamily: 'sf-semibold',
391
498
  color: TEXT_DARKEST,
392
499
  flex: 1,
500
+ marginTop: 4,
393
501
  },
394
502
  comment: {
395
503
  marginTop: 16,
@@ -463,14 +571,49 @@ const styles = {
463
571
  reportLoadingContainer: {
464
572
  paddingVertical: 10,
465
573
  },
466
- };
574
+ goToText: {
575
+ marginTop: 4,
576
+ },
577
+ muteSpinnerContainer: {
578
+ width: 100,
579
+ marginBottom: 6,
580
+ },
581
+ muteContainer: {
582
+ flexDirection: 'row',
583
+ alignItems: 'center',
584
+ justifyContent: 'flex-end',
585
+ marginBottom: 6,
586
+ },
587
+ mutedForText: {
588
+ fontFamily: 'sf-semibold',
589
+ fontSize: 13,
590
+ marginRight: 8,
591
+ },
592
+ muteButton: {
593
+ borderWidth: 1,
594
+ paddingHorizontal: 8,
595
+ },
596
+ muteButtonInner: {
597
+ flexDirection: 'row',
598
+ alignItems: 'center',
599
+ },
600
+ muteButtonIcon: {
601
+ fontSize: 14,
602
+ },
603
+ muteButtonText: {
604
+ fontFamily: 'sf-semibold',
605
+ fontSize: 14,
606
+ marginLeft: 6,
607
+ },
608
+ });
467
609
 
468
610
  const mapStateToProps = state => {
469
611
  return {
470
612
  colourBrandingMain: getMainBrandingColourFromState(state),
471
613
  user: state.user,
614
+ notificationsForComments: getSiteSettingFromState(state, 'NotificationsForComments', false),
472
615
  };
473
616
  };
474
617
 
475
- const commentSection = connect(mapStateToProps, {}, null, { withRef: true })(CommentSection);
618
+ const commentSection = connect(mapStateToProps, {}, null, { forwardRef: true })(CommentSection);
476
619
  export { commentSection as CommentSection };
@@ -5,7 +5,7 @@ import { Spinner } from './Spinner';
5
5
 
6
6
  class ImageUploadProgress extends Component {
7
7
  onRetryUpload = (imageUri, uploadUri) => {
8
- this.props.uploader.getWrappedInstance().retryUpload(imageUri, uploadUri);
8
+ this.props.uploader.retryUpload(imageUri, uploadUri);
9
9
  };
10
10
 
11
11
  render() {
@@ -3,7 +3,7 @@ import { View, ScrollView, TouchableOpacity, Text, Platform, Linking, Modal, Dim
3
3
  import { connect } from 'react-redux';
4
4
  import { Icon } from 'react-native-elements';
5
5
  import _ from 'lodash';
6
- import * as Permissions from 'expo-permissions';
6
+ import { Camera } from 'expo-camera';
7
7
  import * as MediaLibrary from 'expo-media-library';
8
8
  import * as ImageManipulator from 'expo-image-manipulator';
9
9
  import * as ImagePicker from 'expo-image-picker';
@@ -60,7 +60,7 @@ class ImageUploader extends Component {
60
60
  }
61
61
 
62
62
  loadLocalAlbums = async () => {
63
- const hasPermission = await Permissions.getAsync(Permissions.CAMERA_ROLL);
63
+ const hasPermission = await MediaLibrary.getPermissionsAsync();
64
64
  if (!hasPermission.granted) return;
65
65
 
66
66
  this.setState({ loadingLocalFolders: true }, async () => {
@@ -254,8 +254,8 @@ class ImageUploader extends Component {
254
254
  }
255
255
 
256
256
  askPermissionsAsync = async () => {
257
- const cameraPermission = await Permissions.askAsync(Permissions.CAMERA);
258
- const rollPermission = await Permissions.askAsync(Permissions.CAMERA_ROLL);
257
+ const cameraPermission = await Camera.requestCameraPermissionsAsync();
258
+ const rollPermission = await MediaLibrary.requestPermissionsAsync();
259
259
  if (cameraPermission.status !== 'granted' || rollPermission.status !== 'granted') {
260
260
  this.showWarningPopup(cameraPermission.status !== 'granted', rollPermission.status !== 'granted');
261
261
  return false;
@@ -786,4 +786,4 @@ const mapStateToProps = state => {
786
786
  };
787
787
  };
788
788
 
789
- export default connect(mapStateToProps, { stockImagesLoaded, imageLibraryLoaded }, null, { withRef: true })(ImageUploader);
789
+ export default connect(mapStateToProps, { stockImagesLoaded, imageLibraryLoaded }, null, { forwardRef: true })(ImageUploader);
@@ -158,7 +158,7 @@ class PlussChat extends Component {
158
158
  };
159
159
 
160
160
  showUploadMenu() {
161
- this.imageUploader.getWrappedInstance().showUploadMenu();
161
+ this.imageUploader.showUploadMenu();
162
162
  }
163
163
 
164
164
  onFocusInput = () => {
@@ -3,7 +3,8 @@ import { Platform, View, Modal, TouchableOpacity, StyleSheet } from 'react-nativ
3
3
  import * as ScreenOrientation from 'expo-screen-orientation';
4
4
  import { StatusBarHeight, getCompressed, imageExists } from '../helper';
5
5
  import { Pl60Icon } from '../fonts';
6
- import { SharingTools, MediaPlayer } from './';
6
+ import { SharingTools } from './SharingTools';
7
+ import { MediaPlayer } from './MediaPlayer';
7
8
 
8
9
  class VideoPopup extends Component {
9
10
  constructor(props) {
@@ -3,7 +3,7 @@ import { StyleSheet, View, FlatList, Dimensions, ActivityIndicator, Platform } f
3
3
  import _ from 'lodash';
4
4
  import * as ScreenOrientation from 'expo-screen-orientation';
5
5
  import * as MediaLibrary from 'expo-media-library';
6
- import * as Permissions from 'expo-permissions';
6
+ import { Camera } from 'expo-camera';
7
7
  import { isVideo } from '../../helper';
8
8
  import ImageTile from './ImageTile';
9
9
 
@@ -49,8 +49,8 @@ export default class ImageBrowser extends React.Component {
49
49
  };
50
50
 
51
51
  getPermissionsAsync = async () => {
52
- const { status: camera } = await Permissions.askAsync(Permissions.CAMERA);
53
- const { status: cameraRoll } = await Permissions.askAsync(Permissions.CAMERA_ROLL);
52
+ const { status: camera } = await Camera.requestCameraPermissionsAsync();
53
+ const { status: cameraRoll } = await MediaLibrary.requestPermissionsAsync();
54
54
  this.setState({
55
55
  hasCameraPermission: camera === 'granted',
56
56
  hasCameraRollPermission: cameraRoll === 'granted',