@plusscommunities/pluss-core-app 7.0.1-beta.1 → 7.0.2-auth.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/dist/module/apis/fileActions.js +21 -28
- package/dist/module/apis/fileActions.js.map +1 -1
- package/dist/module/components/DocumentUploader.js +237 -0
- package/dist/module/components/DocumentUploader.js.map +1 -0
- package/dist/module/components/ImagePopup.js +42 -20
- package/dist/module/components/ImagePopup.js.map +1 -1
- package/dist/module/components/ImageUploader.js +50 -25
- package/dist/module/components/ImageUploader.js.map +1 -1
- package/dist/module/components/MediaPlayer.js +2 -2
- package/dist/module/components/MediaPlayer.js.map +1 -1
- package/dist/module/components/PlussChat.js +80 -6
- package/dist/module/components/PlussChat.js.map +1 -1
- package/dist/module/components/PlussChatMessage.js +41 -4
- package/dist/module/components/PlussChatMessage.js.map +1 -1
- package/dist/module/components/index.js +1 -0
- package/dist/module/components/index.js.map +1 -1
- package/dist/module/components/react-native-expo-image-cropper/ExpoImageManipulator.js +2 -1
- package/dist/module/components/react-native-expo-image-cropper/ExpoImageManipulator.js.map +1 -1
- package/dist/module/config.js +6 -1
- package/dist/module/config.js.map +1 -1
- package/dist/module/session.js +2 -2
- package/dist/module/session.js.map +1 -1
- package/package.json +4 -1
- package/src/apis/fileActions.js +19 -27
- package/src/components/DocumentUploader.js +207 -0
- package/src/components/ImagePopup.js +41 -26
- package/src/components/ImageUploader.js +66 -28
- package/src/components/MediaPlayer.js +2 -2
- package/src/components/PlussChat.js +88 -2
- package/src/components/PlussChatMessage.js +40 -3
- package/src/components/index.js +1 -0
- package/src/components/react-native-expo-image-cropper/ExpoImageManipulator.js +1 -1
- package/src/config.js +5 -0
- package/src/session.js +2 -2
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
|
+
import { View, TouchableOpacity, Text, StyleSheet, ActivityIndicator } from 'react-native';
|
|
3
|
+
import { Icon } from '@rneui/themed';
|
|
4
|
+
// import * as DocumentPicker from 'expo-document-picker';
|
|
5
|
+
import { connect } from 'react-redux';
|
|
6
|
+
import Config from '../config';
|
|
7
|
+
import { fileActions } from '../apis';
|
|
8
|
+
import { getValueOrDefault } from '../helper';
|
|
9
|
+
import { TEXT_DARK, getMainBrandingColourFromState } from '../colours';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_DOCUMENT_NAME = 'document';
|
|
12
|
+
|
|
13
|
+
class DocumentUploader extends Component {
|
|
14
|
+
static defaultProps = {
|
|
15
|
+
allowedTypes: ['application/pdf'],
|
|
16
|
+
buttonTitle: 'Upload Document',
|
|
17
|
+
buttonStyle: {},
|
|
18
|
+
buttonTextStyle: {},
|
|
19
|
+
onUploadStarted: () => {},
|
|
20
|
+
onUploadSuccess: () => {},
|
|
21
|
+
onUploadFailed: () => {},
|
|
22
|
+
onUploadProgress: null,
|
|
23
|
+
userId: null,
|
|
24
|
+
fileName: null,
|
|
25
|
+
disabled: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
state = {
|
|
29
|
+
isUploading: false,
|
|
30
|
+
uploadProgress: 0,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
pickDocument = async () => {
|
|
34
|
+
const { allowedTypes, multiple } = this.props;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const result = {};
|
|
38
|
+
// const result = await DocumentPicker.getDocumentAsync({
|
|
39
|
+
// type: allowedTypes,
|
|
40
|
+
// copyToCacheDirectory: true,
|
|
41
|
+
// multiple: multiple || false,
|
|
42
|
+
// });
|
|
43
|
+
// console.log('pickDocument', JSON.stringify(result, null, 2));
|
|
44
|
+
|
|
45
|
+
if (!result.canceled) {
|
|
46
|
+
await this.handleDocumentPicked(result);
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.log('Document picker error:', error);
|
|
50
|
+
this.props.onUploadFailed(null, error.message);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
handleDocumentPicked = async documentResult => {
|
|
55
|
+
const { assets } = documentResult;
|
|
56
|
+
const { userId } = this.props;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
this.setState({ isUploading: true, uploadProgress: 0 });
|
|
60
|
+
|
|
61
|
+
// Process each asset in parallel
|
|
62
|
+
const uploadPromises = assets.map(async asset => {
|
|
63
|
+
const { name, uri } = asset;
|
|
64
|
+
|
|
65
|
+
// Generate a unique filename if not provided
|
|
66
|
+
const [file, fileExt] = name.split('.');
|
|
67
|
+
const fileName = `${getValueOrDefault(this.props.fileName, DEFAULT_DOCUMENT_NAME)}_${Date.now()}.${fileExt}`;
|
|
68
|
+
const uploadUri = fileActions.getUploadUrl(userId, fileName);
|
|
69
|
+
// console.log('handleDocumentPicked', JSON.stringify({ uploadUri, uri, name, fileName }, null, 2));
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// Notify parent component that upload has started for this file
|
|
73
|
+
this.props.onUploadStarted(uploadUri, uri, file, fileExt.toUpperCase());
|
|
74
|
+
|
|
75
|
+
const res = await fileActions.uploadUserMediaWithProgress(uri, uploadUri, progress => {
|
|
76
|
+
if (this.props.onUploadProgress) this.props.onUploadProgress(progress);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const fileUrl = Config.env.baseUploadsUrl + res.key;
|
|
80
|
+
this.props.onUploadSuccess(fileUrl, uploadUri);
|
|
81
|
+
console.log('Upload success', fileUrl);
|
|
82
|
+
return { success: true, url: fileUrl, uploadUri };
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error(`Upload failed for ${name}:`, error);
|
|
85
|
+
this.props.onUploadFailed(uploadUri, error.message);
|
|
86
|
+
return { success: false, error, uploadUri };
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Wait for all uploads to complete
|
|
91
|
+
const results = await Promise.all(uploadPromises);
|
|
92
|
+
return results;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('Document upload error:', error);
|
|
95
|
+
return [];
|
|
96
|
+
} finally {
|
|
97
|
+
this.setState({ isUploading: false, uploadProgress: 0 });
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
renderUploadButton = () => {
|
|
102
|
+
const { buttonTitle, buttonStyle, buttonTextStyle, disabled } = this.props;
|
|
103
|
+
const { isUploading } = this.state;
|
|
104
|
+
const mainColor = getMainBrandingColourFromState(this.props);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<TouchableOpacity
|
|
108
|
+
style={[styles.uploadButton, { borderColor: mainColor }, buttonStyle]}
|
|
109
|
+
onPress={this.pickDocument}
|
|
110
|
+
disabled={isUploading || disabled}
|
|
111
|
+
activeOpacity={0.7}
|
|
112
|
+
>
|
|
113
|
+
<View style={styles.buttonContent}>
|
|
114
|
+
{isUploading ? (
|
|
115
|
+
<ActivityIndicator color={mainColor} />
|
|
116
|
+
) : (
|
|
117
|
+
<Icon name="attachment" type="entypo" color={mainColor} size={18} style={styles.icon} />
|
|
118
|
+
)}
|
|
119
|
+
<Text style={[styles.buttonText, { color: mainColor }, buttonTextStyle]}>{buttonTitle}</Text>
|
|
120
|
+
</View>
|
|
121
|
+
</TouchableOpacity>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
renderProgress = () => {
|
|
126
|
+
const { uploadProgress } = this.state;
|
|
127
|
+
if (uploadProgress <= 0 || uploadProgress >= 1) return null;
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<View style={styles.progressContainer}>
|
|
131
|
+
<View style={styles.progressBar}>
|
|
132
|
+
<View style={[styles.progressFill, { width: `${uploadProgress * 100}%` }]} />
|
|
133
|
+
</View>
|
|
134
|
+
<Text style={styles.progressText}>{Math.round(uploadProgress * 100)}%</Text>
|
|
135
|
+
</View>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
render() {
|
|
140
|
+
return (
|
|
141
|
+
<View style={styles.container}>
|
|
142
|
+
{this.renderUploadButton()}
|
|
143
|
+
{this.renderProgress()}
|
|
144
|
+
</View>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const styles = StyleSheet.create({
|
|
150
|
+
container: {
|
|
151
|
+
marginVertical: 10,
|
|
152
|
+
},
|
|
153
|
+
uploadButton: {
|
|
154
|
+
flexDirection: 'row',
|
|
155
|
+
alignItems: 'center',
|
|
156
|
+
justifyContent: 'center',
|
|
157
|
+
paddingVertical: 8,
|
|
158
|
+
paddingHorizontal: 16,
|
|
159
|
+
borderRadius: 6,
|
|
160
|
+
backgroundColor: '#fff',
|
|
161
|
+
borderWidth: 1,
|
|
162
|
+
borderColor: '#007AFF',
|
|
163
|
+
},
|
|
164
|
+
buttonContent: {
|
|
165
|
+
flexDirection: 'row',
|
|
166
|
+
alignItems: 'center',
|
|
167
|
+
},
|
|
168
|
+
buttonText: {
|
|
169
|
+
color: '#fff',
|
|
170
|
+
fontSize: 16,
|
|
171
|
+
fontWeight: '500',
|
|
172
|
+
marginLeft: 8,
|
|
173
|
+
},
|
|
174
|
+
icon: {
|
|
175
|
+
marginRight: 8,
|
|
176
|
+
},
|
|
177
|
+
progressContainer: {
|
|
178
|
+
marginTop: 8,
|
|
179
|
+
alignItems: 'center',
|
|
180
|
+
},
|
|
181
|
+
progressBar: {
|
|
182
|
+
height: 4,
|
|
183
|
+
width: '100%',
|
|
184
|
+
backgroundColor: '#E0E0E0',
|
|
185
|
+
borderRadius: 2,
|
|
186
|
+
overflow: 'hidden',
|
|
187
|
+
},
|
|
188
|
+
progressFill: {
|
|
189
|
+
height: '100%',
|
|
190
|
+
backgroundColor: '#4CAF50',
|
|
191
|
+
},
|
|
192
|
+
progressText: {
|
|
193
|
+
marginTop: 4,
|
|
194
|
+
fontSize: 12,
|
|
195
|
+
color: TEXT_DARK,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const mapStateToProps = state => {
|
|
200
|
+
const { user } = state;
|
|
201
|
+
return {
|
|
202
|
+
user,
|
|
203
|
+
colourBrandingMain: getMainBrandingColourFromState(state),
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export default connect(mapStateToProps)(DocumentUploader);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, { Component } from 'react';
|
|
2
2
|
import _ from 'lodash';
|
|
3
3
|
import moment from 'moment';
|
|
4
|
-
import { Dimensions, Modal, TouchableOpacity, StyleSheet, View,
|
|
4
|
+
import { Dimensions, Modal, TouchableOpacity, StyleSheet, View, Text } from 'react-native';
|
|
5
5
|
import ImageViewer from 'react-native-image-zoom-viewer';
|
|
6
6
|
import { Icon } from '@rneui/themed';
|
|
7
|
+
import AutoHeightImage from 'react-native-auto-height-image';
|
|
7
8
|
import { TEXT_DARK } from '../colours';
|
|
8
9
|
import { StatusBarHeight, isVideo, get1400 } from '../helper';
|
|
9
10
|
import { Pl60Icon } from '../fonts';
|
|
@@ -71,9 +72,8 @@ class ImagePopup extends Component {
|
|
|
71
72
|
);
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const isVideo = !_.isNil(media) && media.isVideo;
|
|
75
|
+
renderImageFooter = (media, style) => {
|
|
76
|
+
if (!media.user || !media.date) return null;
|
|
77
77
|
|
|
78
78
|
let dateText, timeText;
|
|
79
79
|
if (!_.isNil(media.date)) {
|
|
@@ -83,25 +83,31 @@ class ImagePopup extends Component {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
return (
|
|
86
|
-
<View>
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
86
|
+
<View style={[styles.imageInfoContainer, style]}>
|
|
87
|
+
<ProfilePic Diameter={42} ProfilePic={media?.user?.profilePic} />
|
|
88
|
+
<View style={styles.imageTextContainer}>
|
|
89
|
+
<Text style={styles.imageTextName}>{media?.user?.displayName}</Text>
|
|
90
|
+
<Text numberOfLines={2} style={styles.iamgeTextDate}>{`Uploaded ${dateText} • ${timeText}`}</Text>
|
|
91
|
+
</View>
|
|
92
|
+
</View>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
renderImage = props => {
|
|
97
|
+
const media = this.state.images.find(image => image.url === props.source.uri);
|
|
98
|
+
const isVideo = !_.isNil(media) && media.isVideo;
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<View style={styles.imageContainer}>
|
|
102
|
+
<AutoHeightImage source={{ uri: media.url }} width={SCREEN_WIDTH} resizeMode="contain" />
|
|
103
|
+
{isVideo && (
|
|
104
|
+
<View style={styles.videoOverlay}>
|
|
105
|
+
<TouchableOpacity onPress={this.toggleFullscreenVideo.bind(this, media.original)}>
|
|
106
|
+
<Icon name="play" type="font-awesome" iconStyle={styles.videoPlayIcon} />
|
|
107
|
+
</TouchableOpacity>
|
|
103
108
|
</View>
|
|
104
109
|
)}
|
|
110
|
+
{this.renderImageFooter(media)}
|
|
105
111
|
</View>
|
|
106
112
|
);
|
|
107
113
|
};
|
|
@@ -120,6 +126,15 @@ class ImagePopup extends Component {
|
|
|
120
126
|
if (_.isEmpty(images)) {
|
|
121
127
|
return null;
|
|
122
128
|
}
|
|
129
|
+
|
|
130
|
+
// This is required for the ImageViewer to render the image info section properly
|
|
131
|
+
const imagesWithSizes = images.map(image => {
|
|
132
|
+
return {
|
|
133
|
+
...image,
|
|
134
|
+
width: SCREEN_WIDTH,
|
|
135
|
+
height: SCREEN_HEIGHT,
|
|
136
|
+
};
|
|
137
|
+
});
|
|
123
138
|
return (
|
|
124
139
|
<Modal visible={visible} animationType="slide" onRequestClose={onClose} style={styles.modal}>
|
|
125
140
|
<ImageViewer
|
|
@@ -128,7 +143,7 @@ class ImagePopup extends Component {
|
|
|
128
143
|
onChange={this.onChange}
|
|
129
144
|
onSwipeDown={onClose}
|
|
130
145
|
enableSwipeDown
|
|
131
|
-
imageUrls={
|
|
146
|
+
imageUrls={imagesWithSizes}
|
|
132
147
|
saveToLocalByLongPress={false}
|
|
133
148
|
renderImage={this.renderImage}
|
|
134
149
|
/>
|
|
@@ -149,10 +164,10 @@ const styles = StyleSheet.create({
|
|
|
149
164
|
height: SCREEN_HEIGHT,
|
|
150
165
|
backgroundColor: '#000',
|
|
151
166
|
},
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
167
|
+
imageContainer: {
|
|
168
|
+
flex: 1,
|
|
169
|
+
justifyContent: 'center',
|
|
170
|
+
alignItems: 'center',
|
|
156
171
|
},
|
|
157
172
|
menuIconContainer: {
|
|
158
173
|
position: 'absolute',
|
|
@@ -142,23 +142,25 @@ class ImageUploader extends Component {
|
|
|
142
142
|
let uploadUri;
|
|
143
143
|
try {
|
|
144
144
|
const fileName = `${getValueOrDefault(this.props.fileName, DEFAULT_IMAGE_NAME)}.${DEFULAT_IMAGE_TYPE}`;
|
|
145
|
-
uploadUri = fileActions.getUploadUrl(this.props.userId, fileName);
|
|
146
145
|
|
|
147
|
-
this.props.onUploadStarted(uploadUri, imageUri);
|
|
148
146
|
this.hideUploadMenu();
|
|
149
147
|
|
|
150
148
|
const resized = await this.resizeImageAsync(imageUri);
|
|
149
|
+
const blob = await fileActions.imageToBlob(resized.uri);
|
|
150
|
+
const presignedUriRes = await fileActions.getPresignedUrl(_.last(resized.uri.split('/')), blob.type);
|
|
151
|
+
uploadUri = presignedUriRes.key;
|
|
152
|
+
this.props.onUploadStarted(uploadUri, imageUri);
|
|
151
153
|
if (this.props.onlySelectImage) {
|
|
152
154
|
this.props.onImageSelected(resized, fileName);
|
|
153
155
|
return;
|
|
154
156
|
}
|
|
155
157
|
|
|
156
|
-
const
|
|
158
|
+
const uploadResult = await fileActions.uploadUserMediaWithProgress(blob, presignedUriRes.url, progress => {
|
|
157
159
|
if (this.props.onUploadProgress) this.props.onUploadProgress(progress);
|
|
158
160
|
});
|
|
159
161
|
|
|
160
|
-
this.props.onUploadSuccess(Config.env.baseUploadsUrl +
|
|
161
|
-
console.log('upload success', Config.env.baseUploadsUrl +
|
|
162
|
+
this.props.onUploadSuccess(Config.env.baseUploadsUrl + uploadUri, uploadUri);
|
|
163
|
+
console.log('upload success', Config.env.baseUploadsUrl + uploadUri);
|
|
162
164
|
} catch (e) {
|
|
163
165
|
console.log('handleImagePicked error', e);
|
|
164
166
|
this.props.onUploadFailed(uploadUri);
|
|
@@ -170,7 +172,6 @@ class ImageUploader extends Component {
|
|
|
170
172
|
const imagesUploaded = imagesSelected.map(image => {
|
|
171
173
|
const fileName = `${getValueOrDefault(this.props.fileName, DEFAULT_IMAGE_NAME)}.${DEFULAT_IMAGE_TYPE}`;
|
|
172
174
|
const uploadUri = fileActions.getUploadUrl(this.props.userId, fileName);
|
|
173
|
-
if (!this.props.onlySelectImage) this.props.onUploadStarted(uploadUri, image.uri);
|
|
174
175
|
return { imageUri: image.uri, uploadUri, fileName };
|
|
175
176
|
});
|
|
176
177
|
this.hideUploadMenu();
|
|
@@ -181,16 +182,21 @@ class ImageUploader extends Component {
|
|
|
181
182
|
const { imageUri, uploadUri, fileName } = image;
|
|
182
183
|
try {
|
|
183
184
|
const resized = await this.resizeImageAsync(imageUri);
|
|
185
|
+
const blob = await fileActions.imageToBlob(resized.uri);
|
|
184
186
|
if (this.props.onlySelectImage) {
|
|
185
187
|
this.props.onImageSelected(resized, fileName);
|
|
186
188
|
return;
|
|
187
189
|
}
|
|
190
|
+
const presignedUriRes = await fileActions.getPresignedUrl(_.last(resized.uri.split('/')), blob.type);
|
|
191
|
+
const uploadUri = presignedUriRes.key;
|
|
192
|
+
if (!this.props.onlySelectImage) this.props.onUploadStarted(uploadUri, imageUri);
|
|
188
193
|
|
|
189
|
-
const
|
|
194
|
+
const uploadResult = await fileActions.uploadUserMediaWithProgress(blob, presignedUriRes.url, progress => {
|
|
190
195
|
if (this.props.onUploadProgress) this.props.onUploadProgress(progress);
|
|
191
196
|
});
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
|
|
198
|
+
this.props.onUploadSuccess(Config.env.baseUploadsUrl + uploadUri, uploadUri);
|
|
199
|
+
console.log('upload success', Config.env.baseUploadsUrl + uploadUri);
|
|
194
200
|
} catch (e) {
|
|
195
201
|
console.log('handleMultiImagePicked error', e);
|
|
196
202
|
this.props.onUploadFailed(uploadUri);
|
|
@@ -203,17 +209,19 @@ class ImageUploader extends Component {
|
|
|
203
209
|
try {
|
|
204
210
|
const fileType = uri.substring(uri.lastIndexOf('.') + 1);
|
|
205
211
|
const fileName = `${getValueOrDefault(this.props.fileName, DEFAULT_VIDEO_NAME)}.${fileType}`;
|
|
206
|
-
|
|
212
|
+
const blob = await fileActions.imageToBlob(uri);
|
|
213
|
+
const presignedUriRes = await fileActions.getPresignedUrl(fileName, blob.type);
|
|
214
|
+
uploadUri = presignedUriRes.key;
|
|
207
215
|
|
|
208
216
|
this.props.onUploadStarted(uploadUri, uri);
|
|
209
217
|
this.hideUploadMenu();
|
|
210
218
|
|
|
211
|
-
const
|
|
219
|
+
const uploadResult = await fileActions.uploadUserMediaWithProgress(blob, presignedUriRes.url, progress => {
|
|
212
220
|
if (this.props.onUploadProgress) this.props.onUploadProgress(progress);
|
|
213
221
|
});
|
|
214
222
|
|
|
215
|
-
this.props.onUploadSuccess(Config.env.baseUploadsUrl +
|
|
216
|
-
console.log('upload success', Config.env.baseUploadsUrl +
|
|
223
|
+
this.props.onUploadSuccess(Config.env.baseUploadsUrl + uploadUri, uploadUri);
|
|
224
|
+
console.log('upload success', Config.env.baseUploadsUrl + uploadUri);
|
|
217
225
|
} catch (e) {
|
|
218
226
|
console.log('handleVideoPicked error', e);
|
|
219
227
|
this.props.onUploadFailed(uploadUri);
|
|
@@ -266,6 +274,7 @@ class ImageUploader extends Component {
|
|
|
266
274
|
};
|
|
267
275
|
|
|
268
276
|
isEditingEnabled = () => {
|
|
277
|
+
return false;
|
|
269
278
|
return !this.props.multiple && !this.props.allowVideo && getValueOrDefault(this.props.allowsEditing, DEFAULT_ALLOWS_EDITING);
|
|
270
279
|
};
|
|
271
280
|
|
|
@@ -304,13 +313,21 @@ class ImageUploader extends Component {
|
|
|
304
313
|
};
|
|
305
314
|
|
|
306
315
|
openLibrary = async () => {
|
|
307
|
-
this.setState(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
316
|
+
this.setState(
|
|
317
|
+
{
|
|
318
|
+
showUploadMenu: false,
|
|
319
|
+
},
|
|
320
|
+
() => {
|
|
321
|
+
setTimeout(() => {
|
|
322
|
+
this.setState({
|
|
323
|
+
showPhotos: true,
|
|
324
|
+
selected: [],
|
|
325
|
+
showRemote: true,
|
|
326
|
+
selectedAlbumId: '',
|
|
327
|
+
});
|
|
328
|
+
}, 1000);
|
|
329
|
+
},
|
|
330
|
+
);
|
|
314
331
|
};
|
|
315
332
|
|
|
316
333
|
openPhotos = async () => {
|
|
@@ -334,13 +351,21 @@ class ImageUploader extends Component {
|
|
|
334
351
|
// iOS behaviour
|
|
335
352
|
if (!(await this.askPermissionsAsync())) return;
|
|
336
353
|
|
|
337
|
-
this.setState(
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
354
|
+
this.setState(
|
|
355
|
+
{
|
|
356
|
+
showUploadMenu: false,
|
|
357
|
+
},
|
|
358
|
+
() => {
|
|
359
|
+
setTimeout(() => {
|
|
360
|
+
this.setState({
|
|
361
|
+
showPhotos: true,
|
|
362
|
+
selected: [],
|
|
363
|
+
showRemote: false,
|
|
364
|
+
selectedAlbumId: '',
|
|
365
|
+
});
|
|
366
|
+
}, 1000);
|
|
367
|
+
},
|
|
368
|
+
);
|
|
344
369
|
};
|
|
345
370
|
|
|
346
371
|
hidePhotos = () => {
|
|
@@ -348,7 +373,19 @@ class ImageUploader extends Component {
|
|
|
348
373
|
};
|
|
349
374
|
|
|
350
375
|
openCropper = () => {
|
|
351
|
-
|
|
376
|
+
// Switching modals - requires a timeout to ensure the modal is closed before opening the next one
|
|
377
|
+
this.setState(
|
|
378
|
+
{
|
|
379
|
+
showPhotos: false,
|
|
380
|
+
},
|
|
381
|
+
() => {
|
|
382
|
+
setTimeout(() => {
|
|
383
|
+
this.setState({
|
|
384
|
+
showCropper: true,
|
|
385
|
+
});
|
|
386
|
+
}, 1000);
|
|
387
|
+
},
|
|
388
|
+
);
|
|
352
389
|
};
|
|
353
390
|
|
|
354
391
|
toggleCropper = () => {
|
|
@@ -390,6 +427,7 @@ class ImageUploader extends Component {
|
|
|
390
427
|
} else {
|
|
391
428
|
if (mediaSelected[0].mediaType === MediaLibrary.MediaType.video) {
|
|
392
429
|
const uri = mediaSelected[0].localUri || mediaSelected[0].uri;
|
|
430
|
+
console.log('picked a video');
|
|
393
431
|
this.handleVideoPicked(uri);
|
|
394
432
|
} else {
|
|
395
433
|
this.handleMultiImagePicked(mediaSelected);
|
|
@@ -3,7 +3,7 @@ import { View, StyleSheet, Dimensions } from 'react-native';
|
|
|
3
3
|
import YoutubePlayer, { getYoutubeMeta } from 'react-native-youtube-iframe';
|
|
4
4
|
import { Vimeo } from 'react-native-vimeo-iframe';
|
|
5
5
|
import { WebView } from 'react-native-webview';
|
|
6
|
-
import { Video } from 'expo-av';
|
|
6
|
+
import { Video, ResizeMode } from 'expo-av';
|
|
7
7
|
import { Spinner } from './Spinner';
|
|
8
8
|
|
|
9
9
|
const SCREEN_HEIGHT = Dimensions.get('window').height;
|
|
@@ -269,7 +269,7 @@ class MediaPlayer extends Component {
|
|
|
269
269
|
source={{ uri: embedUrl }}
|
|
270
270
|
{...EXPO_VIDEO_PROPS}
|
|
271
271
|
shouldPlay={autoPlay}
|
|
272
|
-
resizeMode={
|
|
272
|
+
resizeMode={ResizeMode.CONTAIN}
|
|
273
273
|
useNativeControls
|
|
274
274
|
style={{ width, height }}
|
|
275
275
|
onLoadStart={this.onStreamLoadStart}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { Component } from 'react';
|
|
2
|
-
import { View, Image, ImageBackground, TouchableOpacity, Text, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
|
|
2
|
+
import { View, Image, ImageBackground, TouchableOpacity, Text, KeyboardAvoidingView, Platform, ScrollView, Alert } from 'react-native';
|
|
3
3
|
import { GiftedChat, Bubble, MessageText, Send, InputToolbar, Composer } from 'react-native-gifted-chat';
|
|
4
4
|
import { connect } from 'react-redux';
|
|
5
5
|
import _ from 'lodash';
|
|
@@ -13,6 +13,7 @@ import { PDFPopup } from './PDFPopup';
|
|
|
13
13
|
import { Attachment } from './Attachment';
|
|
14
14
|
import PlussChatMessage from './PlussChatMessage';
|
|
15
15
|
import { TextStyle } from './TextStyle';
|
|
16
|
+
import { ConfirmPopup } from './ConfirmPopup';
|
|
16
17
|
import {
|
|
17
18
|
TEXT_DARK,
|
|
18
19
|
LINEGREY,
|
|
@@ -62,6 +63,8 @@ class PlussChat extends Component {
|
|
|
62
63
|
imagesToUpload: [],
|
|
63
64
|
showFullscreenVideo: false,
|
|
64
65
|
currentVideoUrl: '',
|
|
66
|
+
showDeleteMessageConfirm: false,
|
|
67
|
+
messageToDelete: null,
|
|
65
68
|
};
|
|
66
69
|
this.checkThumb = null;
|
|
67
70
|
}
|
|
@@ -256,6 +259,55 @@ class PlussChat extends Component {
|
|
|
256
259
|
});
|
|
257
260
|
};
|
|
258
261
|
|
|
262
|
+
onDelete = message => {
|
|
263
|
+
// Only proceed if delete handler is provided
|
|
264
|
+
if (!this.props.onDeleteMessage) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Only allow deletion of own messages
|
|
269
|
+
if (message.user._id !== this.props.user.uid) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
this.setState({
|
|
274
|
+
showDeleteMessageConfirm: true,
|
|
275
|
+
messageToDelete: message,
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
onCancelDeleteMessage = () => {
|
|
280
|
+
this.setState({
|
|
281
|
+
showDeleteMessageConfirm: false,
|
|
282
|
+
messageToDelete: null,
|
|
283
|
+
});
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
onConfirmDeleteMessage = async () => {
|
|
287
|
+
const { messageToDelete } = this.state;
|
|
288
|
+
if (!messageToDelete) return;
|
|
289
|
+
|
|
290
|
+
this.setState({
|
|
291
|
+
showDeleteMessageConfirm: false,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
// Call the parent's delete handler if provided
|
|
296
|
+
if (this.props.onDeleteMessage) {
|
|
297
|
+
await this.props.onDeleteMessage(messageToDelete);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this.setState({
|
|
301
|
+
messageToDelete: null,
|
|
302
|
+
});
|
|
303
|
+
} catch (error) {
|
|
304
|
+
// Handle error gracefully if parent handler fails
|
|
305
|
+
console.log('onConfirmDeleteMessage error', error);
|
|
306
|
+
// Error is already handled by parent component
|
|
307
|
+
this.setState({ messageToDelete: null });
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
259
311
|
closeGallery() {
|
|
260
312
|
this.setState({
|
|
261
313
|
imagePopupSource: [],
|
|
@@ -353,14 +405,28 @@ class PlussChat extends Component {
|
|
|
353
405
|
onPressReply={() => {
|
|
354
406
|
this.onReply(props.currentMessage);
|
|
355
407
|
}}
|
|
408
|
+
onPressDelete={this.props.onDeleteMessage ? () => {
|
|
409
|
+
this.onDelete(props.currentMessage);
|
|
410
|
+
} : null}
|
|
356
411
|
{...props}
|
|
357
412
|
/>
|
|
358
413
|
);
|
|
359
414
|
}
|
|
360
415
|
renderMessageText(messageTextProps) {
|
|
416
|
+
// If message is deleted, show placeholder text
|
|
417
|
+
const props = messageTextProps.currentMessage.deleted
|
|
418
|
+
? {
|
|
419
|
+
...messageTextProps,
|
|
420
|
+
currentMessage: {
|
|
421
|
+
...messageTextProps.currentMessage,
|
|
422
|
+
text: '[Message deleted]',
|
|
423
|
+
},
|
|
424
|
+
}
|
|
425
|
+
: messageTextProps;
|
|
426
|
+
|
|
361
427
|
return (
|
|
362
428
|
<MessageText
|
|
363
|
-
{...
|
|
429
|
+
{...props}
|
|
364
430
|
textStyle={{
|
|
365
431
|
left: {
|
|
366
432
|
fontFamily: 'sf-regular',
|
|
@@ -408,6 +474,11 @@ class PlussChat extends Component {
|
|
|
408
474
|
);
|
|
409
475
|
}
|
|
410
476
|
renderCustomView({ currentMessage, position }) {
|
|
477
|
+
// Don't show images or attachments for deleted messages
|
|
478
|
+
if (currentMessage.deleted) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
411
482
|
if (currentMessage.image) {
|
|
412
483
|
const images = typeof currentMessage.image === 'string' ? [currentMessage.image] : currentMessage.image;
|
|
413
484
|
const containerWidth = (() => {
|
|
@@ -765,6 +836,19 @@ class PlussChat extends Component {
|
|
|
765
836
|
);
|
|
766
837
|
}
|
|
767
838
|
|
|
839
|
+
renderDeleteConfirmPopup() {
|
|
840
|
+
return (
|
|
841
|
+
<ConfirmPopup
|
|
842
|
+
visible={this.state.showDeleteMessageConfirm}
|
|
843
|
+
onConfirm={this.onConfirmDeleteMessage}
|
|
844
|
+
onCancel={this.onCancelDeleteMessage}
|
|
845
|
+
text="Are you sure you want to delete this message?"
|
|
846
|
+
yesText="Delete"
|
|
847
|
+
noText="Cancel"
|
|
848
|
+
/>
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
|
|
768
852
|
render() {
|
|
769
853
|
if (Platform.OS === 'android' && !this.props.noAndroidAvoid) {
|
|
770
854
|
return (
|
|
@@ -774,6 +858,7 @@ class PlussChat extends Component {
|
|
|
774
858
|
{this.renderImagePopup()}
|
|
775
859
|
{this.renderVideoPlayerPopup()}
|
|
776
860
|
{this.renderPDF()}
|
|
861
|
+
{this.renderDeleteConfirmPopup()}
|
|
777
862
|
</KeyboardAvoidingView>
|
|
778
863
|
);
|
|
779
864
|
}
|
|
@@ -784,6 +869,7 @@ class PlussChat extends Component {
|
|
|
784
869
|
{this.renderImagePopup()}
|
|
785
870
|
{this.renderVideoPlayerPopup()}
|
|
786
871
|
{this.renderPDF()}
|
|
872
|
+
{this.renderDeleteConfirmPopup()}
|
|
787
873
|
</View>
|
|
788
874
|
);
|
|
789
875
|
}
|