@plusscommunities/pluss-core-app 1.1.2 → 1.2.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,11 +1,12 @@
1
1
  {
2
2
  "name": "@plusscommunities/pluss-core-app",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Core extension package for Pluss Communities platform",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
7
- "upload": "npm version patch && npm publish --access public && rm -rf node_modules",
8
- "test": "echo \"Error: no test specified\" && exit 1"
7
+ "patch": "npm version patch",
8
+ "upload": "npm publish --access public && rm -rf node_modules",
9
+ "upload:p": "npm run patch && npm run upload"
9
10
  },
10
11
  "author": "Phillip Suh",
11
12
  "license": "ISC",
@@ -276,6 +276,7 @@ class Header extends Component {
276
276
  <Animated.View
277
277
  style={[
278
278
  styles.container,
279
+ !this.props.noShadow && styles.containerShadow,
279
280
  this.props.transparent && styles.transparentContainer,
280
281
  this.props.style,
281
282
  { backgroundColor: this.props.headerColourOffset, elevation: this.props.headerElev, shadowColor: this.props.headerShadow },
@@ -297,6 +298,7 @@ class Header extends Component {
297
298
  <View
298
299
  style={[
299
300
  styles.container,
301
+ !this.props.noShadow && styles.containerShadow,
300
302
  this.props.transparent && styles.transparentContainer,
301
303
  this.props.lineSeparated && styles.lineSeparated,
302
304
  this.props.popupHeader && styles.popupHeader,
@@ -331,6 +333,9 @@ const styles = {
331
333
  container: {
332
334
  alignSelf: 'stretch',
333
335
  backgroundColor: '#fff',
336
+ zIndex: 10,
337
+ },
338
+ containerShadow: {
334
339
  shadowColor: '#000',
335
340
  shadowOffset: {
336
341
  width: 0,
@@ -339,7 +344,6 @@ const styles = {
339
344
  shadowOpacity: 0.1,
340
345
  shadowRadius: 6,
341
346
  elevation: 3,
342
- zIndex: 10,
343
347
  },
344
348
  lineSeparated: {
345
349
  borderBottomWidth: 1,
@@ -45,7 +45,7 @@ class ImagePopup extends Component {
45
45
  return { url: get1400(image?.uri), original: image?.uri, isVideo: isVideo(image?.uri), date: image?.date, user: image?.user };
46
46
  });
47
47
 
48
- this.setState({ images, index: this.props.index || 0 });
48
+ this.setState({ images, index: this.state.index || this.props.index || 0 });
49
49
  };
50
50
 
51
51
  onChange = index => {
@@ -1,27 +1,17 @@
1
1
  import React, { Component } from 'react';
2
- import { View, StyleSheet, Animated } from 'react-native';
3
- import { TEXT_BLUEGREY } from '../colours';
2
+ import { StyleSheet, Animated, Dimensions } from 'react-native';
3
+ import { connect } from 'react-redux';
4
+ import { getMainBrandingColourFromState } from '../colours';
4
5
 
5
- const OPACITY_START = 0.1;
6
- const OPACITY_END1 = 0.4;
7
- const OPACITY_END2 = 0.7;
8
- const OPACITY_END3 = 1;
9
- const LEFT_START = 1;
10
- const LEFT_END1 = 16;
11
- const LEFT_END2 = 32;
12
- const CIRCLE_SIZE = 8;
13
- const DEFAULT_HEIGHT = 38;
6
+ const DEFAULT_HEIGHT = 4;
7
+ const DEFAULT_WIDTH = Dimensions.get('window').width;
14
8
 
15
9
  class LoadingIndicator extends Component {
16
10
  constructor(props) {
17
11
  super(props);
18
12
  this.state = {
19
- animatedOpacity1: new Animated.Value(OPACITY_START),
20
- animatedOpacity2: new Animated.Value(OPACITY_START),
21
- animatedOpacity3: new Animated.Value(OPACITY_START),
22
- animatedOpacity4: new Animated.Value(0),
23
- animatedLeft4: new Animated.Value(LEFT_START),
24
- animatedHeight: new Animated.Value(this.props.visible ? DEFAULT_HEIGHT : 0),
13
+ animatedLeft: new Animated.Value(-DEFAULT_WIDTH),
14
+ animatedOpacity: new Animated.Value(0),
25
15
  };
26
16
  this.height = 0;
27
17
  }
@@ -36,8 +26,8 @@ class LoadingIndicator extends Component {
36
26
 
37
27
  componentDidUpdate(prevProps) {
38
28
  if (prevProps.visible !== this.props.visible) {
39
- Animated.timing(this.state.animatedHeight, {
40
- toValue: this.props.visible ? DEFAULT_HEIGHT : 0,
29
+ Animated.timing(this.state.animatedOpacity, {
30
+ toValue: this.props.visible ? 0.8 : 0,
41
31
  duration: 300,
42
32
  useNativeDriver: false,
43
33
  }).start();
@@ -46,95 +36,32 @@ class LoadingIndicator extends Component {
46
36
 
47
37
  animate = () => {
48
38
  Animated.sequence([
49
- Animated.timing(this.state.animatedOpacity4, {
50
- toValue: OPACITY_END1,
51
- duration: 750,
39
+ Animated.timing(this.state.animatedLeft, {
40
+ toValue: DEFAULT_WIDTH,
41
+ duration: 1500,
52
42
  useNativeDriver: false,
53
43
  }),
54
- Animated.parallel([
55
- Animated.timing(this.state.animatedOpacity1, {
56
- toValue: OPACITY_END1,
57
- duration: 250,
58
- useNativeDriver: false,
59
- }),
60
- Animated.timing(this.state.animatedLeft4, {
61
- toValue: LEFT_END1,
62
- duration: 250,
63
- useNativeDriver: false,
64
- }),
65
- ]),
66
- Animated.timing(this.state.animatedOpacity4, {
67
- toValue: OPACITY_END2,
68
- duration: 250,
69
- useNativeDriver: false,
70
- }),
71
- Animated.parallel([
72
- Animated.timing(this.state.animatedOpacity2, {
73
- toValue: OPACITY_END2,
74
- duration: 250,
75
- useNativeDriver: false,
76
- }),
77
- Animated.timing(this.state.animatedLeft4, {
78
- toValue: LEFT_END2,
79
- duration: 250,
80
- useNativeDriver: false,
81
- }),
82
- ]),
83
- Animated.parallel([
84
- Animated.timing(this.state.animatedOpacity4, {
85
- toValue: OPACITY_END3,
86
- duration: 250,
87
- useNativeDriver: false,
88
- }),
89
- Animated.timing(this.state.animatedOpacity3, {
90
- toValue: OPACITY_END3,
91
- duration: 250,
92
- useNativeDriver: false,
93
- }),
94
- ]),
95
- Animated.parallel([
96
- Animated.timing(this.state.animatedLeft4, {
97
- toValue: LEFT_START,
98
- duration: 1,
99
- useNativeDriver: false,
100
- }),
101
- Animated.timing(this.state.animatedOpacity4, {
102
- toValue: 0,
103
- duration: 1,
104
- useNativeDriver: false,
105
- }),
106
- ]),
107
- Animated.timing(this.state.animatedOpacity3, {
108
- toValue: OPACITY_START,
109
- duration: 250,
110
- useNativeDriver: false,
111
- }),
112
- Animated.timing(this.state.animatedOpacity2, {
113
- toValue: OPACITY_START,
114
- duration: 250,
115
- useNativeDriver: false,
116
- }),
117
- Animated.timing(this.state.animatedOpacity1, {
118
- toValue: OPACITY_START,
119
- duration: 250,
44
+ Animated.delay(500),
45
+ Animated.timing(this.state.animatedLeft, {
46
+ toValue: -DEFAULT_WIDTH,
47
+ duration: 1500,
120
48
  useNativeDriver: false,
121
49
  }),
50
+ Animated.delay(500),
122
51
  ]).start(() => {
123
52
  this.animate();
124
53
  });
125
54
  };
126
55
 
127
56
  render() {
128
- const { animatedOpacity1, animatedOpacity2, animatedOpacity3, animatedOpacity4, animatedLeft4, animatedHeight } = this.state;
129
-
130
57
  return (
131
- <Animated.View style={[styles.container, this.props.style, { height: animatedHeight }]} onLayout={this.onLayout}>
132
- <View style={styles.innerContainer}>
133
- <Animated.View style={[styles.circle, { opacity: animatedOpacity1 }]} />
134
- <Animated.View style={[styles.circle, { opacity: animatedOpacity2 }]} />
135
- <Animated.View style={[styles.circle, { opacity: animatedOpacity3 }]} />
136
- <Animated.View style={[styles.circle, { position: 'absolute', left: animatedLeft4, opacity: animatedOpacity4 }]} />
137
- </View>
58
+ <Animated.View
59
+ style={[styles.container, this.props.style, { height: DEFAULT_HEIGHT, opacity: this.state.animatedOpacity }]}
60
+ onLayout={this.onLayout}
61
+ >
62
+ <Animated.View
63
+ style={[styles.line, { backgroundColor: this.props.colourBrandingMain, left: this.state.animatedLeft, height: DEFAULT_HEIGHT }]}
64
+ />
138
65
  </Animated.View>
139
66
  );
140
67
  }
@@ -145,20 +72,23 @@ const styles = StyleSheet.create({
145
72
  alignItems: 'center',
146
73
  justifyContent: 'center',
147
74
  overflow: 'hidden',
148
- backgroundColor: '#ebeff2',
75
+ backgroundColor: '#fff',
149
76
  width: '100%',
150
77
  height: 38,
151
78
  },
152
79
  innerContainer: {
153
80
  flexDirection: 'row',
154
81
  },
155
- circle: {
156
- width: CIRCLE_SIZE,
157
- height: CIRCLE_SIZE,
158
- borderRadius: CIRCLE_SIZE / 2,
159
- backgroundColor: TEXT_BLUEGREY,
160
- marginHorizontal: 4,
82
+ line: {
83
+ width: DEFAULT_WIDTH,
161
84
  },
162
85
  });
163
86
 
164
- export default LoadingIndicator;
87
+ const mapStateToProps = state => {
88
+ return {
89
+ colourBrandingMain: getMainBrandingColourFromState(state),
90
+ };
91
+ };
92
+
93
+ export default connect(mapStateToProps, {})(LoadingIndicator);
94
+ // export { loadingIndicator as Default };
@@ -0,0 +1,250 @@
1
+ import React, { Component } from 'react';
2
+ import { View, StyleSheet, Dimensions } from 'react-native';
3
+ import YoutubePlayer, { getYoutubeMeta } from 'react-native-youtube-iframe';
4
+ // import { Vimeo } from 'react-native-vimeo-iframe';
5
+ import WebView from 'react-native-webview';
6
+ import { Video } from 'expo-av';
7
+ import { Spinner } from '../../../../src/components/common';
8
+
9
+ const SCREEN_HEIGHT = Dimensions.get('window').height;
10
+ const SCREEN_WIDTH = Dimensions.get('window').width;
11
+ const EXPO_VIDEO_PROPS = { rate: 1, isMuted: false, volume: 1, shouldPlay: true };
12
+ const RETRY_RECOVER = 30000;
13
+ const RETRY_LOADING = 10000;
14
+
15
+ class MediaPlayer extends Component {
16
+ constructor(props) {
17
+ super(props);
18
+ this.state = {
19
+ isUrlLink: true,
20
+ isYoutube: false,
21
+ forceWebview: false,
22
+ heightFactor: 0.565,
23
+ playbackLoaded: false,
24
+ playbackBuffering: false,
25
+ playbackPlaying: false,
26
+ };
27
+ this.youtubePlayer = null;
28
+ this.videoPlayer = null;
29
+ this.checkStreamLoaded = null;
30
+ this.retryLoading = false;
31
+ }
32
+
33
+ componentDidMount() {
34
+ this.setupForLiveStreaming();
35
+ }
36
+
37
+ componentWillUnmount() {
38
+ if (this.checkStreamLoaded) clearInterval(this.checkStreamLoaded);
39
+ }
40
+
41
+ getYoutubeVideoId = url => {
42
+ url = url.replace(/(>|<)/gi, '').split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
43
+ if (url[2] !== undefined) {
44
+ const ids = url[2].split(/[^0-9a-z_\-]/i);
45
+ return ids[0];
46
+ } else {
47
+ return '';
48
+ }
49
+ };
50
+
51
+ setupForLiveStreaming = async () => {
52
+ // Extract live stream info
53
+ const { streamUrl } = this.props.streamInfo;
54
+ const isUrlLink = streamUrl.startsWith('http');
55
+ const isYoutube = streamUrl.includes('youtube.com/watch?v=') || streamUrl.includes('youtu.be/');
56
+ this.setState({ isUrlLink, isYoutube });
57
+
58
+ // if (isYoutube) {
59
+ // const metadata = await getYoutubeMeta(this.getYoutubeVideoId(streamUrl));
60
+ // this.setState({ heightFactor: metadata.height / metadata.width });
61
+ // console.log('youtube metadata', metadata);
62
+ // }
63
+ };
64
+
65
+ getStreamPlaybackUrl = () => {
66
+ // return 'https://stream.mux.com/nflNhyFQMlrSt00II01Hjh2BLSW009PH1tl023pdpJ01s1vc.m3u8';
67
+ return `https://stream.mux.com/${this.props.streamInfo.playbackId}.m3u8`;
68
+ };
69
+
70
+ checkReloadStream = (wait = RETRY_LOADING, force = false) => {
71
+ if (!force && this.retryLoading) return;
72
+
73
+ console.log(`Check loading in ${wait} milliseconds...`);
74
+ this.retryLoading = true;
75
+ setTimeout(async () => {
76
+ const { playbackLoaded, playbackBuffering } = this.state;
77
+ try {
78
+ if (playbackLoaded === false || playbackBuffering === true) {
79
+ console.log('Reloading started');
80
+ await this.videoPlayer.loadAsync(
81
+ { uri: this.getStreamPlaybackUrl(), overrideFileExtensionAndroid: 'm3u8' },
82
+ EXPO_VIDEO_PROPS,
83
+ false,
84
+ );
85
+ } else {
86
+ console.log('Already loaded - not reloading');
87
+ }
88
+ this.retryLoading = false;
89
+ } catch {}
90
+ }, wait);
91
+ };
92
+
93
+ onStreamLoadStart = () => {
94
+ console.log('Stream load started');
95
+ this.checkStreamLoaded = setInterval(() => {
96
+ const { playbackLoaded } = this.state;
97
+ if (playbackLoaded === false) {
98
+ console.log('Stream loading failed unexpectedly');
99
+ this.checkReloadStream(1000, true);
100
+ } else {
101
+ clearInterval(this.checkStreamLoaded);
102
+ }
103
+ }, RETRY_RECOVER);
104
+ };
105
+
106
+ onStreamLoaded = status => {
107
+ console.log(`Stream loaded - isLoaded:${status.isLoaded}, isBuffering:${status.isBuffering}, isPlaying:${status.isPlaying}`);
108
+ };
109
+
110
+ onStreamError = error => {
111
+ console.log('Stream error', error);
112
+ };
113
+
114
+ onStreamStatusUpdate = status => {
115
+ this.setState(
116
+ {
117
+ playbackLoaded: status.isLoaded,
118
+ playbackBuffering: status.isBuffering,
119
+ playbackPlaying: status.isPlaying,
120
+ },
121
+ () => {
122
+ const { playbackLoaded, playbackBuffering, playbackPlaying, shouldPlay } = this.state;
123
+ // console.log(`Status updated - isLoaded:${playbackLoaded}, isBuffering:${playbackBuffering}, isPlaying:${playbackPlaying}`);
124
+ if (playbackLoaded === false && playbackBuffering === undefined && playbackPlaying === undefined) {
125
+ this.checkReloadStream();
126
+ } else if (playbackLoaded === true && playbackBuffering === true) {
127
+ this.checkReloadStream(RETRY_RECOVER);
128
+ }
129
+ },
130
+ );
131
+ };
132
+
133
+ onYoutubeError = e => {
134
+ // console.log('onYoutubeError', e);
135
+ this.setState({ forceWebview: true });
136
+ };
137
+
138
+ onYoutubeReady = () => {
139
+ // console.log('onYoutubeReady');
140
+ this.setState({ playbackLoaded: true });
141
+ };
142
+
143
+ getYoutubePlayer = (youtubeId, width, height) => (
144
+ <YoutubePlayer
145
+ ref={ref => (this.youtubePlayer = ref)}
146
+ height={height}
147
+ width={width}
148
+ videoId={youtubeId}
149
+ play
150
+ // onChangeState={event => console.log('onChangeState', event)}
151
+ onReady={this.onYoutubeReady}
152
+ onError={this.onYoutubeError}
153
+ // onPlaybackQualityChange={q => console.log('onPlaybackQualityChange', q)}
154
+ volume={50}
155
+ playbackRate={1}
156
+ playerParams={{
157
+ cc_lang_pref: 'us',
158
+ showClosedCaptions: true,
159
+ }}
160
+ />
161
+ );
162
+
163
+ getWebviewPlayer = embedUrl => (
164
+ <WebView
165
+ startInLoadingState
166
+ javaScriptEnabled
167
+ scrollEnabled={false}
168
+ automaticallyAdjustContentInsets={false}
169
+ mediaPlaybackRequiresUserAction
170
+ style={styles.webView}
171
+ source={{ uri: embedUrl }}
172
+ />
173
+ );
174
+
175
+ getVideoPlayer = (embedUrl, width, height) => (
176
+ <Video
177
+ ref={vp => {
178
+ this.videoPlayer = vp;
179
+ }}
180
+ source={{ uri: embedUrl }}
181
+ {...EXPO_VIDEO_PROPS}
182
+ resizeMode={Video.RESIZE_MODE_CONTAIN}
183
+ useNativeControls
184
+ style={{ width, height }}
185
+ onLoadStart={this.onStreamLoadStart}
186
+ onLoad={this.onStreamLoaded}
187
+ onError={this.onStreamError}
188
+ onPlaybackStatusUpdate={this.onStreamStatusUpdate}
189
+ />
190
+ );
191
+
192
+ render() {
193
+ let width, height;
194
+ const { landscape, streamInfo } = this.props;
195
+ if (landscape) {
196
+ width = SCREEN_HEIGHT;
197
+ height = SCREEN_WIDTH;
198
+ } else {
199
+ width = SCREEN_WIDTH;
200
+ height = SCREEN_WIDTH * this.state.heightFactor;
201
+ }
202
+
203
+ const { isUrlLink, isYoutube, forceWebview, playbackLoaded, playbackBuffering } = this.state;
204
+ const { playbackId, streamUrl } = streamInfo;
205
+ let embedUrl, player;
206
+ if (isYoutube && !forceWebview) {
207
+ const youtubeId = this.getYoutubeVideoId(streamUrl);
208
+ embedUrl = youtubeId ? `https://www.youtube.com/embed/${youtubeId}` : streamUrl;
209
+ player = this.getYoutubePlayer(youtubeId, width, height);
210
+ } else if (isUrlLink) {
211
+ embedUrl = streamUrl;
212
+ player = this.getWebviewPlayer(embedUrl);
213
+ } else if (playbackId) {
214
+ embedUrl = this.getStreamPlaybackUrl();
215
+ player = this.getVideoPlayer(embedUrl, width, height);
216
+ }
217
+
218
+ // console.log('Streaming:', embedUrl, 'landscape', landscape, 'playbackLoaded', playbackLoaded, 'playbackBuffering', playbackBuffering);
219
+ return (
220
+ <View style={[styles.container, !isUrlLink && { alignItems: 'center' }, { width, height }]}>
221
+ {player}
222
+ {(!playbackLoaded || playbackBuffering) && (
223
+ <View style={styles.loadingContainer}>
224
+ <Spinner color={'#fff'} />
225
+ </View>
226
+ )}
227
+ </View>
228
+ );
229
+ }
230
+ }
231
+
232
+ const styles = StyleSheet.create({
233
+ container: {
234
+ backgroundColor: '#000',
235
+ },
236
+ webView: {
237
+ flex: 1,
238
+ },
239
+ loadingContainer: {
240
+ position: 'absolute',
241
+ top: 50,
242
+ left: 0,
243
+ right: 0,
244
+ bottom: 50,
245
+ justifyContent: 'center',
246
+ alignItems: 'center',
247
+ },
248
+ });
249
+
250
+ export default MediaPlayer;
@@ -9,6 +9,8 @@ import { getBottomSpace } from 'react-native-iphone-x-helper';
9
9
  import { Spinner } from './Spinner';
10
10
  import { ProfilePic } from './ProfilePic';
11
11
  import PlussChatTime from './PlussChatTime';
12
+ import { PDFPopup } from './PDFPopup';
13
+ import { Attachment } from './Attachment';
12
14
  import PlussChatMessage from './PlussChatMessage';
13
15
  import {
14
16
  TEXT_DARK,
@@ -19,7 +21,7 @@ import {
19
21
  BG_GREY,
20
22
  TEXT_BLUEGREY,
21
23
  } from '../colours';
22
- import { getEnabledTabsFromState, get1400, getThumb300, imageExists, isVideo, getImageSource } from '../helper';
24
+ import { getEnabledTabsFromState, get1400, getThumb300, imageExists, isVideo, getImageSource, getFileName } from '../helper';
23
25
  import Config, { Services } from '../config';
24
26
  import ImageUploader from './ImageUploader';
25
27
  import ImageUploadProgress from './ImageUploadProgress';
@@ -143,6 +145,18 @@ class PlussChat extends Component {
143
145
  }, 2000);
144
146
  };
145
147
 
148
+ onOpenAttachment = a => {
149
+ this.setState({
150
+ selectedPDF: a,
151
+ });
152
+ };
153
+
154
+ onCloseAttachment = () => {
155
+ this.setState({
156
+ selectedPDF: null,
157
+ });
158
+ };
159
+
146
160
  showUploadMenu() {
147
161
  this.imageUploader.getWrappedInstance().showUploadMenu();
148
162
  }
@@ -279,6 +293,11 @@ class PlussChat extends Component {
279
293
  wrapperStyle.paddingBottom = 16;
280
294
  wrapperStyle.minWidth = 142; // 110 image width + 16 padding either side
281
295
  }
296
+ if (!_.isEmpty(bubbleProps.currentMessage.attachments)) {
297
+ wrapperStyle.paddingTop = 8;
298
+ wrapperStyle.paddingBottom = 16;
299
+ wrapperStyle.paddingHorizontal = 16;
300
+ }
282
301
  return (
283
302
  <Bubble
284
303
  {...bubbleProps}
@@ -401,6 +420,25 @@ class PlussChat extends Component {
401
420
  </View>
402
421
  );
403
422
  }
423
+ if (!_.isEmpty(currentMessage.attachments)) {
424
+ console.log('rendering attachments');
425
+ console.log(currentMessage.attachments);
426
+ return (
427
+ <View>
428
+ {currentMessage.attachments.map((url, i) => {
429
+ return (
430
+ <Attachment
431
+ onPress={() => {
432
+ this.onOpenAttachment(url);
433
+ }}
434
+ key={i}
435
+ title={getFileName(url)}
436
+ />
437
+ );
438
+ })}
439
+ </View>
440
+ );
441
+ }
404
442
  if (currentMessage.event) {
405
443
  return (
406
444
  <TouchableOpacity onPress={this.onPressEvent.bind(this, currentMessage.event)}>
@@ -680,6 +718,15 @@ class PlussChat extends Component {
680
718
  );
681
719
  }
682
720
 
721
+ renderPDF() {
722
+ if (_.isEmpty(this.state.selectedPDF)) {
723
+ return null;
724
+ }
725
+ return (
726
+ <PDFPopup source={this.state.selectedPDF} onClose={this.onCloseAttachment} title={getFileName(this.state.selectedPDF)} pdfCount={1} />
727
+ );
728
+ }
729
+
683
730
  render() {
684
731
  if (Platform.OS === 'android' && !this.props.noAndroidAvoid) {
685
732
  return (
@@ -688,6 +735,7 @@ class PlussChat extends Component {
688
735
  {this.renderImageUploader()}
689
736
  {this.renderImagePopup()}
690
737
  {this.renderVideoPlayerPopup()}
738
+ {this.renderPDF()}
691
739
  </KeyboardAvoidingView>
692
740
  );
693
741
  }
@@ -697,6 +745,7 @@ class PlussChat extends Component {
697
745
  {this.renderImageUploader()}
698
746
  {this.renderImagePopup()}
699
747
  {this.renderVideoPlayerPopup()}
748
+ {this.renderPDF()}
700
749
  </View>
701
750
  );
702
751
  }
@@ -47,3 +47,4 @@ export { default as UserListing } from './UserListing';
47
47
  export { default as PlussChat } from './PlussChat';
48
48
  export { default as PositionedImage } from './PositionedImage';
49
49
  export { default as FormattedText } from './FormattedText';
50
+ export { default as MediaPlayer } from './MediaPlayer';
package/src/helper.js CHANGED
@@ -74,11 +74,37 @@ const getExtension = url => {
74
74
  return fileSplit[fileSplit.length - 1].toLowerCase();
75
75
  };
76
76
 
77
+ const getFileName = (url, noExtension) => {
78
+ if (!url) {
79
+ return null;
80
+ }
81
+ const fileSplit = url.split('/');
82
+ const name = fileSplit[fileSplit.length - 1].toLowerCase();
83
+ if (!noExtension) {
84
+ return name;
85
+ }
86
+ return name.split('.')[0];
87
+ };
88
+
77
89
  const isVideo = url => {
78
90
  const extension = getExtension(url);
79
91
  return ['mov', 'mp4'].includes(extension);
80
92
  };
81
93
 
94
+ const isVideoUrl = url => {
95
+ const urlToCheck = getValidUrl(url);
96
+ if (isVideo(urlToCheck)) {
97
+ return true;
98
+ }
99
+ if (isYoutube(urlToCheck)) {
100
+ return true;
101
+ }
102
+ if (isVimeo(urlToCheck)) {
103
+ return true;
104
+ }
105
+ return false;
106
+ };
107
+
82
108
  const isStockImage = url => {
83
109
  return url.indexOf('https://pluss60-dev-uploads.s3.ap-southeast-2.amazonaws.com/uploads/library') !== -1;
84
110
  };
@@ -288,6 +314,29 @@ const isTablet = () => {
288
314
  return Dimensions.get('screen').width > 600;
289
315
  };
290
316
 
317
+ const isVimeo = url => {
318
+ if (_.isEmpty(url)) {
319
+ return false;
320
+ }
321
+ if (url.indexOf('https://vimeo.com/') === 0) {
322
+ return true;
323
+ }
324
+ if (url.indexOf('https://player.vimeo.com/video/') === 0) {
325
+ return true;
326
+ }
327
+ };
328
+
329
+ const getVimeoEmbed = url => {
330
+ if (_.isEmpty(url)) {
331
+ return '';
332
+ }
333
+ if (url.indexOf('https://vimeo.com/') === 0) {
334
+ const vimeoId = 'lol'; //TODO
335
+ return `https://player.vimeo.com/video/${vimeoId}`;
336
+ }
337
+ return url; //TODO
338
+ };
339
+
291
340
  const isYoutube = url => {
292
341
  if (_.isUndefined(url) || _.isEmpty(url)) {
293
342
  return false;
@@ -349,6 +398,7 @@ export {
349
398
  getSiteSettingFromState,
350
399
  getValueOrDefault,
351
400
  getExtension,
401
+ getFileName,
352
402
  isVideo,
353
403
  getThumb300,
354
404
  get1400,
@@ -373,4 +423,5 @@ export {
373
423
  getValidUrl,
374
424
  getUserPreview,
375
425
  allowComments,
426
+ isVideoUrl,
376
427
  };