@plusscommunities/pluss-core-app 1.2.3 → 1.4.1-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.3",
3
+ "version": "1.4.1-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
+ };
@@ -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
  }
@@ -97,6 +103,64 @@ class CommentSection extends Component {
97
103
  this.props.commentReply.getWrappedInstance().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;
@@ -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 };
@@ -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);
@@ -39,7 +39,7 @@ class MediaPlayer extends Component {
39
39
  }
40
40
 
41
41
  componentDidMount = async () => {
42
- this.setupForPlaying();
42
+ await this.setupForPlaying();
43
43
 
44
44
  ScreenOrientation.lockAsync(Platform.OS === 'ios' ? ScreenOrientation.OrientationLock.DEFAULT : ScreenOrientation.OrientationLock.ALL);
45
45
  const motionAvailalble = await DeviceMotion.isAvailableAsync();
@@ -75,25 +75,26 @@ class MediaPlayer extends Component {
75
75
 
76
76
  setupForPlaying = async () => {
77
77
  // Extract live stream info
78
- const { source } = this.props;
79
- const isUrlLink = source.startsWith('http');
80
- const isYoutube = source.includes('youtube.com/watch?v=') || source.includes('youtu.be/');
81
- const isVimeo = source.includes('vimeo.com/');
82
- this.setState({ isUrlLink, isYoutube, isVimeo, playbackLoaded: isYoutube || isVimeo });
78
+ const { source, useVideoPlayer } = this.props;
79
+ const isUrlLink = source && source.startsWith('http');
80
+ const isYoutube = source && (source.includes('youtube.com/watch?v=') || source.includes('youtu.be/'));
81
+ const isVimeo = source && source.includes('vimeo.com/');
82
+ let heightFactor = 0;
83
83
 
84
84
  if (isYoutube) {
85
85
  const metadata = await getYoutubeMeta(this.getYoutubeVideoId(source));
86
- this.setState({ heightFactor: metadata.height / metadata.width, readyToRender: true });
87
- // console.log('youtube metadata', metadata);
88
- } else if (isVimeo) {
89
- this.setState({ heightFactor: 0.565, readyToRender: true });
86
+ heightFactor = metadata.height / metadata.width;
87
+ } else if (useVideoPlayer) {
88
+ heightFactor = 0;
90
89
  } else {
91
- this.setState({ readyToRender: true });
90
+ heightFactor = 0.565;
92
91
  }
92
+
93
+ this.setState({ isUrlLink, isYoutube, isVimeo, playbackLoaded: isYoutube || isVimeo, heightFactor, readyToRender: true });
93
94
  };
94
95
 
95
96
  getStreamPlaybackUrl = () => {
96
- // return 'https://stream.mux.com/nflNhyFQMlrSt00II01Hjh2BLSW009PH1tl023pdpJ01s1vc.m3u8';
97
+ // return 'https://stream.mux.com/Ym4nKktRpGuZNBBBlzhxpyRwZLpvMlPRchcu01x6Ur600.m3u8';
97
98
  return `https://stream.mux.com/${this.props.playbackId}.m3u8`;
98
99
  };
99
100
 
@@ -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',